From 4e524a0c61f03defd82f63d5929c93797722cf63 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 3 Aug 2022 08:59:56 +0200 Subject: [PATCH 001/198] RUBY-2512 Support AWS temporary credentials (#2572) --- .evergreen/config.yml | 8 +++ .evergreen/config/common.yml.erb | 8 +++ .evergreen/run-tests.sh | 4 +- lib/mongo/client.rb | 6 ++ spec/runners/transactions/test.rb | 31 ++++++++++ .../client_side_encryption/awsTemporary.yml | 57 +++++++++++++++++++ spec/support/spec_config.rb | 15 +++++ 7 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 spec/spec_tests/data/client_side_encryption/awsTemporary.yml diff --git a/.evergreen/config.yml b/.evergreen/config.yml index ccf5cff9fd..00d7b7694d 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -128,6 +128,14 @@ functions: export OCSP_MUST_STAPLE="${OCSP_MUST_STAPLE}" export OCSP_CONNECTIVITY="${OCSP_CONNECTIVITY}" export OCSP_VERIFIER="${OCSP_VERIFIER}" + + # Needed for generating temporary aws credentials. + if [[ -v FLE ]]; + then + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + fi EOT # See what we've done diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index b3bde64c2c..e4508c4c1c 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -195,6 +195,14 @@ functions: export OCSP_MUST_STAPLE="${OCSP_MUST_STAPLE}" export OCSP_CONNECTIVITY="${OCSP_CONNECTIVITY}" export OCSP_VERIFIER="${OCSP_VERIFIER}" + + # Needed for generating temporary aws credentials. + if [[ -v FLE ]]; + then + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + fi EOT # See what we've done diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 0e07e1f6fd..cff14598be 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -167,7 +167,6 @@ if test -n "$FLE"; then export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=`pwd`/csfle_lib/lib/mongo_crypt_v1.so fi - # Start the KMS servers first so that they are launching while we are # fetching libmongocrypt. if test "$DOCKER_PRELOAD" != 1; then @@ -187,6 +186,9 @@ if test -n "$FLE"; then python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/server.pem --port 8002 --require_client_cert & python3 -u .evergreen/csfle/kms_kmip_server.py & + # Obtain temporary AWS credentials + PYTHON=python3 . .evergreen/csfle/set-temp-creds.sh + if test "$FLE" = helper; then echo "Using helper gem" elif test "$FLE" = path; then diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index 6953fe9588..08856889fe 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -462,6 +462,12 @@ def hash # and schemaMap, an error will be raised. # - :bypass_query_analysis => Boolean | nil, when true disables automatic # analysis of outgoing commands. + # - :crypt_shared_lib_path => [ String | nil ] Path that should + # be the used to load the crypt shared library. Providing this option + # overrides default crypt shared library load paths for libmongocrypt. + # - :crypt_shared_lib_required => [ Boolean | nil ] Whether + # crypt shared library is required. If 'true', an error will be raised + # if a crypt_shared library cannot be loaded by libmongocrypt. # # Notes on automatic encryption: # - Automatic encryption is an enterprise only feature that only applies diff --git a/spec/runners/transactions/test.rb b/spec/runners/transactions/test.rb index ba2a138714..3f6e8ab298 100644 --- a/spec/runners/transactions/test.rb +++ b/spec/runners/transactions/test.rb @@ -132,6 +132,37 @@ def test_client test_client.subscribe(Mongo::Monitoring::CONNECTION_POOL, sdam_subscriber) end + if kms_providers = @client_options.dig(:auto_encryption_options, :kms_providers) + @client_options[:auto_encryption_options][:kms_providers] = kms_providers.map do |provider, opts| + case provider + when :aws_temporary + [ + :aws, + { + access_key_id: SpecConfig.instance.fle_aws_temp_key, + secret_access_key: SpecConfig.instance.fle_aws_temp_secret, + session_token: SpecConfig.instance.fle_aws_temp_session_token, + } + ] + when :aws_temporary_no_session_token + [ + :aws, + { + access_key_id: SpecConfig.instance.fle_aws_temp_key, + secret_access_key: SpecConfig.instance.fle_aws_temp_secret, + } + ] + else + [provider, opts] + end + end.to_h + end + + if @client_options[:auto_encryption_options] && SpecConfig.instance.crypt_shared_lib_path + @client_options[:auto_encryption_options][:extra_options] ||= {} + @client_options[:auto_encryption_options][:extra_options][:crypt_shared_lib_path] = SpecConfig.instance.crypt_shared_lib_path + end + ClientRegistry.instance.new_local_client( SpecConfig.instance.addresses, SpecConfig.instance.authorized_test_options.merge( diff --git a/spec/spec_tests/data/client_side_encryption/awsTemporary.yml b/spec/spec_tests/data/client_side_encryption/awsTemporary.yml new file mode 100644 index 0000000000..cc787530a0 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/awsTemporary.yml @@ -0,0 +1,57 @@ +runOn: + - minServerVersion: "4.1.10" +database_name: &database_name "default" +collection_name: &collection_name "default" + +data: [] +json_schema: {'properties': {'encrypted_w_altname': {'encrypt': {'keyId': '/altname', 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'}}, 'encrypted_string': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'random': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'}}, 'encrypted_string_equivalent': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} +key_vault_data: [{'status': 1, '_id': {'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}, 'masterKey': {'provider': 'aws', 'key': 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0', 'region': 'us-east-1'}, 'updateDate': {'$date': {'$numberLong': '1552949630483'}}, 'keyMaterial': {'$binary': {'base64': 'AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1552949630483'}}, 'keyAltNames': ['altname', 'another_altname']}] + +tests: + - description: "Insert a document with auto encryption using the AWS provider with temporary credentials" + clientOptions: + autoEncryptOpts: + kmsProviders: + awsTemporary: {} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 1, encrypted_string: "string0" } + expectations: + # Auto encryption will request the collection info. + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + # Then key is fetched from the key vault. + - command_started_event: + command: + find: datakeys + filter: { $or: [ { _id: { $in: [ {'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}} ] } }, { keyAltNames: { $in: [] } } ] } + $db: keyvault + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { _id: 1, encrypted_string: {'$binary': {'base64': 'AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==', 'subType': '06'}} } + ordered: true + command_name: insert + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - *doc0_encrypted + - description: "Insert with invalid temporary credentials" + clientOptions: + autoEncryptOpts: + kmsProviders: + awsTemporaryNoSessionToken: {} + operations: + - name: insertOne + arguments: + document: *doc0 + result: + errorContains: "security token" \ No newline at end of file diff --git a/spec/support/spec_config.rb b/spec/support/spec_config.rb index 756027e2aa..bcf2301d40 100644 --- a/spec/support/spec_config.rb +++ b/spec/support/spec_config.rb @@ -393,6 +393,21 @@ def fle_aws_arn ENV['MONGO_RUBY_DRIVER_AWS_ARN'] end + # AWS temporary access key id (set by set-temp-creds.sh) + def fle_aws_temp_key + ENV['CSFLE_AWS_TEMP_ACCESS_KEY_ID'] + end + + # AWS temporary secret access key (set by set-temp-creds.sh) + def fle_aws_temp_secret + ENV['CSFLE_AWS_TEMP_SECRET_ACCESS_KEY'] + end + + # AWS temporary session token (set by set-temp-creds.sh) + def fle_aws_temp_session_token + ENV['CSFLE_AWS_TEMP_SESSION_TOKEN'] + end + def fle_azure_tenant_id ENV['MONGO_RUBY_DRIVER_AZURE_TENANT_ID'] end From d985b2e0d038e9c3c002cac987dc63b5040f3223 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 3 Aug 2022 09:01:02 +0200 Subject: [PATCH 002/198] RUBY-2497 Add test for limited connection pool (#2573) --- .../auto_encryption_spec.rb | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/spec/integration/client_side_encryption/auto_encryption_spec.rb b/spec/integration/client_side_encryption/auto_encryption_spec.rb index 3f1c05ae1a..df6fb5b31f 100644 --- a/spec/integration/client_side_encryption/auto_encryption_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_spec.rb @@ -29,7 +29,8 @@ # Spawn mongocryptd on non-default port for sharded cluster tests extra_options: extra_options, }, - database: 'auto_encryption' + database: 'auto_encryption', + max_pool_size: max_pool_size ), ) end @@ -38,6 +39,10 @@ let(:bypass_auto_encryption) { false } + let(:max_pool_size) do + Mongo::Server::ConnectionPool::DEFAULT_MAX_SIZE + end + let(:encrypted_ssn_binary) do BSON::Binary.new(Base64.decode64(encrypted_ssn), :ciphertext) end @@ -79,6 +84,12 @@ end end + shared_context 'limited connection pool' do + let(:max_pool_size) do + 1 + end + end + before(:each) do client['users'].drop key_vault_collection.drop @@ -97,6 +108,11 @@ context 'with schema map' do include_context 'schema map in client options' it_behaves_like 'it performs an encrypted command' + + context 'with limited connection pool' do + include_context 'limited connection pool' + it_behaves_like 'it performs an encrypted command' + end end end @@ -111,6 +127,11 @@ context 'with schema map' do include_context 'schema map in client options' it_behaves_like 'it performs an encrypted command' + + context 'with limited connection pool' do + include_context 'limited connection pool' + it_behaves_like 'it performs an encrypted command' + end end end @@ -125,6 +146,11 @@ context 'with schema map' do include_context 'schema map in client options' it_behaves_like 'it performs an encrypted command' + + context 'with limited connection pool' do + include_context 'limited connection pool' + it_behaves_like 'it performs an encrypted command' + end end end @@ -139,6 +165,11 @@ context 'with schema map' do include_context 'schema map in client options' it_behaves_like 'it performs an encrypted command' + + context 'with limited connection pool' do + include_context 'limited connection pool' + it_behaves_like 'it performs an encrypted command' + end end end @@ -153,6 +184,11 @@ context 'with schema map' do include_context 'schema map in client options' it_behaves_like 'it performs an encrypted command' + + context 'with limited connection pool' do + include_context 'limited connection pool' + it_behaves_like 'it performs an encrypted command' + end end end end From 2c7afb473f0179c02958723721743172333eb346 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 3 Aug 2022 16:49:08 +0200 Subject: [PATCH 003/198] RUBY-3080 Fix FLE tests in docker (#2580) --- .evergreen/run-tests-docker.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.evergreen/run-tests-docker.sh b/.evergreen/run-tests-docker.sh index f3605ebb96..0dbebc22d5 100755 --- a/.evergreen/run-tests-docker.sh +++ b/.evergreen/run-tests-docker.sh @@ -13,7 +13,8 @@ fi params= for var in MONGODB_VERSION TOPOLOGY RVM_RUBY \ OCSP_ALGORITHM OCSP_STATUS OCSP_DELEGATE OCSP_MUST_STAPLE \ - OCSP_CONNECTIVITY OCSP_VERIFIER FLE + OCSP_CONNECTIVITY OCSP_VERIFIER FLE \ + AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION do value="${!var}" if test -n "$value"; then From f649313dd3dddea1f63d690e34237e2ac7ce9ee6 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 3 Aug 2022 17:04:26 +0200 Subject: [PATCH 004/198] RUBY-3081 Avoid creating mongocryptd client (#2579) --- lib/mongo/crypt/auto_encrypter.rb | 4 ++-- spec/mongo/crypt/auto_encrypter_spec.rb | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/mongo/crypt/auto_encrypter.rb b/lib/mongo/crypt/auto_encrypter.rb index 31bdd68094..413e282e1f 100644 --- a/lib/mongo/crypt/auto_encrypter.rb +++ b/lib/mongo/crypt/auto_encrypter.rb @@ -117,7 +117,7 @@ def initialize(options) @crypt_handle.crypt_shared_lib_available? || @options[:extra_options][:crypt_shared_lib_required] - if !@options[:extra_options][:crypt_shared_lib_required] + unless @options[:extra_options][:crypt_shared_lib_required] || @crypt_handle.crypt_shared_lib_available? # Set server selection timeout to 1 to prevent the client waiting for a # long timeout before spawning mongocryptd @mongocryptd_client = Client.new( @@ -135,7 +135,7 @@ def initialize(options) key_vault_namespace: @options[:key_vault_namespace], key_vault_client: @key_vault_client, metadata_client: @metadata_client, - mongocryptd_options: @options[:extra_options] + mongocryptd_options: @mongocryptd_options ) rescue begin diff --git a/spec/mongo/crypt/auto_encrypter_spec.rb b/spec/mongo/crypt/auto_encrypter_spec.rb index 3282396d44..eab89e96b8 100644 --- a/spec/mongo/crypt/auto_encrypter_spec.rb +++ b/spec/mongo/crypt/auto_encrypter_spec.rb @@ -157,6 +157,13 @@ expect(auto_encrypter.metadata_client).not_to eq(client) end end + + context 'when crypt shared library is available' do + it 'does not create a mongocryptd client' do + allow_any_instance_of(Mongo::Crypt::Handle).to receive(:"crypt_shared_lib_available?").and_return true + expect(auto_encrypter.mongocryptd_client).to be_nil + end + end end shared_examples 'with schema map in auto encryption commands' do From b9671fc1ba027c20d44969ef252e1bd68856a35e Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 3 Aug 2022 14:59:34 -0400 Subject: [PATCH 005/198] RUBY-2757 Bump maxWireVersion in SDAM monitoring tests (#2574) --- .../sdam_monitoring/discovered_standalone.yml | 2 +- .../replica_set_with_no_primary.yml | 77 +++++++++---------- .../replica_set_with_primary.yml | 77 +++++++++---------- .../replica_set_with_removal.yml | 2 +- .../sdam_monitoring/required_replica_set.yml | 2 +- .../data/sdam_monitoring/standalone.yml | 2 +- ...one_suppress_equal_description_changes.yml | 4 +- 7 files changed, 82 insertions(+), 84 deletions(-) diff --git a/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml b/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml index 0ad4462b9c..5d808f2600 100644 --- a/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml +++ b/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml @@ -5,7 +5,7 @@ phases: responses: - - "a:27017" - - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 4 } + - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 6 } outcome: events: diff --git a/spec/spec_tests/data/sdam_monitoring/replica_set_with_no_primary.yml b/spec/spec_tests/data/sdam_monitoring/replica_set_with_no_primary.yml index 8a49869f60..ce9cdf46e6 100644 --- a/spec/spec_tests/data/sdam_monitoring/replica_set_with_no_primary.yml +++ b/spec/spec_tests/data/sdam_monitoring/replica_set_with_no_primary.yml @@ -1,11 +1,11 @@ description: "Monitoring a topology that is a replica set with no primary connected" uri: "mongodb://a,b" -phases: - - - responses: - - +phases: + - + responses: + - - "a:27017" - - + - ok: 1 helloOk: true isWritablePrimary: false @@ -13,101 +13,100 @@ phases: setName: "rs" setVersion: 1 primary: "b:27017" - hosts: + hosts: - "a:27017" - "b:27017" minWireVersion: 0 - maxWireVersion: 4 - outcome: - events: - - - topology_opening_event: + maxWireVersion: 6 + outcome: + events: + - + topology_opening_event: topologyId: "42" - - - topology_description_changed_event: + - + topology_description_changed_event: topologyId: "42" - previousDescription: + previousDescription: topologyType: "Unknown" servers: [] - newDescription: + newDescription: topologyType: "Unknown" - servers: - - + servers: + - address: "a:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - - + - address: "b:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - - - server_opening_event: + - + server_opening_event: topologyId: "42" address: "a:27017" - - - server_opening_event: + - + server_opening_event: topologyId: "42" address: "b:27017" - - - server_description_changed_event: + - + server_description_changed_event: topologyId: "42" address: "a:27017" - previousDescription: + previousDescription: address: "a:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - newDescription: + newDescription: address: "a:27017" arbiters: [] - hosts: + hosts: - "a:27017" - "b:27017" passives: [] primary: "b:27017" setName: "rs" type: "RSSecondary" - - - topology_description_changed_event: + - + topology_description_changed_event: topologyId: "42" - previousDescription: + previousDescription: topologyType: "Unknown" - servers: - - + servers: + - address: "a:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - - + - address: "b:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - newDescription: + newDescription: topologyType: "ReplicaSetNoPrimary" setName: "rs" - servers: - - + servers: + - address: "a:27017" arbiters: [] - hosts: + hosts: - "a:27017" - "b:27017" passives: [] primary: "b:27017" setName: "rs" type: "RSSecondary" - - + - address: "b:27017" arbiters: [] hosts: [] passives: [] type: "PossiblePrimary" - diff --git a/spec/spec_tests/data/sdam_monitoring/replica_set_with_primary.yml b/spec/spec_tests/data/sdam_monitoring/replica_set_with_primary.yml index 016240f49d..2c78919063 100644 --- a/spec/spec_tests/data/sdam_monitoring/replica_set_with_primary.yml +++ b/spec/spec_tests/data/sdam_monitoring/replica_set_with_primary.yml @@ -1,112 +1,111 @@ description: "Monitoring a topology that is a replica set with a primary connected" uri: "mongodb://a,b" -phases: - - - responses: - - +phases: + - + responses: + - - "a:27017" - - + - ok: 1 helloOk: true isWritablePrimary: true setName: "rs" setVersion: 1 primary: "a:27017" - hosts: + hosts: - "a:27017" - "b:27017" minWireVersion: 0 - maxWireVersion: 4 - outcome: - events: - - - topology_opening_event: + maxWireVersion: 6 + outcome: + events: + - + topology_opening_event: topologyId: "42" - - - topology_description_changed_event: + - + topology_description_changed_event: topologyId: "42" - previousDescription: + previousDescription: topologyType: "Unknown" servers: [] - newDescription: + newDescription: topologyType: "Unknown" - servers: - - + servers: + - address: "a:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - - + - address: "b:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - - - server_opening_event: + - + server_opening_event: topologyId: "42" address: "a:27017" - - - server_opening_event: + - + server_opening_event: topologyId: "42" address: "b:27017" - - - server_description_changed_event: + - + server_description_changed_event: topologyId: "42" address: "a:27017" - previousDescription: + previousDescription: address: "a:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - newDescription: + newDescription: address: "a:27017" arbiters: [] - hosts: + hosts: - "a:27017" - "b:27017" passives: [] primary: "a:27017" setName: "rs" type: "RSPrimary" - - - topology_description_changed_event: + - + topology_description_changed_event: topologyId: "42" - previousDescription: + previousDescription: topologyType: "Unknown" - servers: - - + servers: + - address: "a:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - - + - address: "b:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - newDescription: + newDescription: topologyType: "ReplicaSetWithPrimary" setName: "rs" - servers: - - + servers: + - address: "a:27017" arbiters: [] - hosts: + hosts: - "a:27017" - "b:27017" passives: [] primary: "a:27017" setName: "rs" type: "RSPrimary" - - + - address: "b:27017" arbiters: [] hosts: [] passives: [] type: "Unknown" - diff --git a/spec/spec_tests/data/sdam_monitoring/replica_set_with_removal.yml b/spec/spec_tests/data/sdam_monitoring/replica_set_with_removal.yml index e2277abd32..2471f47680 100644 --- a/spec/spec_tests/data/sdam_monitoring/replica_set_with_removal.yml +++ b/spec/spec_tests/data/sdam_monitoring/replica_set_with_removal.yml @@ -50,7 +50,7 @@ phases: primary: "a:27017", hosts: [ "a:27017" ], minWireVersion: 0, - maxWireVersion: 4 + maxWireVersion: 6 } - - "b:27017" diff --git a/spec/spec_tests/data/sdam_monitoring/required_replica_set.yml b/spec/spec_tests/data/sdam_monitoring/required_replica_set.yml index 4463967668..7a060128f3 100644 --- a/spec/spec_tests/data/sdam_monitoring/required_replica_set.yml +++ b/spec/spec_tests/data/sdam_monitoring/required_replica_set.yml @@ -14,7 +14,7 @@ phases: primary: "a:27017", hosts: [ "a:27017", "b:27017" ], minWireVersion: 0, - maxWireVersion: 4 + maxWireVersion: 6 } outcome: events: diff --git a/spec/spec_tests/data/sdam_monitoring/standalone.yml b/spec/spec_tests/data/sdam_monitoring/standalone.yml index 780a032a13..d9f6bcfaf3 100644 --- a/spec/spec_tests/data/sdam_monitoring/standalone.yml +++ b/spec/spec_tests/data/sdam_monitoring/standalone.yml @@ -5,7 +5,7 @@ phases: responses: - - "a:27017" - - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 4 } + - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 6 } outcome: events: diff --git a/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml b/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml index 00c2e770b9..f2f83ffbac 100644 --- a/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml +++ b/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml @@ -5,10 +5,10 @@ phases: responses: - - "a:27017" - - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 4 } + - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 6 } - - "a:27017" - - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 4 } + - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 6 } outcome: events: From 657ad734ca25032fb5bb368b5372ad0b14ec2100 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 3 Aug 2022 15:01:43 -0400 Subject: [PATCH 006/198] RUBY-2765 change Populator to Mongo::Server::ConnectionPool::Populator (#2576) --- lib/mongo/server/connection_pool/populator.rb | 62 ++++++++++--------- .../server/connection_pool/populator_spec.rb | 2 +- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/lib/mongo/server/connection_pool/populator.rb b/lib/mongo/server/connection_pool/populator.rb index 00169e3fd4..c1892abe69 100644 --- a/lib/mongo/server/connection_pool/populator.rb +++ b/lib/mongo/server/connection_pool/populator.rb @@ -17,43 +17,45 @@ module Mongo class Server - # A manager that maintains the invariant that the - # size of a connection pool is at least minPoolSize. - # - # @api private - class Populator - include BackgroundThread - - # @param [ Server::ConnectionPool ] pool The connection pool. - # @param [ Hash ] options The options. + class ConnectionPool + # A manager that maintains the invariant that the + # size of a connection pool is at least minPoolSize. # - # @option options [ Logger ] :logger A custom logger to use. - def initialize(pool, options = {}) - @pool = pool - @thread = nil - @options = options - end + # @api private + class Populator + include BackgroundThread + + # @param [ Server::ConnectionPool ] pool The connection pool. + # @param [ Hash ] options The options. + # + # @option options [ Logger ] :logger A custom logger to use. + def initialize(pool, options = {}) + @pool = pool + @thread = nil + @options = options + end - attr_reader :options + attr_reader :options - def pre_stop - @pool.populate_semaphore.signal - end + def pre_stop + @pool.populate_semaphore.signal + end - private + private - def do_work - throw(:done) if @pool.closed? + def do_work + throw(:done) if @pool.closed? - begin - unless @pool.populate - @pool.populate_semaphore.wait + begin + unless @pool.populate + @pool.populate_semaphore.wait + end + rescue Error::AuthError, Error => e + # Errors encountered when trying to add connections to + # pool; try again later + log_warn("Populator failed to connect a connection for #{@pool.address}: #{e.class}: #{e}.") + @pool.populate_semaphore.wait(5) end - rescue Error::AuthError, Error => e - # Errors encountered when trying to add connections to - # pool; try again later - log_warn("Populator failed to connect a connection for #{@pool.address}: #{e.class}: #{e}.") - @pool.populate_semaphore.wait(5) end end end diff --git a/spec/mongo/server/connection_pool/populator_spec.rb b/spec/mongo/server/connection_pool/populator_spec.rb index 7d8784622b..9d121326a8 100644 --- a/spec/mongo/server/connection_pool/populator_spec.rb +++ b/spec/mongo/server/connection_pool/populator_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' -describe Mongo::Server::Populator do +describe Mongo::Server::ConnectionPool::Populator do let(:options) { {} } let(:client) do From b174d243dfc4cc78c455574639766c004cb86eb4 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 4 Aug 2022 08:44:00 +0200 Subject: [PATCH 007/198] RUBY-3074 Add rewrapManyDataKey prose test (#2581) --- lib/mongo/client_encryption.rb | 1 - .../rewrap_prose_spec.rb | 99 +++++++++++++++++++ .../createDataKey-kms_providers-invalid.yml | 2 +- 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 spec/integration/client_side_encryption/rewrap_prose_spec.rb diff --git a/lib/mongo/client_encryption.rb b/lib/mongo/client_encryption.rb index 364c63fdf2..e6cdb595db 100644 --- a/lib/mongo/client_encryption.rb +++ b/lib/mongo/client_encryption.rb @@ -204,6 +204,5 @@ def remove_key_alt_name(id, key_alt_name) def rewrap_many_data_key(filter, opts = {}) @encrypter.rewrap_many_data_key(filter, opts) end - end end diff --git a/spec/integration/client_side_encryption/rewrap_prose_spec.rb b/spec/integration/client_side_encryption/rewrap_prose_spec.rb new file mode 100644 index 0000000000..351ae2c269 --- /dev/null +++ b/spec/integration/client_side_encryption/rewrap_prose_spec.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require "spec_helper" + +describe "RewrapManyDataKey" do + require_libmongocrypt + min_server_version "6.0.0" + require_topology :replica_set, :sharded, :load_balanced + + include_context "define shared FLE helpers" + + let(:kms_providers) do + {}.merge(aws_kms_providers) + .merge(azure_kms_providers) + .merge(gcp_kms_providers) + .merge(kmip_kms_providers) + .merge(local_kms_providers) + end + + let(:master_keys) do + { + aws: { + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + }, + azure: { + key_vault_endpoint: "key-vault-csfle.vault.azure.net", + key_name: "key-name-csfle", + }, + gcp: { + project_id: "devprod-drivers", + location: "global", + key_ring: "key-ring-csfle", + key_name: "key-name-csfle", + }, + kmip: {} + } + end + + before(:each) do + authorized_client.use('keyvault')['datakeys'].drop + end + + %i(aws azure gcp kmip local).each do |src_provider| + %i(aws azure gcp kmip local).each do |dst_provider| + context "with #{src_provider} as source provider and #{dst_provider} as destination provider" do + let(:client_encryption_1) do + key_vault_client = ClientRegistry.instance.new_local_client( + SpecConfig.instance.addresses, + SpecConfig.instance.test_options + ) + Mongo::ClientEncryption.new( + key_vault_client, + key_vault_namespace: 'keyvault.datakeys', + kms_providers: kms_providers, + kms_tls_options: { + kmip: default_kms_tls_options_for_provider + }, + ) + end + + let(:client_encryption_2) do + key_vault_client = ClientRegistry.instance.new_local_client( + SpecConfig.instance.addresses, + SpecConfig.instance.test_options + ) + Mongo::ClientEncryption.new( + key_vault_client, + key_vault_namespace: 'keyvault.datakeys', + kms_providers: kms_providers, + kms_tls_options: { + kmip: default_kms_tls_options_for_provider + }, + ) + end + + it 'rewraps' do + key_id = client_encryption_1.create_data_key( + src_provider.to_s, + master_key: master_keys[src_provider] + ) + ciphertext = client_encryption_1.encrypt( + "test", + key_id: key_id, + algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + ) + client_encryption_2.rewrap_many_data_key( + {}, + provider: dst_provider.to_s, + master_key: master_keys[dst_provider] + ) + expect(client_encryption_1.decrypt(ciphertext)).to eq('test') + expect(client_encryption_2.decrypt(ciphertext)).to eq('test') + end + end + end + end +end diff --git a/spec/spec_tests/data/client_side_encryption/unified/createDataKey-kms_providers-invalid.yml b/spec/spec_tests/data/client_side_encryption/unified/createDataKey-kms_providers-invalid.yml index 70e51acbdc..f692a09075 100644 --- a/spec/spec_tests/data/client_side_encryption/unified/createDataKey-kms_providers-invalid.yml +++ b/spec/spec_tests/data/client_side_encryption/unified/createDataKey-kms_providers-invalid.yml @@ -1,4 +1,4 @@ -description: createDataKey-provider-invalid +description: createDataKey-kms_providers-invalid schemaVersion: "1.8" From 713b15dd2e257796e4c2b8c25b4df745005a07b3 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 4 Aug 2022 15:31:16 -0400 Subject: [PATCH 008/198] RUBY-2759 Add minItems unified spec test (#2575) --- spec/spec_tests/data/unified/tests-minItems.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 spec/spec_tests/data/unified/tests-minItems.yml diff --git a/spec/spec_tests/data/unified/tests-minItems.yml b/spec/spec_tests/data/unified/tests-minItems.yml new file mode 100644 index 0000000000..834120f835 --- /dev/null +++ b/spec/spec_tests/data/unified/tests-minItems.yml @@ -0,0 +1,5 @@ +description: "tests-minItems" + +schemaVersion: "1.0" + +tests: [] From 2fbbdde58ecee6ebb99a65aa333ad8f5cc3fb0a9 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 4 Aug 2022 15:31:35 -0400 Subject: [PATCH 009/198] RUBY-2774 Split unified runner valid-fail tests with malformed operations into separate files (#2577) * RUBY-2774 add tests * RUBY-2774 fix tests * Update spec/runners/unified.rb * redo insert argument stuff * handle closes on unknown cursors Co-authored-by: Oleg Pudeyev --- spec/runners/unified.rb | 6 ++-- .../unified/change_stream_operations.rb | 10 ++++++ .../entity-findCursor-malformed.yml | 31 ++++++++++++++++ .../unified/valid-fail/entity-findCursor.yml | 31 ++++++++++++++++ .../ignoreResultAndError-malformed.yml | 34 ++++++++++++++++++ .../valid-fail/ignoreResultAndError.yml | 35 +++++++++++++++++++ spec/support/using_hash.rb | 11 +++++- 7 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 spec/spec_tests/data/unified/valid-fail/entity-findCursor-malformed.yml create mode 100644 spec/spec_tests/data/unified/valid-fail/entity-findCursor.yml create mode 100644 spec/spec_tests/data/unified/valid-fail/ignoreResultAndError-malformed.yml create mode 100644 spec/spec_tests/data/unified/valid-fail/ignoreResultAndError.yml diff --git a/spec/runners/unified.rb b/spec/runners/unified.rb index c82101c11c..9ca2ac4e46 100644 --- a/spec/runners/unified.rb +++ b/spec/runners/unified.rb @@ -69,11 +69,11 @@ def define_unified_spec_tests(base_path, paths, expect_failure: false) test.assert_events # HACK: other errors are possible and likely will need to # be added here later as the tests evolve. - rescue Mongo::Error::OperationFailure, Unified::Error::UnsupportedOperation + rescue Mongo::Error::OperationFailure, Unified::Error::UnsupportedOperation, UsingHash::UsingHashKeyError, Unified::Error::EntityMissing rescue => e - fail "Expected to raise Mongo::Error::OperationFailure or Unified::Error::UnsupportedOperation, got #{e}" + fail "Expected to raise Mongo::Error::OperationFailure or Unified::Error::UnsupportedOperation or UsingHash::UsingHashKeyError or Unified::Error::EntityMissing, got #{e.class}: #{e}" else - fail "Expected to raise Mongo::Error::OperationFailure or Unified::Error::UnsupportedOperation, but no error was raised" + fail "Expected to raise Mongo::Error::OperationFailure or Unified::Error::UnsupportedOperation or UsingHash::UsingHashKeyError or Unified::Error::EntityMissing, but no error was raised" end ensure test.cleanup diff --git a/spec/runners/unified/change_stream_operations.rb b/spec/runners/unified/change_stream_operations.rb index 6fa98d5971..ffe1cb98b3 100644 --- a/spec/runners/unified/change_stream_operations.rb +++ b/spec/runners/unified/change_stream_operations.rb @@ -37,5 +37,15 @@ def iterate_until_document_or_error(op) object = entities.get(:change_stream, object_id) object.to_enum.next end + + def close(op) + object_id = op.use!('object') + # The Ruby driver unified spec runner does not currently implement + # find cursors as created by createFindCursor. This will be done + # as part of CSOT implementation. When this is done, the line(s) below + # should be changed to retrieve such cursor instances and close them. + object = entities.get(:csot_cursor, object_id) + object.close + end end end diff --git a/spec/spec_tests/data/unified/valid-fail/entity-findCursor-malformed.yml b/spec/spec_tests/data/unified/valid-fail/entity-findCursor-malformed.yml new file mode 100644 index 0000000000..d4ebddda06 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-fail/entity-findCursor-malformed.yml @@ -0,0 +1,31 @@ +# This test is split out into a separate file to accomodate drivers that validate operation structure while decoding +# from JSON/YML. Such drivers fail to decode any files containing invalid operations. Combining this test in a file +# with other entity-findCursor valid-fail tests, which test failures that occur during test execution, would prevent +# such drivers from decoding the file and running any of the tests. +description: entity-findCursor-malformed + +schemaVersion: '1.3' + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] + +tests: + - description: createFindCursor fails if filter is not specified + operations: + - name: createFindCursor + object: *collection0 + saveResultAsEntity: &cursor0 cursor0 diff --git a/spec/spec_tests/data/unified/valid-fail/entity-findCursor.yml b/spec/spec_tests/data/unified/valid-fail/entity-findCursor.yml new file mode 100644 index 0000000000..d796c132d2 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-fail/entity-findCursor.yml @@ -0,0 +1,31 @@ +description: entity-findCursor + +schemaVersion: '1.3' + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] + +tests: + - description: iterateUntilDocumentOrError fails if it references a nonexistent entity + operations: + - name: iterateUntilDocumentOrError + object: cursor0 + + - description: close fails if it references a nonexistent entity + operations: + - name: close + object: cursor0 diff --git a/spec/spec_tests/data/unified/valid-fail/ignoreResultAndError-malformed.yml b/spec/spec_tests/data/unified/valid-fail/ignoreResultAndError-malformed.yml new file mode 100644 index 0000000000..a2bee42221 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-fail/ignoreResultAndError-malformed.yml @@ -0,0 +1,34 @@ +# This test is split out into a separate file to accomodate drivers that validate operation structure while decoding +# from JSON/YML. Such drivers fail to decode any files containing invalid operations. Combining this test in a file +# with other ignoreResultAndError valid-fail tests, which test failures that occur during test execution, would prevent +# such drivers from decoding the file and running any of the tests. +description: ignoreResultAndError-malformed + +schemaVersion: '1.3' + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - description: malformed operation fails if ignoreResultAndError is true + operations: + - name: insertOne + object: *collection0 + arguments: + foo: bar + ignoreResultAndError: true diff --git a/spec/spec_tests/data/unified/valid-fail/ignoreResultAndError.yml b/spec/spec_tests/data/unified/valid-fail/ignoreResultAndError.yml new file mode 100644 index 0000000000..802c601f92 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-fail/ignoreResultAndError.yml @@ -0,0 +1,35 @@ +description: ignoreResultAndError + +schemaVersion: '1.3' + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - description: operation errors are not ignored if ignoreResultAndError is false + operations: + - name: insertOne + object: *collection0 + arguments: + document: &insertDocument { _id: 1 } + - name: insertOne + object: *collection0 + arguments: + # Insert the same document to force a DuplicateKey error. + document: *insertDocument + ignoreResultAndError: false diff --git a/spec/support/using_hash.rb b/spec/support/using_hash.rb index 5f9106cb0d..db400b3ef1 100644 --- a/spec/support/using_hash.rb +++ b/spec/support/using_hash.rb @@ -2,6 +2,9 @@ # encoding: utf-8 class UsingHash < Hash + class UsingHashKeyError < KeyError + end + def use(key) wrap(self[key]).tap do delete(key) @@ -9,7 +12,13 @@ def use(key) end def use!(key) - wrap(fetch(key)).tap do + begin + value = fetch(key) + rescue KeyError => e + raise UsingHashKeyError, e.to_s + end + + wrap(value).tap do delete(key) end end From 6ce5a4f429d1a246a7f9f07b35ae6f6dbd8f63c0 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 5 Aug 2022 10:45:45 +0200 Subject: [PATCH 010/198] RUBY-3075 Sync fle2-InsertFind-Unindexed test (#2583) --- .../client_side_encryption/fle2-InsertFind-Unindexed.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml b/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml index 26071fff60..285b46d270 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml @@ -80,4 +80,8 @@ tests: arguments: filter: { encryptedUnindexed: "value123" } result: - errorContains: "Cannot query" \ No newline at end of file + # Expected error message changed in https://github.com/10gen/mongo-enterprise-modules/commit/212b584d4f7a44bed41c826a180a4aff00923d7a#diff-5f12b55e8d5c52c2f62853ec595dc2c1e2e5cb4fdbf7a32739a8e3acb3c6f818 + # Before the message was "cannot query non-indexed fields with the randomized encryption algorithm" + # After: "can only execute encrypted equality queries with an encrypted equality index" + # Use a small common substring. + errorContains: "encrypt" \ No newline at end of file From 7b6f3a15122723f8afd9ae23034dac4469f74fd5 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 5 Aug 2022 15:54:27 +0200 Subject: [PATCH 011/198] RUBY-3078 Retry unified KMS tests (#2584) --- spec/runners/unified.rb | 3 +++ spec/runners/unified/test.rb | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/spec/runners/unified.rb b/spec/runners/unified.rb index 9ca2ac4e46..d860f2a22d 100644 --- a/spec/runners/unified.rb +++ b/spec/runners/unified.rb @@ -48,6 +48,9 @@ def define_unified_spec_tests(base_path, paths, expect_failure: false) end end + if test.retry? + retry_test tries: 3 + end if expect_failure it 'fails as expected' do if test.group_reqs diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index c2f7353bee..3d3c55015c 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -56,6 +56,10 @@ def initialize(spec, **opts) attr_reader :reqs, :group_reqs attr_reader :options + def retry? + @description =~ /KMS/i + end + def skip? !!@skip_reason end @@ -71,6 +75,7 @@ def require_single_mongos? attr_reader :entities def create_entities + return if @entities_created @spec['createEntities'].each do |entity_spec| unless entity_spec.keys.length == 1 raise NotImplementedError, "Entity must have exactly one key" @@ -259,6 +264,7 @@ def create_entities end entities.set(type.to_sym, id, entity) end + @entities_created = true end def set_initial_data From b2ea49c720a3223d1a5791a062263e2c2ffb0051 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 5 Aug 2022 15:58:55 +0200 Subject: [PATCH 012/198] RUBY-3083 Fix AWS auth test configuration (#2585) --- .evergreen/config.yml | 2 +- .evergreen/config/common.yml.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 00d7b7694d..043c021fef 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -130,7 +130,7 @@ functions: export OCSP_VERIFIER="${OCSP_VERIFIER}" # Needed for generating temporary aws credentials. - if [[ -v FLE ]]; + if [ -n "${FLE}" ]; then export AWS_ACCESS_KEY_ID="${fle_aws_key}" export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index e4508c4c1c..29bd231356 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -197,7 +197,7 @@ functions: export OCSP_VERIFIER="${OCSP_VERIFIER}" # Needed for generating temporary aws credentials. - if [[ -v FLE ]]; + if [ -n "${FLE}" ]; then export AWS_ACCESS_KEY_ID="${fle_aws_key}" export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" From 9a586362c91420e8588bbb818132edaed239705a Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 5 Aug 2022 11:29:19 -0400 Subject: [PATCH 013/198] RUBY-1595 Improve error reporting when symbol/string is passed as read preference (#2582) * RUBY-1595 Improve error reporting when symbol/string is passed as read preference * tweak wording * RUBY-1595 fix tests Co-authored-by: Oleg Pudeyev --- lib/mongo/client.rb | 2 +- lib/mongo/error/invalid_read_option.rb | 2 +- spec/mongo/client_construction_spec.rb | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index 08856889fe..25f289f4ea 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -1576,7 +1576,7 @@ def validate_read!(option, opts) # for custom classes implementing key access ([]). # Instead reject common cases of strings and symbols. if read.is_a?(String) || read.is_a?(Symbol) - raise Error::InvalidReadOption.new(read, 'must be a hash') + raise Error::InvalidReadOption.new(read, "the read preference must be specified as a hash: { mode: #{read.inspect} }") end if mode = read[:mode] diff --git a/lib/mongo/error/invalid_read_option.rb b/lib/mongo/error/invalid_read_option.rb index 7115022f23..f1115d1b2a 100644 --- a/lib/mongo/error/invalid_read_option.rb +++ b/lib/mongo/error/invalid_read_option.rb @@ -31,7 +31,7 @@ class InvalidReadOption < Error # # @since 2.6.0 def initialize(read_option, msg) - super("Invalid read option: #{read_option}: #{msg}") + super("Invalid read preference value: #{read_option.inspect}: #{msg}") end end end diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index 5bc278beb4..24a2d8615c 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -1773,28 +1773,28 @@ expect do client = new_local_client_nmio(['127.0.0.1:27017'], :read => {:mode => :bogus}) - end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read option: {"mode"=>:bogus}: mode bogus is not one of recognized modes') + end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read preference value: {"mode"=>:bogus}: mode bogus is not one of recognized modes') end it 'rejects bogus read preference as string' do expect do client = new_local_client_nmio(['127.0.0.1:27017'], :read => {:mode => 'bogus'}) - end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read option: {"mode"=>"bogus"}: mode bogus is not one of recognized modes') + end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read preference value: {"mode"=>"bogus"}: mode bogus is not one of recognized modes') end it 'rejects read option specified as a string' do expect do client = new_local_client_nmio(['127.0.0.1:27017'], :read => 'primary') - end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read option: primary: must be a hash') + end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read preference value: "primary": the read preference must be specified as a hash: { mode: "primary" }') end it 'rejects read option specified as a symbol' do expect do client = new_local_client_nmio(['127.0.0.1:27017'], :read => :primary) - end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read option: primary: must be a hash') + end.to raise_error(Mongo::Error::InvalidReadOption, "Invalid read preference value: :primary: the read preference must be specified as a hash: { mode: :primary }") end end end From 82c858635375c22dc26ba28247d0692625f7644b Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Tue, 9 Aug 2022 11:55:08 -0400 Subject: [PATCH 014/198] RUBY-1627 Document collection insert options (#2587) * RUBY-1627 Document collection insert options * RUBY-1627 add comment option * RUBY-1627 fix wording * RUBY-1627 use pipe instead of comma * RUBY-1627 alphabetize the options --- lib/mongo/collection.rb | 62 +++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 8fe69fbbc0..821085dc98 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -83,7 +83,7 @@ class Collection # # @param [ Object ] other The object to check. # - # @return [ true, false ] If the objects are equal. + # @return [ true | false ] If the objects are equal. # # @since 2.0.0 def ==(other) @@ -237,7 +237,7 @@ def with(new_options) # @example Is the collection capped? # collection.capped? # - # @return [ true, false ] If the collection is capped. + # @return [ true | false ] If the collection is capped. # # @since 2.0.0 def capped? @@ -318,8 +318,10 @@ def create(opts = {}) # # @param [ Hash ] opts The options for the drop operation. # - # @option options [ Session ] :session The session to use for the operation. + # @option opts [ Session ] :session The session to use for the operation. # @option opts [ Hash ] :write_concern The write concern options. + # @option opts [ Hash | nil ] :encrypted_fields Encrypted fields hash that + # was provided to `create` collection helper. # # @return [ Result ] The result of the command. # @@ -356,27 +358,27 @@ def drop(opts = {}) # @param [ Hash ] filter The filter to use in the find. # @param [ Hash ] options The options for the find. # - # @option options [ true, false ] :allow_disk_use When set to true, the + # @option options [ true | false ] :allow_disk_use When set to true, the # server can write temporary data to disk while executing the find # operation. This option is only available on MongoDB server versions # 4.4 and newer. - # @option options [ true, false ] :allow_partial_results Allows the query to get partial + # @option options [ true | false ] :allow_partial_results Allows the query to get partial # results if some shards are down. # @option options [ Integer ] :batch_size The number of documents returned in each batch # of results from MongoDB. # @option options [ Hash ] :collation The collation to use. - # @option options [ Object ] :comment A user-provided - # comment to attach to this command. + # @option options [ Object ] :comment A user-provided comment to attach to + # this command. # @option options [ :tailable, :tailable_await ] :cursor_type The type of cursor to use. # @option options [ Integer ] :limit The max number of docs to return from the query. # @option options [ Integer ] :max_time_ms # The maximum amount of time to allow the query to run, in milliseconds. # @option options [ Hash ] :modifiers A document containing meta-operators modifying the # output or behavior of a query. - # @option options [ true, false ] :no_cursor_timeout The server normally times out idle + # @option options [ true | false ] :no_cursor_timeout The server normally times out idle # cursors after an inactivity period (10 minutes) to prevent excess memory use. # Set this option to prevent that. - # @option options [ true, false ] :oplog_replay For internal replication + # @option options [ true | false ] :oplog_replay For internal replication # use only, applications should not set this option. # @option options [ Hash ] :projection The fields to include or exclude from each doc # in the result set. @@ -402,11 +404,11 @@ def find(filter = nil, options = {}) # @param [ Array ] pipeline The aggregation pipeline. # @param [ Hash ] options The aggregation options. # - # @option options [ true, false ] :allow_disk_use Set to true if disk + # @option options [ true | false ] :allow_disk_use Set to true if disk # usage is allowed during the aggregation. # @option options [ Integer ] :batch_size The number of documents to return # per batch. - # @option options [ true, false ] :bypass_document_validation Whether or + # @option options [ true | false ] :bypass_document_validation Whether or # not to skip document level validation. # @option options [ Hash ] :collation The collation to use. # @option options [ Object ] :comment A user-provided @@ -416,7 +418,7 @@ def find(filter = nil, options = {}) # See the server documentation for details. # @option options [ Integer ] :max_time_ms The maximum amount of time in # milliseconds to allow the aggregation to run. - # @option options [ true, false ] :use_cursor Indicates whether the command + # @option options [ true | false ] :use_cursor Indicates whether the command # will request that the server provide results using a cursor. Note that # as of server version 3.6, aggregations always provide results using a # cursor and this option is therefore not valid. @@ -642,7 +644,13 @@ def inspect # @param [ Hash ] document The document to insert. # @param [ Hash ] opts The insert options. # + # @option opts [ true | false ] :bypass_document_validation Whether or + # not to skip document level validation. + # @option opts [ Object ] :comment A user-provided comment to attach to + # this command. # @option opts [ Session ] :session The session to use for the operation. + # @option opts [ Hash ] :write_concern The write concern options. + # Can be :w => Integer, :fsync => Boolean, :j => Boolean. # # @return [ Result ] The database response wrapper. # @@ -687,9 +695,15 @@ def insert_one(document, opts = {}) # @param [ Array ] documents The documents to insert. # @param [ Hash ] options The insert options. # + # @option options [ true | false ] :bypass_document_validation Whether or + # not to skip document level validation. + # @option options [ Object ] :comment A user-provided comment to attach to + # this command. # @option options [ true | false ] :ordered Whether the operations # should be executed in order. # @option options [ Session ] :session The session to use for the operation. + # @option options [ Hash ] :write_concern The write concern options. + # Can be :w => Integer, :fsync => Boolean, :j => Boolean. # # @return [ Result ] The database response wrapper. # @@ -709,11 +723,11 @@ def insert_many(documents, options = {}) # @param [ Array ] requests The bulk write requests. # @param [ Hash ] options The options. # - # @option options [ true, false ] :ordered Whether the operations + # @option options [ true | false ] :ordered Whether the operations # should be executed in order. # @option options [ Hash ] :write_concern The write concern options. # Can be :w => Integer, :fsync => Boolean, :j => Boolean. - # @option options [ true, false ] :bypass_document_validation Whether or + # @option options [ true | false ] :bypass_document_validation Whether or # not to skip document level validation. # @option options [ Session ] :session The session to use for the set of operations. # @option options [ Hash ] :let Mapping of variables to use in the command. @@ -802,9 +816,9 @@ def parallel_scan(cursor_count, options = {}) # @param [ Hash ] replacement The replacement document.. # @param [ Hash ] options The options. # - # @option options [ true, false ] :upsert Whether to upsert if the + # @option options [ true | false ] :upsert Whether to upsert if the # document doesn't exist. - # @option options [ true, false ] :bypass_document_validation Whether or + # @option options [ true | false ] :bypass_document_validation Whether or # not to skip document level validation. # @option options [ Hash ] :collation The collation to use. # @option options [ Session ] :session The session to use. @@ -829,9 +843,9 @@ def replace_one(filter, replacement, options = {}) # @param [ Hash | Array ] update The update document or pipeline. # @param [ Hash ] options The options. # - # @option options [ true, false ] :upsert Whether to upsert if the + # @option options [ true | false ] :upsert Whether to upsert if the # document doesn't exist. - # @option options [ true, false ] :bypass_document_validation Whether or + # @option options [ true | false ] :bypass_document_validation Whether or # not to skip document level validation. # @option options [ Hash ] :collation The collation to use. # @option options [ Array ] :array_filters A set of filters specifying to which array elements @@ -858,9 +872,9 @@ def update_many(filter, update, options = {}) # @param [ Hash | Array ] update The update document or pipeline. # @param [ Hash ] options The options. # - # @option options [ true, false ] :upsert Whether to upsert if the + # @option options [ true | false ] :upsert Whether to upsert if the # document doesn't exist. - # @option options [ true, false ] :bypass_document_validation Whether or + # @option options [ true | false ] :bypass_document_validation Whether or # not to skip document level validation. # @option options [ Hash ] :collation The collation to use. # @option options [ Array ] :array_filters A set of filters specifying to which array elements @@ -927,8 +941,8 @@ def find_one_and_delete(filter, options = {}) # @option options [ Hash ] :sort The key and direction pairs by which the result set # will be sorted. # @option options [ Symbol ] :return_document Either :before or :after. - # @option options [ true, false ] :upsert Whether to upsert if the document doesn't exist. - # @option options [ true, false ] :bypass_document_validation Whether or + # @option options [ true | false ] :upsert Whether to upsert if the document doesn't exist. + # @option options [ true | false ] :bypass_document_validation Whether or # not to skip document level validation. # @option options [ Hash ] :write_concern The write concern options. # Defaults to the collection's write concern. @@ -967,8 +981,8 @@ def find_one_and_update(filter, update, options = {}) # @option options [ Hash ] :sort The key and direction pairs by which the result set # will be sorted. # @option options [ Symbol ] :return_document Either :before or :after. - # @option options [ true, false ] :upsert Whether to upsert if the document doesn't exist. - # @option options [ true, false ] :bypass_document_validation Whether or + # @option options [ true | false ] :upsert Whether to upsert if the document doesn't exist. + # @option options [ true | false ] :bypass_document_validation Whether or # not to skip document level validation. # @option options [ Hash ] :write_concern The write concern options. # Defaults to the collection's write concern. From c3ec8c1031c7343a7984d23a2049daa473e36d40 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 10 Aug 2022 19:53:14 +0200 Subject: [PATCH 015/198] RUBY-2288 Verify clients closure (#2590) --- spec/mongo/client_construction_spec.rb | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index 24a2d8615c..3945de77ed 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -2111,6 +2111,53 @@ it 'is closed after block' do expect(block_client.cluster.connected?).to eq(false) end + + context 'with auto encryption options' do + require_libmongocrypt + min_server_fcv '4.2' + require_enterprise + clean_slate + + include_context 'define shared FLE helpers' + include_context 'with local kms_providers' + + let(:auto_encryption_options) do + { + key_vault_client: key_vault_client, + key_vault_namespace: key_vault_namespace, + kms_providers: kms_providers, + schema_map: schema_map, + extra_options: extra_options, + } + end + + let(:key_vault_client) { new_local_client_nmio(SpecConfig.instance.addresses) } + + let(:block_client) do + c = nil + Mongo::Client.new( + SpecConfig.instance.addresses, + SpecConfig.instance.test_options.merge( + auto_encryption_options: auto_encryption_options, + database: SpecConfig.instance.test_db + ), + ) do |client| + c = client + end + c + end + + it 'closes all clients after block' do + expect(block_client.cluster.connected?).to eq(false) + [ + block_client.encrypter.mongocryptd_client, + block_client.encrypter.key_vault_client, + block_client.encrypter.metadata_client + ].each do |crypt_client| + expect(crypt_client.cluster.connected?).to eq(false) + end + end + end end context 'when the block raises an error' do From 92027aba6155d3b7d9a1a4fe872e4f74a0828590 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 11 Aug 2022 17:03:00 -0400 Subject: [PATCH 016/198] RUBY-1673 Document collection options (#2588) * RUBY-1673 Document collection options * RUBY-1673 remove read_preference option * RUBY-1673 amend #with docs to include read_concern * RUBY-1673 add missing options * RUBY-1673 verify max and size options work * RUBY-1673 nitpick * RUBY-1673 alphabetize the options * RUBY-1673 fix mmap tests * RUBY-1673 fix mmap tests --- lib/mongo/collection.rb | 129 ++++++++++++++++++++++++----- lib/mongo/query_cache.rb | 40 ++++----- lib/mongo/session.rb | 2 +- spec/mongo/collection_crud_spec.rb | 20 +++++ spec/mongo/collection_spec.rb | 12 ++- 5 files changed, 162 insertions(+), 41 deletions(-) diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 821085dc98..66c9d620d7 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -100,19 +100,59 @@ def ==(other) # @param [ String, Symbol ] name The collection name. # @param [ Hash ] options The collection options. # - # @option options [ Hash ] :write Deprecated. Equivalent to :write_concern + # @option opts [ true | false ] :capped Create a fixed-sized collection. + # @option opts [ Hash ] :change_stream_pre_and_post_images Used to enable + # pre- and post-images on the created collection. + # The hash may have the following items: + # - *:enabled* -- true or false. + # @option opts [ Hash ] :clustered_index Create a clustered index. + # This option specifies how this collection should be clustered on _id. + # The hash may have the following items: + # - *:key* -- The clustered index key field. Must be set to { _id: 1 }. + # - *:unique* -- Must be set to true. The collection will not accept + # inserted or updated documents where the clustered index key value + # matches an existing value in the index. + # - *:name* -- Optional. A name that uniquely identifies the clustered index. + # @option opts [ Hash ] :collation The collation to use. + # @option opts [ Hash ] :encrypted_fields Hash describing encrypted fields + # for queryable encryption. + # @option opts [ Integer ] :expire_after Number indicating + # after how many seconds old time-series data should be deleted. + # @option opts [ Integer ] :max The maximum number of documents in a + # capped collection. The size limit takes precedents over max. + # @option opts [ Array ] :pipeline An array of pipeline stages. + # A view will be created by applying this pipeline to the view_on + # collection or view. + # @option options [ Hash ] :read_concern The read concern options hash, + # with the following optional keys: + # - *:level* -- the read preference level as a symbol; valid values + # are *:local*, *:majority*, and *:snapshot* + # @option options [ Hash ] :read The read preference options. + # The hash may have the following items: + # - *:mode* -- read preference specified as a symbol; valid values are + # *:primary*, *:primary_preferred*, *:secondary*, *:secondary_preferred* + # and *:nearest*. + # - *:tag_sets* -- an array of hashes. + # - *:local_threshold*. + # @option opts [ Session ] :session The session to use for the operation. + # @option opts [ Integer ] :size The size of the capped collection. + # @option opts [ Hash ] :time_series Create a time-series collection. + # The hash may have the following items: + # - *:timeField* -- The name of the field which contains the date in each + # time series document. + # - *:metaField* -- The name of the field which contains metadata in each + # time series document. + # - *:granularity* -- Set the granularity to the value that is the closest + # match to the time span between consecutive incoming measurements. + # Possible values are "seconds" (default), "minutes", and "hours". + # @option opts [ Hash ] :validator Hash describing document validation + # options for the collection. + # @option opts [ String ] :view_on The name of the source collection or + # view from which to create a view. + # @option opts [ Hash ] :write Deprecated. Equivalent to :write_concern # option. - # @option options [ Hash ] :write_concern The write concern options. + # @option opts [ Hash ] :write_concern The write concern options. # Can be :w => Integer|String, :fsync => Boolean, :j => Boolean. - # @option options [ Hash ] :time_series Create a time-series collection. - # See https://mongodb.com/docs/manual/core/timeseries-collections/ for more - # information about time-series collection. - # @option options [ Integer ] :expire_after Number indicating - # after how many seconds old time-series data should be deleted. - # @options clustered_index [ Hash ] :clustered_index Create a clustered index. - # This option specifies how this collection should be clustered on _id. - # See https://www.mongodb.com/docs/v5.3/reference/method/db.createCollection/#std-label-db.createCollection.clusteredIndex - # for more information about this option. # # @since 2.0.0 def initialize(database, name, options = {}) @@ -204,17 +244,37 @@ def write_concern_with_session(session) wc end - # Provides a new collection with either a new read preference or new write concern - # merged over the existing read preference / write concern. + # Provides a new collection with either a new read preference, new read + # concern or new write concern merged over the existing read preference / + # read concern / write concern. # # @example Get a collection with a changed read preference. # collection.with(read: { mode: :primary_preferred }) + + # @example Get a collection with a changed read concern. + # collection.with(read_concern: { level: :majority }) # # @example Get a collection with a changed write concern. # collection.with(write_concern: { w: 3 }) - + # # @param [ Hash ] new_options The new options to use. # + # @option new_options [ Hash ] :read The read preference options. + # The hash may have the following items: + # - *:mode* -- read preference specified as a symbol; valid values are + # *:primary*, *:primary_preferred*, *:secondary*, *:secondary_preferred* + # and *:nearest*. + # - *:tag_sets* -- an array of hashes. + # - *:local_threshold*. + # @option new_options [ Hash ] :read_concern The read concern options hash, + # with the following optional keys: + # - *:level* -- the read preference level as a symbol; valid values + # are *:local*, *:majority*, and *:snapshot* + # @option new_options [ Hash ] :write Deprecated. Equivalent to :write_concern + # option. + # @option new_options [ Hash ] :write_concern The write concern options. + # Can be :w => Integer|String, :fsync => Boolean, :j => Boolean. + # # @return [ Mongo::Collection ] A new collection instance. # # @since 2.1.0 @@ -251,17 +311,48 @@ def capped? # # @param [ Hash ] opts The options for the create operation. # - # @option opts [ Session ] :session The session to use for the operation. - # @option opts [ Hash ] :write_concern The write concern options. - # @option opts [ Hash ] :time_series Create a time-series collection. - # @option opts [ Integer ] :expire_after Number indicating - # after how many seconds old time-series data should be deleted. + # @option opts [ true | false ] :capped Create a fixed-sized collection. # @option opts [ Hash ] :change_stream_pre_and_post_images Used to enable # pre- and post-images on the created collection. + # The hash may have the following items: + # - *:enabled* -- true or false. + # @option opts [ Hash ] :clustered_index Create a clustered index. + # This option specifies how this collection should be clustered on _id. + # The hash may have the following items: + # - *:key* -- The clustered index key field. Must be set to { _id: 1 }. + # - *:unique* -- Must be set to true. The collection will not accept + # inserted or updated documents where the clustered index key value + # matches an existing value in the index. + # - *:name* -- Optional. A name that uniquely identifies the clustered index. + # @option opts [ Hash ] :collation The collation to use. # @option opts [ Hash ] :encrypted_fields Hash describing encrypted fields # for queryable encryption. + # @option opts [ Integer ] :expire_after Number indicating + # after how many seconds old time-series data should be deleted. + # @option opts [ Integer ] :max The maximum number of documents in a + # capped collection. The size limit takes precedents over max. + # @option opts [ Array ] :pipeline An array of pipeline stages. + # A view will be created by applying this pipeline to the view_on + # collection or view. + # @option opts [ Session ] :session The session to use for the operation. + # @option opts [ Integer ] :size The size of the capped collection. + # @option opts [ Hash ] :time_series Create a time-series collection. + # The hash may have the following items: + # - *:timeField* -- The name of the field which contains the date in each + # time series document. + # - *:metaField* -- The name of the field which contains metadata in each + # time series document. + # - *:granularity* -- Set the granularity to the value that is the closest + # match to the time span between consecutive incoming measurements. + # Possible values are "seconds" (default), "minutes", and "hours". # @option opts [ Hash ] :validator Hash describing document validation # options for the collection. + # @option opts [ String ] :view_on The name of the source collection or + # view from which to create a view. + # @option opts [ Hash ] :write Deprecated. Equivalent to :write_concern + # option. + # @option opts [ Hash ] :write_concern The write concern options. + # Can be :w => Integer|String, :fsync => Boolean, :j => Boolean. # # @return [ Result ] The result of the command. # diff --git a/lib/mongo/query_cache.rb b/lib/mongo/query_cache.rb index e50f12fe6c..07eea1055d 100644 --- a/lib/mongo/query_cache.rb +++ b/lib/mongo/query_cache.rb @@ -114,24 +114,24 @@ def clear_namespace(namespace) # # @param [ Mongo::CachingCursor ] cursor The CachingCursor instance to store. # - # @option opts [ String | nil ] namespace The namespace of the query, + # @option opts [ String | nil ] :namespace The namespace of the query, # in the format "database_name.collection_name". - # @option opts [ Array, Hash ] selector The selector passed to the query. + # @option opts [ Array, Hash ] :selector The selector passed to the query. # For most queries, this will be a Hash, but for aggregations, this # will be an Array representing the aggregation pipeline. May not be nil. - # @option opts [ Integer | nil ] skip The skip value of the query. - # @option opts [ Hash | nil ] sort The order of the query results + # @option opts [ Integer | nil ] :skip The skip value of the query. + # @option opts [ Hash | nil ] :sort The order of the query results # (e.g. { name: -1 }). - # @option opts [ Integer | nil ] limit The limit value of the query. - # @option opts [ Hash | nil ] projection The projection of the query + # @option opts [ Integer | nil ] :limit The limit value of the query. + # @option opts [ Hash | nil ] :projection The projection of the query # results (e.g. { name: 1 }). - # @option opts [ Hash | nil ] collation The collation of the query + # @option opts [ Hash | nil ] :collation The collation of the query # (e.g. { "locale" => "fr_CA" }). - # @option opts [ Hash | nil ] read_concern The read concern of the query + # @option opts [ Hash | nil ] :read_concern The read concern of the query # (e.g. { level: :majority }). - # @option opts [ Hash | nil ] read_preference The read preference of + # @option opts [ Hash | nil ] :read_preference The read preference of # the query (e.g. { mode: :secondary }). - # @option opts [ Boolean | nil ] multi_collection Whether the query + # @option opts [ Boolean | nil ] :multi_collection Whether the query # results could potentially come from multiple collections. When true, # these results will be stored under the nil namespace key and cleared # on every write command. @@ -152,24 +152,24 @@ def set(cursor, **opts) # For the given query options, retrieve a cached cursor that can be used # to obtain the correct query results, if one exists in the cache. # - # @option opts [ String | nil ] namespace The namespace of the query, + # @option opts [ String | nil ] :namespace The namespace of the query, # in the format "database_name.collection_name". - # @option opts [ Array, Hash ] selector The selector passed to the query. + # @option opts [ Array, Hash ] :selector The selector passed to the query. # For most queries, this will be a Hash, but for aggregations, this # will be an Array representing the aggregation pipeline. May not be nil. - # @option opts [ Integer | nil ] skip The skip value of the query. - # @option opts [ Hash | nil ] sort The order of the query results + # @option opts [ Integer | nil ] :skip The skip value of the query. + # @option opts [ Hash | nil ] :sort The order of the query results # (e.g. { name: -1 }). - # @option opts [ Integer | nil ] limit The limit value of the query. - # @option opts [ Hash | nil ] projection The projection of the query + # @option opts [ Integer | nil ] :limit The limit value of the query. + # @option opts [ Hash | nil ] :projection The projection of the query # results (e.g. { name: 1 }). - # @option opts [ Hash | nil ] collation The collation of the query + # @option opts [ Hash | nil ] :collation The collation of the query # (e.g. { "locale" => "fr_CA" }). - # @option opts [ Hash | nil ] read_concern The read concern of the query + # @option opts [ Hash | nil ] :read_concern The read concern of the query # (e.g. { level: :majority }). - # @option opts [ Hash | nil ] read_preference The read preference of + # @option opts [ Hash | nil ] :read_preference The read preference of # the query (e.g. { mode: :secondary }). - # @option opts [ Boolean | nil ] multi_collection Whether the query + # @option opts [ Boolean | nil ] :multi_collection Whether the query # results could potentially come from multiple collections. When true, # these results will be stored under the nil namespace key and cleared # on every write command. diff --git a/lib/mongo/session.rb b/lib/mongo/session.rb index 6c58c27547..8b6d31ec35 100644 --- a/lib/mongo/session.rb +++ b/lib/mongo/session.rb @@ -522,7 +522,7 @@ def with_transaction(options=nil) # # @option options [ Integer ] :max_commit_time_ms The maximum amount of # time to allow a single commitTransaction command to run, in milliseconds. - # @option options [ Hash ] read_concern The read concern options hash, + # @option options [ Hash ] :read_concern The read concern options hash, # with the following optional keys: # - *:level* -- the read preference level as a symbol; valid values # are *:local*, *:majority*, and *:snapshot* diff --git a/spec/mongo/collection_crud_spec.rb b/spec/mongo/collection_crud_spec.rb index 829e6f0452..85c4e6d960 100644 --- a/spec/mongo/collection_crud_spec.rb +++ b/spec/mongo/collection_crud_spec.rb @@ -4412,4 +4412,24 @@ def generate expect(result).to be_nil end end + + context "when creating collection with view_on and pipeline" do + before do + authorized_client["my_view"].drop + authorized_collection.insert_one({ bar: "here!" }) + authorized_client["my_view", + view_on: authorized_collection.name, + pipeline: [ { :'$project' => { "baz": "$bar" } } ] + ].create + end + + it "the view has a document" do + expect(authorized_client["my_view"].find.to_a.length).to eq(1) + end + + it "applies the pipeline" do + expect(authorized_client["my_view"].find.first).to have_key("baz") + expect(authorized_client["my_view"].find.first["baz"]).to eq("here!") + end + end end diff --git a/spec/mongo/collection_spec.rb b/spec/mongo/collection_spec.rb index a4c1b774df..184347b245 100644 --- a/spec/mongo/collection_spec.rb +++ b/spec/mongo/collection_spec.rb @@ -647,7 +647,11 @@ context 'when the collection is capped' do let(:collection) do - described_class.new(database, :specs, :capped => true, :size => 1024) + described_class.new(database, :specs, :capped => true, :size => 4096, :max => 512) + end + + let(:collstats) do + database.read_command(:collstats => :specs).documents.first end before do @@ -658,6 +662,12 @@ it 'returns true' do expect(collection).to be_capped end + + it "applies the options" do + expect(collstats["capped"]).to be true + expect(collstats["max"]).to eq(512) + expect(collstats["maxSize"]).to eq(4096) + end end context 'when the collection is not capped' do From c5341dc45c176c01899e928102ccfe1968071073 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> Date: Fri, 12 Aug 2022 16:09:01 -0400 Subject: [PATCH 017/198] Clarify collection method documentation w.r.t. effective options (#2593) Co-authored-by: Oleg Pudeyev --- lib/mongo/collection.rb | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 66c9d620d7..41d43ca4c5 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -173,7 +173,11 @@ def initialize(database, name, options = {}) @options.freeze end - # Get the read concern for this collection instance. + # Get the effective read concern for this collection instance. + # + # If a read concern was provided in collection options, that read concern + # will be returned, otherwise the database's effective read concern will + # be returned. # # @example Get the read concern. # collection.read_concern @@ -185,7 +189,7 @@ def read_concern options[:read_concern] || database.read_concern end - # Get the server selector on this collection. + # Get the server selector for this collection. # # @example Get the server selector. # collection.server_selector @@ -197,7 +201,11 @@ def server_selector @server_selector ||= ServerSelector.get(read_preference || database.server_selector) end - # Get the read preference on this collection. + # Get the effective read preference for this collection. + # + # If a read preference was provided in collection options, that read + # preference will be returned, otherwise the database's effective read + # preference will be returned. # # @example Get the read preference. # collection.read_preference @@ -209,7 +217,11 @@ def read_preference @read_preference ||= options[:read] || database.read_preference end - # Get the write concern on this collection. + # Get the effective write concern on this collection. + # + # If a write concern was provided in collection options, that write + # concern will be returned, otherwise the database's effective write + # concern will be returned. # # @example Get the write concern. # collection.write_concern @@ -222,7 +234,8 @@ def write_concern options[:write_concern] || options[:write] || database.write_concern) end - # Get the write concern for the collection, given the session. + # Get the write concern to use for an operation on this collection, + # given a session. # # If the session is in a transaction and the collection # has an unacknowledged write concern, remove the write From 1013de8e0df7c3018e4ab9f2c9219f1aba8cd187 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> Date: Fri, 12 Aug 2022 16:09:16 -0400 Subject: [PATCH 018/198] Repair links to Collection::View::Aggregation in api docs (#2591) Co-authored-by: Oleg Pudeyev --- lib/mongo/collection.rb | 2 +- lib/mongo/database.rb | 2 +- lib/mongo/database/view.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 41d43ca4c5..0a7482d596 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -528,7 +528,7 @@ def find(filter = nil, options = {}) # cursor and this option is therefore not valid. # @option options [ Session ] :session The session to use. # - # @return [ Aggregation ] The aggregation object. + # @return [ View::Aggregation ] The aggregation object. # # @since 2.1.0 def aggregate(pipeline, options = {}) diff --git a/lib/mongo/database.rb b/lib/mongo/database.rb index 6ab9d2840f..0bdd2148fc 100644 --- a/lib/mongo/database.rb +++ b/lib/mongo/database.rb @@ -397,7 +397,7 @@ def users # cursor and this option is therefore not valid. # @option options [ Session ] :session The session to use. # - # @return [ Aggregation ] The aggregation object. + # @return [ Collection::View::Aggregation ] The aggregation object. # # @since 2.10.0 def aggregate(pipeline, options = {}) diff --git a/lib/mongo/database/view.rb b/lib/mongo/database/view.rb index 636974f56d..66f69d0568 100644 --- a/lib/mongo/database/view.rb +++ b/lib/mongo/database/view.rb @@ -141,7 +141,7 @@ def initialize(database) # @param [ Array ] pipeline The aggregation pipeline. # @param [ Hash ] options The aggregation options. # - # @return [ Aggregation ] The aggregation object. + # @return [ Collection::View::Aggregation ] The aggregation object. # # @since 2.10.0 # @api private From 6c72185a8e642df28cda5a409d5f0956574bb288 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 15 Aug 2022 09:28:04 -0400 Subject: [PATCH 019/198] RUBY-1613 Verify handling of write concern errors by user management commands function correctly (#2595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RUBY-1613 Verify handling of write concern errors by user management commands function correctly * Update spec/mongo/auth/user/view_spec.rb * RUBY-1613 ådd min server version * RUBY-1613 attempt to fix tests --- spec/mongo/auth/user/view_spec.rb | 95 +++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/spec/mongo/auth/user/view_spec.rb b/spec/mongo/auth/user/view_spec.rb index 8a43e20c45..4ffb2cee89 100644 --- a/spec/mongo/auth/user/view_spec.rb +++ b/spec/mongo/auth/user/view_spec.rb @@ -486,4 +486,99 @@ end end end + + context "when the result is a write concern error" do + require_topology :replica_set + min_server_version '4.0' + + let(:user) do + Mongo::Auth::User.new({ + user: 'user', + roles: [ Mongo::Auth::Roles::READ_WRITE ], + password: 'password' + }) + end + + before do + authorized_client.use('admin').database.command( + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: [ failCommand ], + writeConcernError: { + code: 64, + codeName: "WriteConcernFailed", + errmsg: "waiting for replication timed out", + errInfo: { wtimeout: true } + } + } + ) + end + + shared_examples "raises the correct write concern error" do + + it "raises a write concern error" do + expect do + view.send(method, input) + end.to raise_error(Mongo::Error::OperationFailure, /[64:WriteConcernFailed]/) + end + + it "raises and reports the write concern error correctly" do + begin + view.send(method, input) + rescue Mongo::Error::OperationFailure => e + expect(e.write_concern_error?).to be true + expect(e.write_concern_error_document).to eq( + "code" => 64, + "codeName" => "WriteConcernFailed", + "errmsg" => "waiting for replication timed out", + "errInfo" => { "wtimeout" => true } + ) + end + end + end + + context "when creating a user" do + + let(:failCommand) { "createUser" } + let(:method) { :create } + let(:input) { user } + + after do + view.remove(user.name) + end + + include_examples "raises the correct write concern error" + end + + context "when updating a user" do + + let(:failCommand) { "updateUser" } + let(:method) { :update } + let(:input) { user.name } + + before do + view.create(user) + end + + after do + view.remove(user.name) + end + + include_examples "raises the correct write concern error" + end + + context "when removing a user" do + + let(:failCommand) { "dropUser" } + let(:method) { :remove } + let(:input) { user.name } + + before do + view.create(user) + end + + include_examples "raises the correct write concern error" + end + end end From 47aeedd61d39c59dc79ae843a42abeddbb3bbad9 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 18 Aug 2022 16:57:04 -0400 Subject: [PATCH 020/198] RUBY-1554 Mention to_json in logging examples in tutorial (#2589) * RUBY-1554 Mention to_json in logging examples in tutorial * Apply suggestions from code review * RUBY-1554 update comment * RUBY-1554 fix spacing --- docs/reference/monitoring.txt | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/reference/monitoring.txt b/docs/reference/monitoring.txt index 1721feb6a6..840a32a213 100644 --- a/docs/reference/monitoring.txt +++ b/docs/reference/monitoring.txt @@ -41,7 +41,10 @@ logging subscriber used internally by the driver: include Mongo::Loggable def started(event) - log_debug("#{prefix(event)} | STARTED | #{format_command(event.command)}") + # The default inspection of a command which is a BSON document gets + # truncated in the middle. To get the full rendering of the command, the + # ``to_json`` method can be called on the document. + log_debug("#{prefix(event)} | STARTED | #{format_command(event.command.to_json)}") end def succeeded(event) @@ -137,35 +140,35 @@ can look like the following: format("SDAM | %s".freeze, message) end end - + class TopologyOpeningLogSubscriber < SDAMLogSubscriber private - + def format_event(event) "Topology type '#{event.topology.display_name}' initializing." end end - + class ServerOpeningLogSubscriber < SDAMLogSubscriber private - + def format_event(event) "Server #{event.address} initializing." end end - + class ServerDescriptionChangedLogSubscriber < SDAMLogSubscriber private - + def format_event(event) "Server description for #{event.address} changed from " + "'#{event.previous_description.server_type}' to '#{event.new_description.server_type}'." end end - + class TopologyChangedLogSubscriber < SDAMLogSubscriber private - + def format_event(event) if event.previous_topology != event.new_topology "Topology type '#{event.previous_topology.display_name}' changed to " + @@ -176,18 +179,18 @@ can look like the following: end end end - + class ServerClosedLogSubscriber < SDAMLogSubscriber private - + def format_event(event) "Server #{event.address} connection closed." end end - + class TopologyClosedLogSubscriber < SDAMLogSubscriber private - + def format_event(event) "Topology type '#{event.topology.display_name}' closed." end @@ -228,7 +231,7 @@ since the events may be published during the client's construction: topology_changed_subscriber = TopologyChangedLogSubscriber.new server_closed_subscriber = ServerClosedLogSubscriber.new topology_closed_subscriber = TopologyClosedLogSubscriber.new - + sdam_proc = Proc.new do |client| client.subscribe(Mongo::Monitoring::TOPOLOGY_OPENING, topology_opening_subscriber) @@ -316,9 +319,9 @@ The following is an example logging heartbeat event subscriber: def failed(event) log_debug("#{event.address} | FAILED | #{event.error.class}: #{event.error.message} | #{event.duration}s") end - + private - + def logger Mongo::Logger.logger end From b70ca2df7763c7e28990c63f7907c0f9bf409cbd Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 19 Aug 2022 15:09:48 -0400 Subject: [PATCH 021/198] RUBY-1775 Make driver Ruby warning-clean (#2597) * RUBY-1775 first batch of changes * RUBY-1775 last change * Update lib/mongo/auth/aws/request.rb --- lib/mongo/auth/aws/conversation.rb | 4 ---- lib/mongo/auth/aws/request.rb | 1 - lib/mongo/cluster/sdam_flow.rb | 1 - lib/mongo/collection/view/change_stream.rb | 2 +- lib/mongo/collection/view/readable.rb | 21 ++++++++++----------- lib/mongo/database/view.rb | 2 +- lib/mongo/server/connection_pool.rb | 2 +- lib/mongo/server/description.rb | 7 ++----- lib/mongo/srv/monitor.rb | 2 -- lib/mongo/timeout.rb | 2 +- lib/mongo/uri.rb | 2 +- 11 files changed, 17 insertions(+), 29 deletions(-) diff --git a/lib/mongo/auth/aws/conversation.rb b/lib/mongo/auth/aws/conversation.rb index 8fce263ddb..bebf55cf5a 100644 --- a/lib/mongo/auth/aws/conversation.rb +++ b/lib/mongo/auth/aws/conversation.rb @@ -65,10 +65,6 @@ def continue(reply_document, connection) # @return [ Integer ] The conversation id. attr_reader :conversation_id - def client_first_payload - {payload: client_first_message} - end - def client_first_data { r: BSON::Binary.new(client_nonce), diff --git a/lib/mongo/auth/aws/request.rb b/lib/mongo/auth/aws/request.rb index 47d8fc3113..072ac9c07d 100644 --- a/lib/mongo/auth/aws/request.rb +++ b/lib/mongo/auth/aws/request.rb @@ -215,7 +215,6 @@ def canonical_request # @return [ String ] The signature. def signature hashed_canonical_request = Digest::SHA256.hexdigest(canonical_request) - hashed_body = Digest::SHA256.new.update(STS_REQUEST_BODY).hexdigest string_to_sign = "AWS4-HMAC-SHA256\n" + "#{formatted_time}\n" + "#{scope}\n" + diff --git a/lib/mongo/cluster/sdam_flow.rb b/lib/mongo/cluster/sdam_flow.rb index 62fcd9d47e..37cfd73606 100644 --- a/lib/mongo/cluster/sdam_flow.rb +++ b/lib/mongo/cluster/sdam_flow.rb @@ -365,7 +365,6 @@ def update_rs_without_primary # This is the set of servers on which monitoring should be started. def add_servers_from_desc(updated_desc) added_servers = [] - address_strs = servers_list.map(&:address).map(&:to_s) %w(hosts passives arbiters).each do |m| updated_desc.send(m).each do |address_str| if server = cluster.add(address_str, monitor: false) diff --git a/lib/mongo/collection/view/change_stream.rb b/lib/mongo/collection/view/change_stream.rb index 8aca8a99db..7ea1e31a08 100644 --- a/lib/mongo/collection/view/change_stream.rb +++ b/lib/mongo/collection/view/change_stream.rb @@ -164,7 +164,7 @@ def each document = try_next yield document if document end - rescue StopIteration => e + rescue StopIteration return self end diff --git a/lib/mongo/collection/view/readable.rb b/lib/mongo/collection/view/readable.rb index 388e448f45..f6022ecc54 100644 --- a/lib/mongo/collection/view/readable.rb +++ b/lib/mongo/collection/view/readable.rb @@ -272,19 +272,18 @@ def estimated_document_count(opts = {}) cmd = { count: collection.name } cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms] if read_concern - cmd[:readConcern] = Options::Mapper.transform_values_to_strings( - read_concern) - end - result = Operation::Count.new( - selector: cmd, - db_name: database.name, - read: read_pref, - session: session, - comment: opts[:comment], - ).execute(server, context: context) - result.n.to_i + cmd[:readConcern] = Options::Mapper.transform_values_to_strings(read_concern) end + result = Operation::Count.new( + selector: cmd, + db_name: database.name, + read: read_pref, + session: session, + comment: opts[:comment], + ).execute(server, context: context) + result.n.to_i end + end rescue Error::OperationFailure => exc if exc.code == 26 # NamespaceNotFound diff --git a/lib/mongo/database/view.rb b/lib/mongo/database/view.rb index 66f69d0568..2bcab546b5 100644 --- a/lib/mongo/database/view.rb +++ b/lib/mongo/database/view.rb @@ -172,7 +172,7 @@ def collections_info(session, server_selector, options = {}, &block) doc['name'].start_with?('system.') || doc['name'].include?('$') end else - docs = cursor.reject do |doc| + cursor.reject do |doc| doc['name'].start_with?("#{database.name}.system") || doc['name'].include?('$') end end diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index a0e303cb4b..7db3406269 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -805,7 +805,7 @@ def create_connection unless @server.load_balancer? opts[:generation] = generation end - connection = Connection.new(@server, opts) + Connection.new(@server, opts) end # Create a connection, connect it, and add it to the pool. diff --git a/lib/mongo/server/description.rb b/lib/mongo/server/description.rb index b9c59139d5..aa66799a47 100644 --- a/lib/mongo/server/description.rb +++ b/lib/mongo/server/description.rb @@ -299,11 +299,6 @@ def features @features end - # @return [ nil | Object ] The service id, if any. - def service_id - config['serviceId'] - end - # @return [ Float ] The moving average time the hello call took to complete. attr_reader :average_round_trip_time @@ -844,6 +839,8 @@ def server_connection_id config['connectionId'] end + # @return [ nil | Object ] The service id, if any. + # # @api experimental def service_id config['serviceId'] diff --git a/lib/mongo/srv/monitor.rb b/lib/mongo/srv/monitor.rb index 56f06f0dba..69a9a98942 100644 --- a/lib/mongo/srv/monitor.rb +++ b/lib/mongo/srv/monitor.rb @@ -70,8 +70,6 @@ def do_work end def scan! - old_hosts = last_result.address_strs - begin last_result = Timeout.timeout(timeout) do @resolver.get_records(@srv_uri.query_hostname) diff --git a/lib/mongo/timeout.rb b/lib/mongo/timeout.rb index 0d91e7cd6b..6871dbadb7 100644 --- a/lib/mongo/timeout.rb +++ b/lib/mongo/timeout.rb @@ -36,7 +36,7 @@ def timeout(sec, klass=nil, message=nil) ::Timeout.timeout(sec) do yield end - rescue ::Timeout::Error => e + rescue ::Timeout::Error raise klass, message end else diff --git a/lib/mongo/uri.rb b/lib/mongo/uri.rb index 0a08022958..f0def070ba 100644 --- a/lib/mongo/uri.rb +++ b/lib/mongo/uri.rb @@ -235,7 +235,7 @@ def self.get(string, opts = {}) raise Error::InvalidURI.new(string, 'Cannot parse an empty URI.') end - scheme, _, remaining = string.partition(SCHEME_DELIM) + scheme, _, _ = string.partition(SCHEME_DELIM) case scheme when MONGODB_SCHEME URI.new(string, opts) From c35c26615706b5d88ed147e755dc6f839a4416b8 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 26 Aug 2022 14:35:52 -0400 Subject: [PATCH 022/198] RUBY-3095 fix options mapper to apply_transform (#2599) * fix options_mapper * remove hard code strat * RUBY-3095 fix bool and integer * alter docstrings * RUBY-3095 more docstrings --- lib/mongo/uri/options_mapper.rb | 41 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/mongo/uri/options_mapper.rb b/lib/mongo/uri/options_mapper.rb index 0af5c74db5..c6f2e2900b 100644 --- a/lib/mongo/uri/options_mapper.rb +++ b/lib/mongo/uri/options_mapper.rb @@ -106,7 +106,7 @@ def smc_to_ruby(opts) value = value.to_sym end - #value = apply_transform(key, value, strategy[:type]) + value = apply_transform(key, value, strategy[:type]) # Sometimes the value here would be nil, for example if we are processing # read preference tags or auth mechanism properties and all of the # data within is invalid. Ignore such options. @@ -290,14 +290,14 @@ def self.uri_option(uri_key, name, **extra) # Returns true for 'true', false for 'false', otherwise nil. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] URI option value. + # @param value [ String | true | false ] URI option value. # # @return [ true | false | nil ] Converted value. def convert_bool(name, value) case value - when "true", 'TRUE' + when true, "true", 'TRUE' true - when "false", 'FALSE' + when false, "false", 'FALSE' false else log_warn("invalid boolean option for #{name}: #{value}") @@ -327,7 +327,7 @@ def revert_repeated_bool(value) # Parses a boolean value and returns its inverse. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] The URI option value. + # @param value [ String | true | false ] The URI option value. # # @return [ true | false | nil ] The inverse of the boolean value parsed out, otherwise nil # (and a warning will be logged). @@ -350,11 +350,11 @@ def revert_inverse_bool(value) # If the value is not a valid integer, warns and returns nil. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] URI option value. + # @param value [ String | Integer ] URI option value. # # @return [ nil | Integer ] Converted value. def convert_integer(name, value) - unless /\A\d+\z/ =~ value + if value.is_a?(String) && /\A\d+\z/ !~ value log_warn("#{value} is not a valid integer for #{name}") return nil end @@ -371,20 +371,29 @@ def revert_integer(value) # options are always in MS so we provide an easy conversion type. # # @param [ String ] name Name of the URI option being processed. - # @param [ Integer ] value The millisecond value. + # @param [ String | Integer ] value The millisecond value. # # @return [ Float ] The seconds value. # # @since 2.0.0 def convert_ms(name, value) - unless /\A-?\d+(\.\d+)?\z/ =~ value - log_warn("Invalid ms value for #{name}: #{value}") - return nil - end - - if value[0] == '-' - log_warn("#{name} cannot be a negative number") - return nil + case value + when String + if /\A-?\d+(\.\d+)?\z/ !~ value + log_warn("Invalid ms value for #{name}: #{value}") + return nil + end + if value.to_s[0] == '-' + log_warn("#{name} cannot be a negative number") + return nil + end + when Integer + if value < 0 + log_warn("#{name} cannot be a negative number") + return nil + end + else + raise ArgumentError, "Can only convert Strings or Integers to ms. Given: #{value.class}" end value.to_f / 1000 From dec1a329a01922226996112de20c1d60cd44efe9 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> Date: Fri, 26 Aug 2022 17:34:05 -0400 Subject: [PATCH 023/198] RUBY-2673 Verify connection pools are created for all server types in direct connection mode (#2592) Co-authored-by: Oleg Pudeyev --- spec/integration/client_construction_spec.rb | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/integration/client_construction_spec.rb b/spec/integration/client_construction_spec.rb index d7034d3c73..d6e98b5d2d 100644 --- a/spec/integration/client_construction_spec.rb +++ b/spec/integration/client_construction_spec.rb @@ -100,6 +100,27 @@ expect(server.description).to be_primary end end + + # This test requires a PSA deployment. The port number is fixed for our + # Evergreen/Docker setups. + context 'when directly connecting to arbiters' do + let(:options) do + SpecConfig.instance.test_options.tap do |opt| + opt.delete(:connect) + opt.update(direct_connection: true) + end + end + + let(:client) do + new_local_client(['localhost:27019'], options) + end + + let(:response) { client.command(ismaster: 1).documents.first } + + it 'connects' do + response.fetch('arbiterOnly').should be true + end + end end context 'in sharded topology' do From 6bf442e7bb18766ff812e7ace484007682638b4a Mon Sep 17 00:00:00 2001 From: Sarun Rattanasiri <7634596+midnight-wonderer@users.noreply.github.com> Date: Sun, 28 Aug 2022 23:56:05 +0700 Subject: [PATCH 024/198] RUBY-3087 reinstate ability to call insert_many with Enumerable inputs which are not Array instances (#2594) * RUBY-3087 add a test for insert_many with enumerable inputs * RUBY-3087 raise exception on bulk_write without using an Array method * RUBY-3087 add test, docs * add docs, release notes * Fix the description of empty bulk_write spec to match the behavior * Update docs/release-notes.txt * change docstrings to use enumerables Co-authored-by: Neil Shweky --- lib/mongo/bulk_write.rb | 10 +++++--- lib/mongo/collection.rb | 4 +-- lib/mongo/collection/view/writable.rb | 2 -- spec/mongo/collection_crud_spec.rb | 36 +++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/lib/mongo/bulk_write.rb b/lib/mongo/bulk_write.rb index 6aae3aa520..ad05ef8f5f 100644 --- a/lib/mongo/bulk_write.rb +++ b/lib/mongo/bulk_write.rb @@ -115,7 +115,8 @@ def execute # ) # # @param [ Mongo::Collection ] collection The collection. - # @param [ Array ] requests The requests, cannot be empty. + # @param [ Enumerable ] requests The requests, + # cannot be empty. # @param [ Hash, BSON::Document ] options The options. # # @since 2.1.0 @@ -330,10 +331,9 @@ def can_hint?(connection) # ArgumentError ] # if the document is invalid. def validate_requests! - if @requests.empty? - raise ArgumentError, "Bulk write requests cannot be empty" - end + requests_empty = true @requests.each do |req| + requests_empty = false if op = req.keys.first if [:update_one, :update_many].include?(op) if doc = maybe_first(req.dig(op, :update)) @@ -359,6 +359,8 @@ def validate_requests! end end end + end.tap do + raise ArgumentError, "Bulk write requests cannot be empty" if requests_empty end end diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 0a7482d596..b763824d3f 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -796,7 +796,7 @@ def insert_one(document, opts = {}) # @example Insert documents into the collection. # collection.insert_many([{ name: 'test' }]) # - # @param [ Array ] documents The documents to insert. + # @param [ Enumerable ] documents The documents to insert. # @param [ Hash ] options The insert options. # # @option options [ true | false ] :bypass_document_validation Whether or @@ -824,7 +824,7 @@ def insert_many(documents, options = {}) # @example Execute a bulk write. # collection.bulk_write(operations, options) # - # @param [ Array ] requests The bulk write requests. + # @param [ Enumerable ] requests The bulk write requests. # @param [ Hash ] options The options. # # @option options [ true | false ] :ordered Whether the operations diff --git a/lib/mongo/collection/view/writable.rb b/lib/mongo/collection/view/writable.rb index 22cf2d32f8..34b35d0478 100644 --- a/lib/mongo/collection/view/writable.rb +++ b/lib/mongo/collection/view/writable.rb @@ -98,8 +98,6 @@ def find_one_and_delete(opts = {}) end.first&.fetch('value', nil) end - # db['users'].bulk_write([{insert_one: {x: 1}}, {insert_one: {x: 2}}]) - # Finds a single document and replaces it. # # @example Find a document and replace it, returning the original. diff --git a/spec/mongo/collection_crud_spec.rb b/spec/mongo/collection_crud_spec.rb index 85c4e6d960..411b6bd4a0 100644 --- a/spec/mongo/collection_crud_spec.rb +++ b/spec/mongo/collection_crud_spec.rb @@ -362,6 +362,42 @@ expect(result.inserted_ids.size).to eq(2) end + context 'when an enumerable is used instead of an array' do + + context 'when the enumerable is not empty' do + + let(:source_data) do + [{ name: 'test1' }, { name: 'test2' }] + end + + let(:result) do + authorized_collection.insert_many(source_data.lazy) + end + + it 'should accepts them without raising an error' do + expect { result }.to_not raise_error + expect(result.inserted_count).to eq(source_data.size) + end + end + + context 'when the enumerable is empty' do + + let(:source_data) do + [] + end + + let(:result) do + authorized_collection.insert_many(source_data.lazy) + end + + it 'should raise ArgumentError' do + expect do + result + end.to raise_error(ArgumentError, /Bulk write requests cannot be empty/) + end + end + end + context 'when a session is provided' do let(:session) do From 2d1352c9cb6a48771df915f15c24c88c857e0e59 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Sun, 28 Aug 2022 15:09:08 -0400 Subject: [PATCH 025/198] RUBY-3033 update sdam specs (#2598) * RUBY-3033 auth-error tests work * RUBY-3033 more tests passing * RUBY-3033 more tests done * RUBY-3033 connect timeout * RUBY-3033 more tests * RUBY-3033 add threading methods * RUBY-3033 another test * RUBY-3033 rediscover test done + topology methods * fix options_mapper * RUBY-3033 fixed hello-command-error * RUBY-3033 fix hello network error * RUBY-3033 hello tests done * RUBY-3033 delete integration tests * RUBY-3033 use timeout_ms * Update spec/runners/unified/thread_operations.rb * RUBY-3033 scan on waiting for primary and use monotonic time * RUBY-3033 fix test * RUBY-3033 use select instead of filter * RUBY-3033 add back observe sensitive * permit load balancers to be marked unknown without service ids * grammar Co-authored-by: Oleg Pudeyev --- lib/mongo/server.rb | 16 +- spec/mongo/server/connection_spec.rb | 2 +- spec/runners/unified.rb | 4 +- spec/runners/unified/assertions.rb | 6 +- spec/runners/unified/ddl_operations.rb | 54 +++ spec/runners/unified/support_operations.rb | 37 ++ spec/runners/unified/test.rb | 55 ++- spec/runners/unified/thread_operations.rb | 73 ++++ .../sdam_integration/cancel-server-check.yml | 96 ------ .../sdam_integration/connectTimeoutMS.yml | 88 ----- .../sdam_integration/find-network-error.yml | 85 ----- .../sdam_integration/find-shutdown-error.yml | 118 ------- .../sdam_integration/hello-command-error.yml | 152 --------- .../sdam_integration/hello-network-error.yml | 148 -------- .../data/sdam_integration/hello-timeout.yml | 219 ------------ .../sdam_integration/insert-network-error.yml | 88 ----- .../insert-shutdown-error.yml | 117 ------- .../rediscover-quickly-after-step-down.yml | 98 ------ .../data/sdam_unified/auth-error.yml | 130 +++++++ .../sdam_unified/auth-misc-command-error.yml | 132 ++++++++ .../data/sdam_unified/auth-network-error.yml | 132 ++++++++ .../data/sdam_unified/auth-shutdown-error.yml | 133 ++++++++ .../data/sdam_unified/cancel-server-check.yml | 143 ++++++++ .../data/sdam_unified/connectTimeoutMS.yml | 130 +++++++ .../data/sdam_unified/find-network-error.yml | 135 ++++++++ .../data/sdam_unified/find-shutdown-error.yml | 163 +++++++++ .../data/sdam_unified/hello-command-error.yml | 233 +++++++++++++ .../data/sdam_unified/hello-network-error.yml | 228 +++++++++++++ .../data/sdam_unified/hello-timeout.yml | 318 ++++++++++++++++++ .../sdam_unified/insert-network-error.yml | 137 ++++++++ .../sdam_unified/insert-shutdown-error.yml | 162 +++++++++ .../data/sdam_unified/pool-cleared-error.yml | 239 +++++++++++++ .../rediscover-quickly-after-step-down.yml | 144 ++++++++ spec/spec_tests/sdam_integration_spec.rb | 16 - spec/spec_tests/sdam_unified_spec.rb | 13 + 35 files changed, 2800 insertions(+), 1244 deletions(-) create mode 100644 spec/runners/unified/thread_operations.rb delete mode 100644 spec/spec_tests/data/sdam_integration/cancel-server-check.yml delete mode 100644 spec/spec_tests/data/sdam_integration/connectTimeoutMS.yml delete mode 100644 spec/spec_tests/data/sdam_integration/find-network-error.yml delete mode 100644 spec/spec_tests/data/sdam_integration/find-shutdown-error.yml delete mode 100644 spec/spec_tests/data/sdam_integration/hello-command-error.yml delete mode 100644 spec/spec_tests/data/sdam_integration/hello-network-error.yml delete mode 100644 spec/spec_tests/data/sdam_integration/hello-timeout.yml delete mode 100644 spec/spec_tests/data/sdam_integration/insert-network-error.yml delete mode 100644 spec/spec_tests/data/sdam_integration/insert-shutdown-error.yml delete mode 100644 spec/spec_tests/data/sdam_integration/rediscover-quickly-after-step-down.yml create mode 100644 spec/spec_tests/data/sdam_unified/auth-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/auth-misc-command-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/auth-network-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/auth-shutdown-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/cancel-server-check.yml create mode 100644 spec/spec_tests/data/sdam_unified/connectTimeoutMS.yml create mode 100644 spec/spec_tests/data/sdam_unified/find-network-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/find-shutdown-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/hello-command-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/hello-network-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/hello-timeout.yml create mode 100644 spec/spec_tests/data/sdam_unified/insert-network-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/insert-shutdown-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/pool-cleared-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/rediscover-quickly-after-step-down.yml delete mode 100644 spec/spec_tests/sdam_integration_spec.rb create mode 100644 spec/spec_tests/sdam_unified_spec.rb diff --git a/lib/mongo/server.rb b/lib/mongo/server.rb index 54edba142a..9bc00daba9 100644 --- a/lib/mongo/server.rb +++ b/lib/mongo/server.rb @@ -480,18 +480,15 @@ def handle_auth_failure! rescue Mongo::Error::SocketTimeoutError # possibly cluster is slow, do not give up on it raise - rescue Mongo::Error::SocketError => e - # non-timeout network error + rescue Mongo::Error::SocketError, Auth::Unauthorized => e + # non-timeout network error or auth error, clear the pool and mark the + # topology as unknown unknown!( generation: e.generation, service_id: e.service_id, stop_push_monitor: true, ) raise - rescue Auth::Unauthorized - # auth error, keep server description and topology as they are - pool.disconnect! - raise end # Whether the server supports modern read retries. @@ -555,10 +552,13 @@ def unknown!(options = {}) # However, this method also clears connection pool for the server # when the latter is marked unknown, and this part needs to happen # when the server is a load balancer. + # + # It is possible for a load balancer server to not have a service id, + # for example if there haven't been any successful connections yet to + # this server, but the server can still be marked unknown if one + # of such connections failed midway through its establishment. if service_id = options[:service_id] pool.disconnect!(service_id: service_id) - elsif Lint.enabled? - raise Error::LintError, 'Load balancer was asked to be marked unknown without a service id' end return end diff --git a/spec/mongo/server/connection_spec.rb b/spec/mongo/server/connection_spec.rb index 7c22e8ae12..d03132bb9e 100644 --- a/spec/mongo/server/connection_spec.rb +++ b/spec/mongo/server/connection_spec.rb @@ -410,7 +410,7 @@ class ConnectionSpecTestException < Exception; end end #it_behaves_like 'disconnects connection pool' - it_behaves_like 'keeps server type and topology' + it_behaves_like 'marks server unknown' end # need a separate context here, otherwise disconnect expectation diff --git a/spec/runners/unified.rb b/spec/runners/unified.rb index d860f2a22d..66161e01f9 100644 --- a/spec/runners/unified.rb +++ b/spec/runners/unified.rb @@ -64,7 +64,7 @@ def define_unified_spec_tests(base_path, paths, expect_failure: false) end end begin - test.create_entities + test.create_spec_entities test.set_initial_data begin test.run @@ -94,7 +94,7 @@ def define_unified_spec_tests(base_path, paths, expect_failure: false) skip "Requirements not satisfied" end end - test.create_entities + test.create_spec_entities test.set_initial_data test.run test.assert_outcome diff --git a/spec/runners/unified/assertions.rb b/spec/runners/unified/assertions.rb index b1050b6cb6..7e8b1a16ce 100644 --- a/spec/runners/unified/assertions.rb +++ b/spec/runners/unified/assertions.rb @@ -118,7 +118,7 @@ def assert_outcome spec = UsingHash[spec] collection = client.use(spec.use!('databaseName'))[spec.use!('collectionName')] expected_docs = spec.use!('documents') - actual_docs = collection.find({}, order: :_id).to_a + actual_docs = collection.find({}, sort: { _id: 1 }).to_a assert_documents_match(actual_docs, expected_docs) unless spec.empty? raise NotImplementedError, "Unhandled keys: #{spec}" @@ -241,6 +241,10 @@ def assert_matches(actual, expected, msg) actual_v = actual.inserted_id expected_v = expected.values.first.values.first assert_value_matches(actual_v, expected_v, 'inserted_id') + elsif expected.keys == %w(insertedId) + actual_v = actual.inserted_id + expected_v = expected.values.first + assert_value_matches(actual_v, expected_v, 'inserted_id') else if expected.empty? # This needs to be a match assertion. Check type only diff --git a/spec/runners/unified/ddl_operations.rb b/spec/runners/unified/ddl_operations.rb index a8fcfa795c..affa4410f7 100644 --- a/spec/runners/unified/ddl_operations.rb +++ b/spec/runners/unified/ddl_operations.rb @@ -175,5 +175,59 @@ def assert_index_not_exists(op) end end end + + def create_entities(op) + consume_test_runner(op) + use_arguments(op) do |args| + generate_entities(args.use!('entities')) + end + end + + def record_topology_description(op) + consume_test_runner(op) + use_arguments(op) do |args| + client = entities.get(:client, args.use!('client')) + entities.set(:topology, args.use!('id'), client.cluster.topology) + end + end + + def assert_topology_type(op) + consume_test_runner(op) + use_arguments(op) do |args| + topology = entities.get(:topology, args.use!('topologyDescription')) + type = args.use!('topologyType') + unless topology.display_name == type + raise Error::ResultMismatch, "Expected topology type to be #{type}, but got #{topology.class}" + end + end + end + + def retrieve_primary(topology) + topology.server_descriptions.detect { |k, desc| desc.primary? }&.first + end + + def wait_for_primary_change(op) + consume_test_runner(op) + use_arguments(op) do |args| + client = entities.get(:client, args.use!('client')) + topology = entities.get(:topology, args.use!('priorTopologyDescription')) + timeout_ms = args.use('timeoutMS') || 10000 + old_primary = retrieve_primary(topology) + + deadline = Mongo::Utils.monotonic_time + timeout_ms / 1000.0 + loop do + client.cluster.scan! + new_primary = client.cluster.next_primary.address + if new_primary && old_primary != new_primary + break + end + if Mongo::Utils.monotonic_time >= deadline + raise "Did not receive a change in primary from #{old_primary} in 10 seconds" + else + sleep 0.1 + end + end + end + end end end diff --git a/spec/runners/unified/support_operations.rb b/spec/runners/unified/support_operations.rb index 0f2d3fdafc..7446654f72 100644 --- a/spec/runners/unified/support_operations.rb +++ b/spec/runners/unified/support_operations.rb @@ -17,6 +17,9 @@ def run_command(op) if session = args.use('session') opts[:session] = entities.get(:session, session) end + if read_preference = args.use('readPreference') + opts[:read] = ::Utils.snakeize_hash(read_preference) + end database.command(cmd, **opts) end @@ -247,6 +250,40 @@ def _loop(op) end end + def assert_event_count(op) + consume_test_runner(op) + use_arguments(op) do |args| + client = entities.get(:client, args.use!('client')) + subscriber = @subscribers.fetch(client) + event = args.use!('event') + assert_eq(event.keys.length, 1, "Expected event must have one key: #{event}") + count = args.use!('count') + + events = select_events(subscriber, event) + assert_eq(events.length, count, "Expected event #{event} to occur #{count} times but received it #{events.length} times.") + end + end + + def select_events(subscriber, event) + expected_name, opts = event.first + expected_name = expected_name.sub(/Event$/, '').sub(/^(.)/) { $1.upcase } + subscriber.wanted_events.select do |wevent| + if wevent.class.name.sub(/.*::/, '') == expected_name + spec = UsingHash[opts] + result = true + if new_desc = spec.use('newDescription') + if type = new_desc.use('type') + result &&= wevent.new_description.server_type == type.downcase.to_sym + end + end + unless spec.empty? + raise NotImplementedError, "Unhandled keys: #{spec}" + end + result + end + end + end + private def assert_no_arguments(op) diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index 3d3c55015c..67132457d1 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -8,6 +8,7 @@ require 'runners/unified/ddl_operations' require 'runners/unified/change_stream_operations' require 'runners/unified/support_operations' +require 'runners/unified/thread_operations' require 'runners/unified/assertions' require 'support/utils' require 'support/crypt' @@ -21,6 +22,7 @@ class Test include DdlOperations include ChangeStreamOperations include SupportOperations + include ThreadOperations include Assertions include RSpec::Core::Pending @@ -74,9 +76,13 @@ def require_single_mongos? attr_reader :entities - def create_entities + def create_spec_entities return if @entities_created - @spec['createEntities'].each do |entity_spec| + generate_entities(@spec['createEntities']) + end + + def generate_entities(es) + es.each do |entity_spec| unless entity_spec.keys.length == 1 raise NotImplementedError, "Entity must have exactly one key" end @@ -139,10 +145,12 @@ def create_entities opts[:server_api] = server_api end - create_client(**opts).tap do |client| - @observe_sensitive = spec.use('observeSensitiveCommands') - subscriber = (@subscribers[client] ||= EventSubscriber.new) - if oe = spec.use('observeEvents') + observe_events = spec.use('observeEvents') + subscriber = EventSubscriber.new + current_proc = opts[:sdam_proc] + opts[:sdam_proc] = lambda do |client| + current_proc.call if current_proc + if oe = observe_events oe.each do |event| case event when 'commandStartedEvent', 'commandSucceededEvent', 'commandFailedEvent' @@ -160,12 +168,23 @@ def create_entities end kind = event.sub('Event', '').gsub(/([A-Z])/) { "_#{$1}" }.sub('pool', 'Pool').downcase.to_sym subscriber.add_wanted_events(kind) + when 'serverDescriptionChangedEvent' + unless client.send(:monitoring).subscribers[Mongo::Monitoring::SERVER_DESCRIPTION_CHANGED]&.include?(subscriber) + client.subscribe(Mongo::Monitoring::SERVER_DESCRIPTION_CHANGED, subscriber) + end + kind = event.sub('Event', '').gsub(/([A-Z])/) { "_#{$1}" }.downcase.to_sym + subscriber.add_wanted_events(kind) else raise NotImplementedError, "Unknown event #{event}" end end end end + + create_client(**opts).tap do |client| + @observe_sensitive = spec.use('observeSensitiveCommands') + @subscribers[client] ||= subscriber + end when 'database' client = entities.get(:client, spec.use!('client')) opts = Utils.snakeize_hash(spec.use('databaseOptions') || {}) @@ -256,6 +275,28 @@ def create_entities key_vault_client, opts ) + when 'thread' + thread_context = ThreadContext.new + thread = Thread.new do + loop do + begin + op_spec = thread_context.operations.pop(true) + execute_operation(op_spec) + rescue ThreadError + # Queue is empty + end + if thread_context.stop? + break + else + sleep 1 + end + end + end + class << thread + attr_accessor :context + end + thread.context = thread_context + thread else raise NotImplementedError, "Unknown type #{type}" end @@ -354,7 +395,7 @@ def execute_operation(op) end public_send(method_name, op) - rescue Mongo::Error, BSON::String::IllegalKey, ArgumentError => e + rescue Mongo::Error, BSON::String::IllegalKey, Mongo::Auth::Unauthorized, ArgumentError => e if expected_error.use('isClientError') # isClientError doesn't actually mean a client error. # It means anything other than OperationFailure. DRIVERS-1799 diff --git a/spec/runners/unified/thread_operations.rb b/spec/runners/unified/thread_operations.rb new file mode 100644 index 0000000000..b19aa8cbe6 --- /dev/null +++ b/spec/runners/unified/thread_operations.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true +# encoding: utf-8 + +module Unified + + module ThreadOperations + + class ThreadContext + def initialize + @operations = Queue.new + end + + def stop? + !!@stop + end + + def signal_stop + @stop = true + end + + attr_reader :operations + end + + def wait(op) + consume_test_runner(op) + use_arguments(op) do |args| + sleep args.use!('ms') / 1000.0 + end + end + + def wait_for_event(op) + consume_test_runner(op) + use_arguments(op) do |args| + client = entities.get(:client, args.use!('client')) + subscriber = @subscribers.fetch(client) + event = args.use!('event') + assert_eq(event.keys.length, 1, "Expected event must have one key: #{event}") + count = args.use!('count') + + deadline = Mongo::Utils.monotonic_time + 10 + loop do + events = select_events(subscriber, event) + if events.length >= count + break + end + if Mongo::Utils.monotonic_time >= deadline + raise "Did not receive an event matching #{event} in 10 seconds; received #{events.length} but expected #{count} events" + else + sleep 0.1 + end + end + end + end + + def run_on_thread(op) + consume_test_runner(op) + use_arguments(op) do |args| + thread = entities.get(:thread, args.use!('thread')) + operation = args.use!('operation') + thread.context.operations << operation + end + end + + def wait_for_thread(op) + consume_test_runner(op) + use_arguments(op) do |args| + thread = entities.get(:thread, args.use!('thread')) + thread.context.signal_stop + thread.join + end + end + end +end diff --git a/spec/spec_tests/data/sdam_integration/cancel-server-check.yml b/spec/spec_tests/data/sdam_integration/cancel-server-check.yml deleted file mode 100644 index 2a759a8452..0000000000 --- a/spec/spec_tests/data/sdam_integration/cancel-server-check.yml +++ /dev/null @@ -1,96 +0,0 @@ -# Test SDAM error handling. -runOn: - # General failCommand requirements (this file does not use appName - # with failCommand). - - minServerVersion: "4.0" - topology: ["replicaset"] - - minServerVersion: "4.2" - topology: ["sharded"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "cancel-server-check" - -data: [] - -tests: - - description: Cancel server check - clientOptions: - retryWrites: true - heartbeatFrequencyMS: 10000 - # Server selection timeout MUST be less than heartbeatFrequencyMS for - # this test. This setting ensures that the retried insert will fail - # after 5 seconds if the driver does not properly cancel the in progress - # check. - serverSelectionTimeoutMS: 5000 - appname: cancelServerCheckTest - operations: - # Perform an operation to ensure the node is discovered. - - name: insertOne - object: collection - arguments: - document: - _id: 1 - # Configure the next inserts to fail with a non-timeout network error. - # This should: - # 1) Mark the server Unknown - # 2) Clear the connection pool - # 3) Cancel the in progress hello or legacy hello check and close the Monitor - # connection - # 4) The write will be then we retried, server selection will request an - # immediate check, and block for ~500ms until the next Monitor check - # proceeds. - # 5) The write will succeed on the second attempt. - - name: configureFailPoint - object: testRunner - arguments: - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: True - - name: insertOne - object: collection - arguments: - document: - _id: 2 - result: - insertedId: 2 - # The first error should mark the server Unknown and then clear the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - # Perform another operation to ensure the node still selectable. - - name: insertOne - object: collection - arguments: - document: - _id: 3 - result: - insertedId: 3 - # Assert the server was marked Unknown and pool was cleared exactly once. - - name: assertEventCount - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - - # Order of operations is non-deterministic so we cannot check events. - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} diff --git a/spec/spec_tests/data/sdam_integration/connectTimeoutMS.yml b/spec/spec_tests/data/sdam_integration/connectTimeoutMS.yml deleted file mode 100644 index 3ee4d32926..0000000000 --- a/spec/spec_tests/data/sdam_integration/connectTimeoutMS.yml +++ /dev/null @@ -1,88 +0,0 @@ -# Test SDAM error handling. -runOn: - # failCommand appName requirements - - minServerVersion: "4.4" - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "connectTimeoutMS" - -data: [] - -tests: - - description: connectTimeoutMS=0 - clientOptions: - retryWrites: false - connectTimeoutMS: 0 - heartbeatFrequencyMS: 500 - appname: connectTimeoutMS=0 - operations: - # Perform an operation to ensure the node is discovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 1 - - _id: 2 - # Block the next streaming hello check for longer than - # heartbeatFrequencyMS to ensure that the connection timeout remains - # unlimited. - - name: configureFailPoint - object: testRunner - arguments: - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["hello", "isMaster"] - appName: connectTimeoutMS=0 - blockConnection: true - blockTimeMS: 550 - - name: wait - object: testRunner - arguments: - ms: 750 - # Perform an operation to ensure the node is still selectable. - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - # Assert that the server was never marked Unknown and the pool was never - # cleared. - - name: assertEventCount - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 0 - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - - _id: 4 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} diff --git a/spec/spec_tests/data/sdam_integration/find-network-error.yml b/spec/spec_tests/data/sdam_integration/find-network-error.yml deleted file mode 100644 index 85bac6c953..0000000000 --- a/spec/spec_tests/data/sdam_integration/find-network-error.yml +++ /dev/null @@ -1,85 +0,0 @@ -# Test SDAM error handling. -runOn: - # failCommand appName requirements - - minServerVersion: "4.4" - # TODO Remove the line below when load balancer is ready - topology: ["replicaset","sharded","single"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "find-network-error" - -data: &data - - {_id: 1} - - {_id: 2} - -tests: - - description: Reset server and pool after network error on find - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["find"] - closeConnection: true - appName: findNetworkErrorTest - clientOptions: - retryWrites: false - retryReads: false - appname: findNetworkErrorTest - operations: - - name: find - object: collection - arguments: - filter: - _id: 1 - error: true - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - # Perform another operation to ensure the node is rediscovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 5 - - _id: 6 - # Assert the server was marked Unknown and pool was cleared exactly once. - - name: assertEventCount - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - - expectations: - - command_started_event: - command: - find: *collection_name - command_name: find - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 5 - - _id: 6 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 5} - - {_id: 6} diff --git a/spec/spec_tests/data/sdam_integration/find-shutdown-error.yml b/spec/spec_tests/data/sdam_integration/find-shutdown-error.yml deleted file mode 100644 index 4a1fb6ae2d..0000000000 --- a/spec/spec_tests/data/sdam_integration/find-shutdown-error.yml +++ /dev/null @@ -1,118 +0,0 @@ -# Test SDAM error handling. -runOn: - # failCommand appName requirements - - minServerVersion: "4.4" - # TODO Remove the line below when load balancer is ready - topology: ["replicaset","sharded","single"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "find-shutdown-error" - -data: [] - -tests: - - description: Concurrent shutdown error on find - clientOptions: - retryWrites: false - retryReads: false - heartbeatFrequencyMS: 500 - appname: shutdownErrorFindTest - operations: - # Perform an operation to ensure the node is discovered. - - name: insertOne - object: collection - arguments: - document: - _id: 1 - # Configure the next two finds to fail with a non-timeout shutdown - # errors. Block the connection for 500ms to ensure both operations check - # out connections from the same pool generation. - - name: configureFailPoint - object: testRunner - arguments: - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["find"] - appName: shutdownErrorFindTest - errorCode: 91 - blockConnection: true - blockTimeMS: 500 - # Start threads. - - name: startThread - object: testRunner - arguments: - name: thread1 - - name: startThread - object: testRunner - arguments: - name: thread2 - # Perform concurrent find operations. Both fail with shutdown errors. - - name: runOnThread - object: testRunner - arguments: - name: thread1 - operation: - name: find - object: collection - arguments: - filter: - _id: 1 - error: true - - name: runOnThread - object: testRunner - arguments: - name: thread2 - operation: - name: find - object: collection - arguments: - filter: - _id: 1 - error: true - # Stop threads. - - name: waitForThread - object: testRunner - arguments: - name: thread1 - - name: waitForThread - object: testRunner - arguments: - name: thread2 - # The first shutdown error should mark the server Unknown and then clear - # the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - # Perform an operation to ensure the node is rediscovered. - - name: insertOne - object: collection - arguments: - document: - _id: 4 - # Assert the server was marked Unknown and pool was cleared exactly once. - - name: assertEventCount - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - - # Order of operations is non-deterministic so we cannot check events. - outcome: - collection: - data: - - {_id: 1} - - {_id: 4} diff --git a/spec/spec_tests/data/sdam_integration/hello-command-error.yml b/spec/spec_tests/data/sdam_integration/hello-command-error.yml deleted file mode 100644 index 1cdb5adc62..0000000000 --- a/spec/spec_tests/data/sdam_integration/hello-command-error.yml +++ /dev/null @@ -1,152 +0,0 @@ -# Test SDAM error handling. -runOn: - # failCommand appName requirements - - minServerVersion: "4.9" - # TODO Remove the line below when load balancer is completed. - topology: ["replicaset","sharded","single"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "hello-command-error" - -data: [] - -tests: - - description: Command error on Monitor handshake - # Configure the initial handshake to fail with a command error. - # Use times: 2 so that the RTT hello fails as well. - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["hello", "isMaster"] - appName: commandErrorHandshakeTest - closeConnection: false - errorCode: 91 # ShutdownInProgress - clientOptions: - retryWrites: false - connectTimeoutMS: 250 - heartbeatFrequencyMS: 500 - appname: commandErrorHandshakeTest - operations: - # The command error on the initial handshake should mark the server - # Unknown (emitting a ServerDescriptionChangedEvent) and clear the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - # Perform an operation to ensure the node is discovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 1 - - _id: 2 - # We cannot assert the server was marked Unknown and pool was cleared an - # exact number of times because the RTT hello may or may not have - # triggered this failpoint as well. - # - name: assertEventCount - # object: testRunner - # arguments: - # event: ServerMarkedUnknownEvent - # count: 1 - # - name: assertEventCount - # object: testRunner - # arguments: - # event: PoolClearedEvent - # count: 1 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - - description: Command error on Monitor check - clientOptions: - retryWrites: false - connectTimeoutMS: 1000 - heartbeatFrequencyMS: 500 - appname: commandErrorCheckTest - operations: - # Perform an operation to ensure the node is discovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 1 - - _id: 2 - # Configure the next streaming hello check to fail with a command error. - # Must use "times: 2" here. For a longer on explanation on why, check out - # https://jira.mongodb.org/browse/RUBY-3050. - - name: configureFailPoint - object: testRunner - arguments: - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["hello", "isMaster"] - appName: commandErrorCheckTest - closeConnection: false - blockConnection: true - blockTimeMS: 750 - errorCode: 91 # ShutdownInProgress - # The command error on the next check should mark the server Unknown and - # clear the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - # Perform an operation to ensure the node is rediscovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - # We cannot assert the server was marked Unknown and pool was cleared an - # exact number of times because the RTT hello may have triggered this - # failpoint one or many times as well. - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - - _id: 4 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} diff --git a/spec/spec_tests/data/sdam_integration/hello-network-error.yml b/spec/spec_tests/data/sdam_integration/hello-network-error.yml deleted file mode 100644 index b667103040..0000000000 --- a/spec/spec_tests/data/sdam_integration/hello-network-error.yml +++ /dev/null @@ -1,148 +0,0 @@ -#hello Test SDAM error handling. -runOn: - # failCommand appName requirements - - minServerVersion: "4.9" - # TODO Uncomment the line below when load balancer is ready. - topology: ["replicaset","sharded","single"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "hello-network-error" - -data: [] - -tests: - - description: Network error on Monitor handshake - # Configure the initial handshake to fail with a network error. - # Use times: 2 so that the RTT hello fails as well. - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["hello", "isMaster"] - appName: networkErrorHandshakeTest - closeConnection: true - clientOptions: - retryWrites: false - connectTimeoutMS: 250 - heartbeatFrequencyMS: 500 - appname: networkErrorHandshakeTest - operations: - # The network error on the initial handshake should mark the server - # Unknown (emitting a ServerDescriptionChangedEvent) and clear the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - # Perform an operation to ensure the node is discovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 1 - - _id: 2 - # We cannot assert the server was marked Unknown and pool was cleared an - # exact number of times because the RTT hello may or may not have - # triggered this failpoint as well. - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - - description: Network error on Monitor check - clientOptions: - retryWrites: false - connectTimeoutMS: 250 - heartbeatFrequencyMS: 500 - appname: networkErrorCheckTest - operations: - # Perform an operation to ensure the node is discovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 1 - - _id: 2 - # Configure the next streaming hello check to fail with a non-timeout - # network error. Must use "times: 2" here. For a longer on explanation - # on why, check out https://jira.mongodb.org/browse/RUBY-3050. - - name: configureFailPoint - object: testRunner - arguments: - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["hello", "isMaster"] - appName: networkErrorCheckTest - closeConnection: true - # The network error on the next check should mark the server Unknown and - # clear the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - # Perform an operation to ensure the node is rediscovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - # We cannot assert the server was marked Unknown and pool was cleared an - # exact number of times because the RTT hello may have triggered this - # failpoint one or many times as well. - # - name: assertEventCount - # object: testRunner - # arguments: - # event: ServerMarkedUnknownEvent - # count: 1 - # - name: assertEventCount - # object: testRunner - # arguments: - # event: PoolClearedEvent - # count: 1 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - - _id: 4 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} diff --git a/spec/spec_tests/data/sdam_integration/hello-timeout.yml b/spec/spec_tests/data/sdam_integration/hello-timeout.yml deleted file mode 100644 index d3adfe003a..0000000000 --- a/spec/spec_tests/data/sdam_integration/hello-timeout.yml +++ /dev/null @@ -1,219 +0,0 @@ -# Test SDAM error handling. -runOn: - # failCommand appName requirements - - minServerVersion: "4.4" - # It seems this spec should not run against LB. - topology: ["single", "replicaset", "sharded"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "hello-timeout" - -data: [] - -tests: - - description: Network timeout on Monitor handshake - # Configure the initial handshake to fail with a timeout. - # Use times: 2 so that the RTT hello is blocked as well. - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["hello", "isMaster"] - appName: timeoutMonitorHandshakeTest - blockConnection: true - blockTimeMS: 1000 - clientOptions: - retryWrites: false - connectTimeoutMS: 250 - heartbeatFrequencyMS: 500 - appname: timeoutMonitorHandshakeTest - operations: - # The network error on the initial handshake should mark the server - # Unknown (emitting a ServerDescriptionChangedEvent) and clear the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - # Perform an operation to ensure the node is discovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 1 - - _id: 2 - # We cannot assert the server was marked Unknown and pool was cleared an - # exact number of times because the RTT hello may or may not have - # triggered this failpoint as well. - # - name: assertEventCount - # object: testRunner - # arguments: - # event: ServerMarkedUnknownEvent - # count: 1 - # - name: assertEventCount - # object: testRunner - # arguments: - # event: PoolClearedEvent - # count: 1 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - - description: Network timeout on Monitor check - clientOptions: - retryWrites: false - connectTimeoutMS: 750 - heartbeatFrequencyMS: 500 - appname: timeoutMonitorCheckTest - operations: - # Perform an operation to ensure the node is discovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 1 - - _id: 2 - # Configure the next streaming hello check to fail with a timeout. - # Use "times: 4" to increase the probability that the Monitor check times - # out since the RTT hello may trigger this failpoint one or many times as - # well. - - name: configureFailPoint - object: testRunner - arguments: - failPoint: - configureFailPoint: failCommand - mode: { times: 4 } - data: - failCommands: ["hello", "isMaster"] - appName: timeoutMonitorCheckTest - blockConnection: true - # blockTimeMS is evaluated after the waiting for heartbeatFrequencyMS server-side, so this value only - # needs to be greater than connectTimeoutMS. The driver will wait for (500+750)ms and the server will - # respond after (500+1000)ms. - blockTimeMS: 1000 - # The network error on the next check should mark the server Unknown and - # clear the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - # Perform an operation to ensure the node is rediscovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - # We cannot assert the server was marked Unknown and pool was cleared an - # exact number of times because the RTT hello may have triggered this - # failpoint one or many times as well. - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - - _id: 4 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - - - description: Driver extends timeout while streaming - clientOptions: - retryWrites: false - connectTimeoutMS: 250 - heartbeatFrequencyMS: 500 - appname: extendsTimeoutTest - operations: - # Perform an operation to ensure the node is discovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 1 - - _id: 2 - # Wait for multiple monitor checks to complete. - - name: wait - object: testRunner - arguments: - ms: 2000 - # Perform an operation to ensure the node is still selectable. - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - # Assert that the server was never marked Unknown and the pool was never - # cleared. - - name: assertEventCount - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 0 - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - - _id: 4 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} diff --git a/spec/spec_tests/data/sdam_integration/insert-network-error.yml b/spec/spec_tests/data/sdam_integration/insert-network-error.yml deleted file mode 100644 index 8660199393..0000000000 --- a/spec/spec_tests/data/sdam_integration/insert-network-error.yml +++ /dev/null @@ -1,88 +0,0 @@ -# Test SDAM error handling. -runOn: - # failCommand appName requirements - - minServerVersion: "4.4" - # TODO Remove the line below when load balancer is ready - topology: ["replicaset","sharded","single"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "insert-network-error" - -data: &data - - {_id: 1} - - {_id: 2} - -tests: - - description: Reset server and pool after network error on insert - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - appName: insertNetworkErrorTest - clientOptions: - retryWrites: false - appname: insertNetworkErrorTest - operations: - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - error: true - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - # Perform another operation to ensure the node is rediscovered. - - name: insertMany - object: collection - arguments: - documents: - - _id: 5 - - _id: 6 - # Assert the server was marked Unknown and pool was cleared exactly once. - - name: assertEventCount - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - - _id: 4 - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 5 - - _id: 6 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 5} - - {_id: 6} diff --git a/spec/spec_tests/data/sdam_integration/insert-shutdown-error.yml b/spec/spec_tests/data/sdam_integration/insert-shutdown-error.yml deleted file mode 100644 index 4263880048..0000000000 --- a/spec/spec_tests/data/sdam_integration/insert-shutdown-error.yml +++ /dev/null @@ -1,117 +0,0 @@ -# Test SDAM error handling. -runOn: - # failCommand appName requirements - - minServerVersion: "4.4" - # TODO Remove the line below when load balancer is ready - topology: ["replicaset","sharded","single"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "insert-shutdown-error" - -data: [] - -tests: - - description: Concurrent shutdown error on insert - clientOptions: - retryWrites: false - heartbeatFrequencyMS: 500 - appname: shutdownErrorInsertTest - operations: - # Perform an operation to ensure the node is discovered. - - name: insertOne - object: collection - arguments: - document: - _id: 1 - # Configure the next two inserts to fail with a non-timeout shutdown - # errors. Block the connection for 500ms to ensure both operations check - # out connections from the same pool generation. - - name: configureFailPoint - object: testRunner - arguments: - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["insert"] - appName: shutdownErrorInsertTest - errorCode: 91 - blockConnection: true - blockTimeMS: 500 - # Start threads. - - name: startThread - object: testRunner - arguments: - name: thread1 - - name: startThread - object: testRunner - arguments: - name: thread2 - # Perform concurrent insert operations. Both fail with shutdown errors. - - name: runOnThread - object: testRunner - arguments: - name: thread1 - operation: - name: insertOne - object: collection - arguments: - document: - _id: 2 - error: true - - name: runOnThread - object: testRunner - arguments: - name: thread2 - operation: - name: insertOne - object: collection - arguments: - document: - _id: 3 - error: true - # Stop threads. - - name: waitForThread - object: testRunner - arguments: - name: thread1 - - name: waitForThread - object: testRunner - arguments: - name: thread2 - # The first shutdown error should mark the server Unknown and then clear - # the pool. - - name: waitForEvent - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - # Perform an operation to ensure the node is rediscovered. - - name: insertOne - object: collection - arguments: - document: - _id: 4 - # Assert the server was marked Unknown and pool was cleared exactly once. - - name: assertEventCount - object: testRunner - arguments: - event: ServerMarkedUnknownEvent - count: 1 - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - - # Order of operations is non-deterministic so we cannot check events. - outcome: - collection: - data: - - {_id: 1} - - {_id: 4} diff --git a/spec/spec_tests/data/sdam_integration/rediscover-quickly-after-step-down.yml b/spec/spec_tests/data/sdam_integration/rediscover-quickly-after-step-down.yml deleted file mode 100644 index fd8b430703..0000000000 --- a/spec/spec_tests/data/sdam_integration/rediscover-quickly-after-step-down.yml +++ /dev/null @@ -1,98 +0,0 @@ -runOn: - # 4.4 is required for streaming. - # A replica set is required for replSetStepDown. - - minServerVersion: "4.4" - topology: ["replicaset"] - -database_name: &database_name "sdam-tests" -collection_name: &collection_name "test-replSetStepDown" - -data: &data - - {_id: 1} - - {_id: 2} - -tests: - - description: Rediscover quickly after replSetStepDown - clientOptions: - appname: replSetStepDownTest - # Configure a large heartbeatFrequencyMS - heartbeatFrequencyMS: 60000 - # Configure a much smaller server selection timeout so that the test - # will error when it cannot discover the new primary soon. - serverSelectionTimeoutMS: 5000 - w: majority - operations: - # Discover the primary. - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - - name: recordPrimary - object: testRunner - # Unfreeze a secondary with replSetFreeze:0 to ensure a speedy election. - - name: runAdminCommand - object: testRunner - command_name: replSetFreeze - arguments: - command: - replSetFreeze: 0 - readPreference: - mode: Secondary - # Run replSetStepDown on the meta client. - - name: runAdminCommand - object: testRunner - command_name: replSetStepDown - arguments: - command: - replSetStepDown: 30 - secondaryCatchUpPeriodSecs: 30 - force: false - - name: waitForPrimaryChange - object: testRunner - arguments: - # We use a relatively large timeout here to workaround slow - # elections on Windows, possibly caused by SERVER-48154. - timeoutMS: 15000 - # Rediscover the new primary. - - name: insertMany - object: collection - arguments: - documents: - - _id: 5 - - _id: 6 - # Assert that no pools were cleared. - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - - _id: 4 - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 5 - - _id: 6 - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - - {_id: 5} - - {_id: 6} diff --git a/spec/spec_tests/data/sdam_unified/auth-error.yml b/spec/spec_tests/data/sdam_unified/auth-error.yml new file mode 100644 index 0000000000..b2dfc4eccc --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/auth-error.yml @@ -0,0 +1,130 @@ +description: auth-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + auth: true + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName auth-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 +tests: + - description: Reset server and pool after AuthenticationFailure error + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslContinue + appName: authErrorTest + errorCode: 18 + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + appname: authErrorTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform another operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 5 + - _id: 6 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + expectEvents: + # Note: The first insert command is never attempted because connection + # checkout fails. + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: auth-error + documents: + - _id: 5 + - _id: 6 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 5 + - _id: 6 diff --git a/spec/spec_tests/data/sdam_unified/auth-misc-command-error.yml b/spec/spec_tests/data/sdam_unified/auth-misc-command-error.yml new file mode 100644 index 0000000000..94d41d6616 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/auth-misc-command-error.yml @@ -0,0 +1,132 @@ +--- +description: auth-misc-command-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + auth: true + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName auth-misc-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Reset server and pool after misc command error + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslContinue + appName: authMiscErrorTest + errorCode: 1 # InternalError + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + appname: authMiscErrorTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform another operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 5 + - _id: 6 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + expectEvents: + # Note: The first insert command is never attempted because connection + # checkout fails. + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: auth-misc-error + documents: + - _id: 5 + - _id: 6 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 5 + - _id: 6 diff --git a/spec/spec_tests/data/sdam_unified/auth-network-error.yml b/spec/spec_tests/data/sdam_unified/auth-network-error.yml new file mode 100644 index 0000000000..9073a927ce --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/auth-network-error.yml @@ -0,0 +1,132 @@ +--- +description: auth-network-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + auth: true + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName auth-network-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Reset server and pool after network error during authentication + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslContinue + closeConnection: true + appName: authNetworkErrorTest + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + appname: authNetworkErrorTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform another operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 5 + - _id: 6 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + expectEvents: + # Note: The first insert command is never attempted because connection + # checkout fails. + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: auth-network-error + documents: + - _id: 5 + - _id: 6 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 5 + - _id: 6 diff --git a/spec/spec_tests/data/sdam_unified/auth-shutdown-error.yml b/spec/spec_tests/data/sdam_unified/auth-shutdown-error.yml new file mode 100644 index 0000000000..87a937d381 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/auth-shutdown-error.yml @@ -0,0 +1,133 @@ +--- +description: auth-shutdown-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + auth: true + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName auth-shutdown-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Reset server and pool after shutdown error during authentication + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslContinue + appName: authShutdownErrorTest + errorCode: 91 + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + appname: authShutdownErrorTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform another operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 5 + - _id: 6 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + expectEvents: + # Note: The first insert command is never attempted because connection + # checkout fails. + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: auth-shutdown-error + documents: + - _id: 5 + - _id: 6 + commandName: insert + databaseName: *databaseName + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 5 + - _id: 6 diff --git a/spec/spec_tests/data/sdam_unified/cancel-server-check.yml b/spec/spec_tests/data/sdam_unified/cancel-server-check.yml new file mode 100644 index 0000000000..67d96706e1 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/cancel-server-check.yml @@ -0,0 +1,143 @@ +--- +description: cancel-server-check + +schemaVersion: "1.10" + +runOnRequirements: + # General failCommand requirements (this file does not use appName + # with failCommand). + - minServerVersion: "4.0" + topologies: + - replicaset + serverless: forbid + - minServerVersion: "4.2" + topologies: + - sharded + serverless: forbid + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName cancel-server-check + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Cancel server check + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: true + heartbeatFrequencyMS: 10000 + # Server selection timeout MUST be less than heartbeatFrequencyMS for + # this test. This setting ensures that the retried insert will fail + # after 5 seconds if the driver does not properly cancel the in progress + # check. + serverSelectionTimeoutMS: 5000 + appname: cancelServerCheckTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertOne + object: *collection + arguments: + document: + _id: 1 + # Configure the next inserts to fail with a non-timeout network error. + # This should: + # 1) Mark the server Unknown + # 2) Clear the connection pool + # 3) Cancel the in progress hello or legacy hello check and close the Monitor + # connection + # 4) The write will be then we retried, server selection will request an + # immediate check, and block for ~500ms until the next Monitor check + # proceeds. + # 5) The write will succeed on the second attempt. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - insert + closeConnection: true + client: *setupClient + - name: insertOne + object: *collection + arguments: + document: + _id: 2 + expectResult: + insertedId: 2 + # The first error should mark the server Unknown and then clear the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform another operation to ensure the node still selectable. + - name: insertOne + object: *collection + arguments: + document: + _id: 3 + expectResult: + insertedId: 3 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + + # Order of operations is non-deterministic so we cannot check events. + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 diff --git a/spec/spec_tests/data/sdam_unified/connectTimeoutMS.yml b/spec/spec_tests/data/sdam_unified/connectTimeoutMS.yml new file mode 100644 index 0000000000..ef6d1150a7 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/connectTimeoutMS.yml @@ -0,0 +1,130 @@ +--- +description: connectTimeoutMS + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName connectTimeoutMS + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: connectTimeoutMS=0 + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + connectTimeoutMS: 0 + heartbeatFrequencyMS: 500 + appname: connectTimeoutMS=0 + useMultipleMongoses: false + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 1 + - _id: 2 + # Block the next streaming hello check for longer than + # heartbeatFrequencyMS to ensure that the connection timeout remains + # unlimited. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - hello + - isMaster + appName: connectTimeoutMS=0 + blockConnection: true + blockTimeMS: 550 + client: *setupClient + - name: wait + object: testRunner + arguments: + ms: 750 + # Perform an operation to ensure the node is still selectable. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + # Assert that the server was never marked Unknown and the pool was never + # cleared. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 0 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 0 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: connectTimeoutMS + documents: + - _id: 1 + - _id: 2 + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: connectTimeoutMS + documents: + - _id: 3 + - _id: 4 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 + - _id: 4 diff --git a/spec/spec_tests/data/sdam_unified/find-network-error.yml b/spec/spec_tests/data/sdam_unified/find-network-error.yml new file mode 100644 index 0000000000..deae09a19f --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/find-network-error.yml @@ -0,0 +1,135 @@ +--- +description: find-network-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName find-network-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Reset server and pool after network error on find + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + closeConnection: true + appName: findNetworkErrorTest + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + retryReads: false + appname: findNetworkErrorTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: find + object: *collection + arguments: + filter: + _id: 1 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform another operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 5 + - _id: 6 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + find: find-network-error + commandName: find + databaseName: *databaseName + - commandStartedEvent: + command: + insert: find-network-error + documents: + - _id: 5 + - _id: 6 + commandName: insert + databaseName: *databaseName + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 5 + - _id: 6 diff --git a/spec/spec_tests/data/sdam_unified/find-shutdown-error.yml b/spec/spec_tests/data/sdam_unified/find-shutdown-error.yml new file mode 100644 index 0000000000..f2da705d9e --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/find-shutdown-error.yml @@ -0,0 +1,163 @@ +--- +description: find-shutdown-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName find-shutdown-error + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Concurrent shutdown error on find + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + uriOptions: + retryWrites: false + retryReads: false + heartbeatFrequencyMS: 500 + appname: shutdownErrorFindTest + observeEvents: + - serverDescriptionChangedEvent + - poolClearedEvent + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertOne + object: *collection + arguments: + document: + _id: 1 + # Configure the next two finds to fail with a non-timeout shutdown + # errors. Block the connection for 500ms to ensure both operations check + # out connections from the same pool generation. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + appName: shutdownErrorFindTest + errorCode: 91 + blockConnection: true + blockTimeMS: 500 + client: *setupClient + # Start threads. + - name: createEntities + object: testRunner + arguments: + entities: + - thread: + id: &thread0 thread0 + - thread: + id: &thread1 thread1 + # Perform concurrent find operations. Both fail with shutdown errors. + - name: runOnThread + object: testRunner + arguments: + thread: *thread0 + operation: + name: find + object: *collection + arguments: + filter: + _id: 1 + expectError: + isError: true + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: find + object: *collection + arguments: + filter: + _id: 1 + expectError: + isError: true + # Stop threads. + - name: waitForThread + object: testRunner + arguments: + thread: *thread0 + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + # The first shutdown error should mark the server Unknown and then clear + # the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform an operation to ensure the node is rediscovered. + - name: insertOne + object: *collection + arguments: + document: + _id: 4 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + + # Order of operations is non-deterministic so we cannot check events. + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 4 diff --git a/spec/spec_tests/data/sdam_unified/hello-command-error.yml b/spec/spec_tests/data/sdam_unified/hello-command-error.yml new file mode 100644 index 0000000000..2dc8be2f3b --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/hello-command-error.yml @@ -0,0 +1,233 @@ +--- +description: hello-command-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.9" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName hello-command-error + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Command error on Monitor handshake + operations: + # Configure the next streaming hello check to fail with a command error. + # Use "times: 4" to increase the probability that the Monitor check fails + # since the RTT hello may trigger this failpoint one or many times as + # well. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 4 + data: + failCommands: + - hello + - isMaster + appName: commandErrorHandshakeTest + closeConnection: false + errorCode: 91 + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - serverDescriptionChangedEvent + - poolClearedEvent + - commandStartedEvent + uriOptions: + retryWrites: false + connectTimeoutMS: 250 + heartbeatFrequencyMS: 500 + appname: commandErrorHandshakeTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # The command error on the initial handshake should mark the server + # Unknown (emitting a ServerDescriptionChangedEvent) and clear the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + # Perform an operation to ensure the node is discovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 1 + - _id: 2 + # We cannot assert the server was marked Unknown and pool was cleared an + # exact number of times because the RTT hello may or may not have + # triggered this failpoint as well. + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: hello-command-error + documents: + - _id: 1 + - _id: 2 + commandName: insert + databaseName: *databaseName + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + + - description: Command error on Monitor check + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + connectTimeoutMS: 1000 + heartbeatFrequencyMS: 500 + appname: commandErrorCheckTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 1 + - _id: 2 + # Configure the next streaming hello check to fail with a command + # error. + # Use times: 2 so that the RTT hello is blocked as well. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - hello + - isMaster + appName: commandErrorCheckTest + closeConnection: false + blockConnection: true + blockTimeMS: 750 + errorCode: 91 + client: *setupClient + # The command error on the next check should mark the server Unknown and + # clear the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform an operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: hello-command-error + documents: + - _id: 1 + - _id: 2 + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: hello-command-error + documents: + - _id: 3 + - _id: 4 + commandName: insert + databaseName: *databaseName + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 + - _id: 4 diff --git a/spec/spec_tests/data/sdam_unified/hello-network-error.yml b/spec/spec_tests/data/sdam_unified/hello-network-error.yml new file mode 100644 index 0000000000..fc3cdc4180 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/hello-network-error.yml @@ -0,0 +1,228 @@ +--- +description: hello-network-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.9" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName hello-network-error + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Network error on Monitor handshake + # Configure the initial handshake to fail with a network error. + # Use times: 2 so that the RTT hello fails as well. + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - hello + - isMaster + appName: networkErrorHandshakeTest + closeConnection: true + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + connectTimeoutMS: 250 + heartbeatFrequencyMS: 500 + appname: networkErrorHandshakeTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # The network error on the initial handshake should mark the server + # Unknown (emitting a ServerDescriptionChangedEvent) and clear the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + # Perform an operation to ensure the node is discovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 1 + - _id: 2 + # We cannot assert the server was marked Unknown and pool was cleared an + # exact number of times because the RTT hello may or may not have + # triggered this failpoint as well. + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: hello-network-error + documents: + - _id: 1 + - _id: 2 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + + - description: Network error on Monitor check + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + connectTimeoutMS: 250 + heartbeatFrequencyMS: 500 + appname: networkErrorCheckTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 1 + - _id: 2 + # Configure the next streaming hello check to fail with a non-timeout + # network error. Use "times: 4" to increase the probability that the + # Monitor check fails since the RTT hello may trigger this failpoint one + # or many times as well. + # The ruby driver only needs to fail twice because the RTT updates topology. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - hello + - isMaster + appName: networkErrorCheckTest + closeConnection: true + client: *setupClient + # The network error on the next check should mark the server Unknown and + # clear the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform an operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + # We cannot assert the server was marked Unknown and pool was cleared an + # exact number of times because the RTT hello may or may not have + # triggered this failpoint as well. + # - name: assertEventCount + # object: testRunner + # arguments: + # client: *client + # event: + # serverDescriptionChangedEvent: + # newDescription: + # type: Unknown + # count: 1 + # - name: assertEventCount + # object: testRunner + # arguments: + # event: + # poolClearedEvent: {} + # count: 1 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: hello-network-error + documents: + - _id: 1 + - _id: 2 + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: hello-network-error + documents: + - _id: 3 + - _id: 4 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 + - _id: 4 diff --git a/spec/spec_tests/data/sdam_unified/hello-timeout.yml b/spec/spec_tests/data/sdam_unified/hello-timeout.yml new file mode 100644 index 0000000000..efab836e65 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/hello-timeout.yml @@ -0,0 +1,318 @@ +--- +description: hello-timeout + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName hello-timeout + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Network timeout on Monitor handshake + operations: + # Configure the initial handshake to fail with a timeout. + # Use times: 2 so that the RTT hello is blocked as well. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - hello + - isMaster + appName: timeoutMonitorHandshakeTest + blockConnection: true + blockTimeMS: 1000 + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + connectTimeoutMS: 250 + heartbeatFrequencyMS: 500 + appname: timeoutMonitorHandshakeTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # The network error on the initial handshake should mark the server + # Unknown (emitting a ServerDescriptionChangedEvent) and clear the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + # Perform an operation to ensure the node is discovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 1 + - _id: 2 + # We cannot assert the server was marked Unknown and pool was cleared an + # exact number of times because the RTT hello may or may not have + # triggered this failpoint as well. + # - name: assertEventCount + # object: testRunner + # arguments: + # event: ServerMarkedUnknownEvent + # count: 1 + # - name: assertEventCount + # object: testRunner + # arguments: + # event: PoolClearedEvent + # count: 1 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: hello-timeout + documents: + - _id: 1 + - _id: 2 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + + - description: Network timeout on Monitor check + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + connectTimeoutMS: 750 + heartbeatFrequencyMS: 500 + appname: timeoutMonitorCheckTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 1 + - _id: 2 + # Configure the next streaming hello check to fail with a timeout. + # Use "times: 4" to increase the probability that the Monitor check times + # out since the RTT hello may trigger this failpoint one or many times as + # well. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: + times: 4 + data: + failCommands: + - hello + - isMaster + appName: timeoutMonitorCheckTest + blockConnection: true + # blockTimeMS is evaluated after the waiting for heartbeatFrequencyMS server-side, so this value only + # needs to be greater than connectTimeoutMS. The driver will wait for (500+750)ms and the server will + # respond after (500+1000)ms. + blockTimeMS: 1000 + client: *setupClient + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + # The network error on the next check should mark the server Unknown and + # clear the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform an operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + # We cannot assert the server was marked Unknown and pool was cleared an + # exact number of times because the RTT hello may have triggered this + # failpoint one or many times as well. + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: hello-timeout + documents: + - _id: 1 + - _id: 2 + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: hello-timeout + documents: + - _id: 3 + - _id: 4 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 + - _id: 4 + + - description: Driver extends timeout while streaming + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + connectTimeoutMS: 250 + heartbeatFrequencyMS: 500 + appname: extendsTimeoutTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 1 + - _id: 2 + # Wait for multiple monitor checks to complete. + - name: wait + object: testRunner + arguments: + ms: 2000 + # Perform an operation to ensure the node is still selectable. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + # Assert that the server was never marked Unknown and the pool was never + # cleared. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 0 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 0 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: hello-timeout + documents: + - _id: 1 + - _id: 2 + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: hello-timeout + documents: + - _id: 3 + - _id: 4 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 + - _id: 4 diff --git a/spec/spec_tests/data/sdam_unified/insert-network-error.yml b/spec/spec_tests/data/sdam_unified/insert-network-error.yml new file mode 100644 index 0000000000..fc9c2f4921 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/insert-network-error.yml @@ -0,0 +1,137 @@ +--- +description: insert-network-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName insert-network-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Reset server and pool after network error on insert + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - insert + closeConnection: true + appName: insertNetworkErrorTest + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + appname: insertNetworkErrorTest + useMultipleMongoses: false + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform another operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 5 + - _id: 6 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: insert-network-error + documents: + - _id: 3 + - _id: 4 + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: insert-network-error + documents: + - _id: 5 + - _id: 6 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 5 + - _id: 6 diff --git a/spec/spec_tests/data/sdam_unified/insert-shutdown-error.yml b/spec/spec_tests/data/sdam_unified/insert-shutdown-error.yml new file mode 100644 index 0000000000..1ec920a6bc --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/insert-shutdown-error.yml @@ -0,0 +1,162 @@ +--- +description: insert-shutdown-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName insert-shutdown-error + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Concurrent shutdown error on insert + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + uriOptions: + retryWrites: false + heartbeatFrequencyMS: 500 + appname: shutdownErrorInsertTest + observeEvents: + - serverDescriptionChangedEvent + - poolClearedEvent + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertOne + object: *collection + arguments: + document: + _id: 1 + # Configure the next two inserts to fail with a non-timeout shutdown + # errors. Block the connection for 500ms to ensure both operations check + # out connections from the same pool generation. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - insert + appName: shutdownErrorInsertTest + errorCode: 91 + blockConnection: true + blockTimeMS: 500 + client: *setupClient + # Start threads. + - name: createEntities + object: testRunner + arguments: + entities: + - thread: + id: &thread0 thread0 + - thread: + id: &thread1 thread1 + # Perform concurrent insert operations. Both fail with shutdown errors. + - name: runOnThread + object: testRunner + arguments: + thread: *thread0 + operation: + name: insertOne + object: *collection + arguments: + document: + _id: 2 + expectError: + isError: true + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: insertOne + object: *collection + arguments: + document: + _id: 3 + expectError: + isError: true + # Stop threads. + - name: waitForThread + object: testRunner + arguments: + thread: *thread0 + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + # The first shutdown error should mark the server Unknown and then clear + # the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform an operation to ensure the node is rediscovered. + - name: insertOne + object: *collection + arguments: + document: + _id: 4 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + + # Order of operations is non-deterministic so we cannot check events. + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 4 diff --git a/spec/spec_tests/data/sdam_unified/pool-cleared-error.yml b/spec/spec_tests/data/sdam_unified/pool-cleared-error.yml new file mode 100644 index 0000000000..07bfc0c0d5 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/pool-cleared-error.yml @@ -0,0 +1,239 @@ +--- +description: pool-cleared-error + +schemaVersion: "1.10" + +runOnRequirements: + # This test requires retryable writes, failCommand appName, and + # failCommand blockConnection with closeConnection:true (SERVER-53512). + - minServerVersion: "4.9" + serverless: forbid + topologies: + - replicaset + - sharded + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName pool-cleared-error + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: PoolClearedError does not mark server unknown + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: true + maxPoolSize: 1 + appname: poolClearedErrorTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Perform an operation to ensure the node is discovered. + - name: insertOne + object: *collection + arguments: + document: + _id: 1 + # Configure the next insert to fail with a network error which will + # clear the pool leaving it paused until the server is rediscovered. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - insert + blockConnection: true + blockTimeMS: 100 + closeConnection: true + appName: poolClearedErrorTest + client: *setupClient + # Start threads. + - name: createEntities + object: testRunner + arguments: + entities: + - thread: + id: &thread0 thread0 + - thread: + id: &thread1 thread1 + - thread: + id: &thread2 thread2 + - thread: + id: &thread3 thread3 + - thread: + id: &thread4 thread4 + - thread: + id: &thread5 thread5 + # Perform concurrent insert operations. The first one to execute will + # fail with a network error, mark the server Unknown, clear the pool, + # and retry. + # The other operations will either: + # - Notice the pool is paused, fail with a PoolClearedError, and retry. + # - Or block waiting in server selection until the server is + # rediscovered. + # + # Note that this test does not guarantee that a PoolClearedError will be + # raised but it is likely since the initial insert is delayed. + - name: runOnThread + object: testRunner + arguments: + thread: *thread0 + operation: + name: insertOne + object: *collection + arguments: + document: + _id: 2 + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: insertOne + object: *collection + arguments: + document: + _id: 3 + - name: runOnThread + object: testRunner + arguments: + thread: *thread2 + operation: + name: insertOne + object: *collection + arguments: + document: + _id: 4 + - name: runOnThread + object: testRunner + arguments: + thread: *thread3 + operation: + name: insertOne + object: *collection + arguments: + document: + _id: 5 + - name: runOnThread + object: testRunner + arguments: + thread: *thread4 + operation: + name: insertOne + object: *collection + arguments: + document: + _id: 6 + - name: runOnThread + object: testRunner + arguments: + thread: *thread5 + operation: + name: insertOne + object: *collection + arguments: + document: + _id: 7 + # Stop threads. + - name: waitForThread + object: testRunner + arguments: + thread: *thread0 + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + - name: waitForThread + object: testRunner + arguments: + thread: *thread2 + - name: waitForThread + object: testRunner + arguments: + thread: *thread3 + - name: waitForThread + object: testRunner + arguments: + thread: *thread4 + - name: waitForThread + object: testRunner + arguments: + thread: *thread5 + # The first shutdown error should mark the server Unknown and then clear + # the pool. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform an operation to ensure the node still useable. + - name: insertOne + object: *collection + arguments: + document: + _id: 8 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + + # Order of operations is non-deterministic so we cannot check events. + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 + - _id: 4 + - _id: 5 + - _id: 6 + - _id: 7 + - _id: 8 diff --git a/spec/spec_tests/data/sdam_unified/rediscover-quickly-after-step-down.yml b/spec/spec_tests/data/sdam_unified/rediscover-quickly-after-step-down.yml new file mode 100644 index 0000000000..d0ed6e41d2 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/rediscover-quickly-after-step-down.yml @@ -0,0 +1,144 @@ +--- +description: rediscover-quickly-after-step-down + +schemaVersion: "1.10" + +runOnRequirements: + # 4.4 is required for streaming. + # A replica set is required for replSetStepDown. + - minServerVersion: "4.4" + serverless: forbid + topologies: + - replicaset + +createEntities: + - client: + id: &setupClient setupClient + - database: + id: &adminDatabase adminDatabase + client: *setupClient + databaseName: admin + +initialData: &initialData + - collectionName: &collectionName test-replSetStepDown + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Rediscover quickly after replSetStepDown + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - poolClearedEvent + - commandStartedEvent + uriOptions: + appname: replSetStepDownTest + # Configure a large heartbeatFrequencyMS + heartbeatFrequencyMS: 60000 + # Configure a much smaller server selection timeout so that the test + # will error when it cannot discover the new primary soon. + serverSelectionTimeoutMS: 5000 + w: majority + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Discover the primary. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + - name: recordTopologyDescription + object: testRunner + arguments: + client: *client + id: &topologyDescription topologyDescription + - name: assertTopologyType + object: testRunner + arguments: + topologyDescription: *topologyDescription + topologyType: ReplicaSetWithPrimary + # Unfreeze a secondary with replSetFreeze:0 to ensure a speedy election. + - name: runCommand + object: *adminDatabase + arguments: + command: + replSetFreeze: 0 + readPreference: + mode: Secondary + commandName: replSetFreeze + # Run replSetStepDown on the meta client. + - name: runCommand + object: *adminDatabase + arguments: + command: + replSetStepDown: 30 + secondaryCatchUpPeriodSecs: 30 + force: false + commandName: replSetStepDown + - name: waitForPrimaryChange + object: testRunner + arguments: + client: *client + priorTopologyDescription: *topologyDescription + # We use a relatively large timeout here to workaround slow + # elections on Windows, possibly caused by SERVER-48154. + timeoutMS: 15000 + # Rediscover the new primary. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 5 + - _id: 6 + # Assert that no pools were cleared. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 0 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: test-replSetStepDown + documents: + - _id: 3 + - _id: 4 + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: test-replSetStepDown + documents: + - _id: 5 + - _id: 6 + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 + - _id: 4 + - _id: 5 + - _id: 6 diff --git a/spec/spec_tests/sdam_integration_spec.rb b/spec/spec_tests/sdam_integration_spec.rb deleted file mode 100644 index 930864863d..0000000000 --- a/spec/spec_tests/sdam_integration_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'spec_helper' - -require 'runners/crud' -require 'runners/transactions' - -SDAM_INTEGRATION_TESTS = Dir.glob("#{CURRENT_PATH}/spec_tests/data/sdam_integration/*.yml").sort - -describe 'SDAM integration tests' do - require_no_multi_mongos - require_wired_tiger - - define_transactions_spec_tests(SDAM_INTEGRATION_TESTS) -end diff --git a/spec/spec_tests/sdam_unified_spec.rb b/spec/spec_tests/sdam_unified_spec.rb new file mode 100644 index 0000000000..7165ddfa52 --- /dev/null +++ b/spec/spec_tests/sdam_unified_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'spec_helper' + +require 'runners/unified' + +base = "#{CURRENT_PATH}/spec_tests/data/sdam_unified" +SDAM_UNIFIED_TESTS = Dir.glob("#{base}/**/*.yml").sort + +describe 'SDAM unified spec tests' do + define_unified_spec_tests(base, SDAM_UNIFIED_TESTS) +end From 48a300558d34bc7059ca9a5b424aea412f92563e Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 29 Aug 2022 14:00:38 +0200 Subject: [PATCH 026/198] RUBY-3085 Remove pre-OP_MSG wire protocols (#2600) --- lib/mongo/operation.rb | 4 +- lib/mongo/operation/aggregate.rb | 3 +- lib/mongo/operation/aggregate/command.rb | 55 ----- lib/mongo/operation/collections_info.rb | 18 +- .../operation/collections_info/command.rb | 48 ---- lib/mongo/operation/command.rb | 3 +- lib/mongo/operation/command/command.rb | 41 ---- lib/mongo/operation/count.rb | 3 +- lib/mongo/operation/count/command.rb | 47 ---- lib/mongo/operation/create.rb | 3 +- lib/mongo/operation/create/command.rb | 47 ---- lib/mongo/operation/create_index.rb | 3 +- lib/mongo/operation/create_index/command.rb | 61 ----- lib/mongo/operation/create_user.rb | 3 +- lib/mongo/operation/create_user/command.rb | 46 ---- lib/mongo/operation/delete.rb | 1 - lib/mongo/operation/delete/command.rb | 52 ----- lib/mongo/operation/distinct.rb | 3 +- lib/mongo/operation/distinct/command.rb | 47 ---- lib/mongo/operation/drop.rb | 3 +- lib/mongo/operation/drop/command.rb | 41 ---- lib/mongo/operation/drop_database.rb | 3 +- lib/mongo/operation/drop_database/command.rb | 41 ---- lib/mongo/operation/drop_index.rb | 3 +- lib/mongo/operation/drop_index/command.rb | 45 ---- lib/mongo/operation/explain.rb | 4 +- lib/mongo/operation/explain/command.rb | 58 ----- lib/mongo/operation/explain/legacy.rb | 52 ----- lib/mongo/operation/find.rb | 4 +- lib/mongo/operation/find/builder.rb | 1 - lib/mongo/operation/find/builder/legacy.rb | 123 ---------- lib/mongo/operation/find/command.rb | 51 ----- lib/mongo/operation/find/legacy.rb | 52 ----- lib/mongo/operation/find/legacy/result.rb | 46 ---- lib/mongo/operation/get_more.rb | 4 +- lib/mongo/operation/get_more/command.rb | 43 ---- lib/mongo/operation/get_more/legacy.rb | 39 ---- lib/mongo/operation/indexes.rb | 18 +- lib/mongo/operation/indexes/command.rb | 42 ---- lib/mongo/operation/indexes/legacy.rb | 48 ---- lib/mongo/operation/insert.rb | 1 - lib/mongo/operation/insert/command.rb | 55 ----- lib/mongo/operation/kill_cursors.rb | 3 +- lib/mongo/operation/kill_cursors/command.rb | 48 ---- lib/mongo/operation/list_collections.rb | 3 +- .../operation/list_collections/command.rb | 46 ---- lib/mongo/operation/map_reduce.rb | 3 +- lib/mongo/operation/map_reduce/command.rb | 51 ----- lib/mongo/operation/parallel_scan.rb | 3 +- lib/mongo/operation/parallel_scan/command.rb | 57 ----- lib/mongo/operation/remove_user.rb | 3 +- lib/mongo/operation/remove_user/command.rb | 46 ---- ...phic_operation.rb => op_msg_executable.rb} | 17 +- .../operation/shared/op_msg_or_command.rb | 41 ---- .../shared/op_msg_or_find_command.rb | 44 ---- lib/mongo/operation/update.rb | 1 - lib/mongo/operation/update/command.rb | 53 ----- lib/mongo/operation/update_user.rb | 3 +- lib/mongo/operation/update_user/command.rb | 45 ---- lib/mongo/operation/users_info.rb | 3 +- lib/mongo/operation/users_info/command.rb | 46 ---- lib/mongo/operation/write_command.rb | 3 +- lib/mongo/operation/write_command/command.rb | 51 ----- lib/mongo/protocol.rb | 3 - lib/mongo/protocol/delete.rb | 172 -------------- lib/mongo/protocol/insert.rb | 181 --------------- lib/mongo/protocol/update.rb | 214 ------------------ spec/integration/command_spec.rb | 24 +- spec/mongo/operation/delete/command_spec.rb | 115 ---------- spec/mongo/operation/find/legacy_spec.rb | 131 ----------- spec/mongo/operation/get_more_spec.rb | 63 ------ spec/mongo/operation/insert/command_spec.rb | 118 ---------- spec/mongo/operation/update/command_spec.rb | 122 ---------- spec/mongo/protocol/delete_spec.rb | 185 --------------- spec/mongo/protocol/insert_spec.rb | 179 --------------- spec/mongo/protocol/update_spec.rb | 204 ----------------- 76 files changed, 38 insertions(+), 3508 deletions(-) delete mode 100644 lib/mongo/operation/aggregate/command.rb delete mode 100644 lib/mongo/operation/collections_info/command.rb delete mode 100644 lib/mongo/operation/command/command.rb delete mode 100644 lib/mongo/operation/count/command.rb delete mode 100644 lib/mongo/operation/create/command.rb delete mode 100644 lib/mongo/operation/create_index/command.rb delete mode 100644 lib/mongo/operation/create_user/command.rb delete mode 100644 lib/mongo/operation/delete/command.rb delete mode 100644 lib/mongo/operation/distinct/command.rb delete mode 100644 lib/mongo/operation/drop/command.rb delete mode 100644 lib/mongo/operation/drop_database/command.rb delete mode 100644 lib/mongo/operation/drop_index/command.rb delete mode 100644 lib/mongo/operation/explain/command.rb delete mode 100644 lib/mongo/operation/explain/legacy.rb delete mode 100644 lib/mongo/operation/find/builder/legacy.rb delete mode 100644 lib/mongo/operation/find/command.rb delete mode 100644 lib/mongo/operation/find/legacy.rb delete mode 100644 lib/mongo/operation/find/legacy/result.rb delete mode 100644 lib/mongo/operation/get_more/command.rb delete mode 100644 lib/mongo/operation/get_more/legacy.rb delete mode 100644 lib/mongo/operation/indexes/command.rb delete mode 100644 lib/mongo/operation/indexes/legacy.rb delete mode 100644 lib/mongo/operation/insert/command.rb delete mode 100644 lib/mongo/operation/kill_cursors/command.rb delete mode 100644 lib/mongo/operation/list_collections/command.rb delete mode 100644 lib/mongo/operation/map_reduce/command.rb delete mode 100644 lib/mongo/operation/parallel_scan/command.rb delete mode 100644 lib/mongo/operation/remove_user/command.rb rename lib/mongo/operation/shared/{polymorphic_operation.rb => op_msg_executable.rb} (82%) delete mode 100644 lib/mongo/operation/shared/op_msg_or_command.rb delete mode 100644 lib/mongo/operation/shared/op_msg_or_find_command.rb delete mode 100644 lib/mongo/operation/update/command.rb delete mode 100644 lib/mongo/operation/update_user/command.rb delete mode 100644 lib/mongo/operation/users_info/command.rb delete mode 100644 lib/mongo/operation/write_command/command.rb delete mode 100644 lib/mongo/protocol/delete.rb delete mode 100644 lib/mongo/protocol/insert.rb delete mode 100644 lib/mongo/protocol/update.rb delete mode 100644 spec/mongo/operation/delete/command_spec.rb delete mode 100644 spec/mongo/operation/find/legacy_spec.rb delete mode 100644 spec/mongo/operation/get_more_spec.rb delete mode 100644 spec/mongo/operation/insert/command_spec.rb delete mode 100644 spec/mongo/operation/update/command_spec.rb delete mode 100644 spec/mongo/protocol/delete_spec.rb delete mode 100644 spec/mongo/protocol/insert_spec.rb delete mode 100644 spec/mongo/protocol/update_spec.rb diff --git a/lib/mongo/operation.rb b/lib/mongo/operation.rb index 1d345ad54c..2c494bffba 100644 --- a/lib/mongo/operation.rb +++ b/lib/mongo/operation.rb @@ -9,7 +9,6 @@ require 'mongo/operation/shared/executable_no_validate' require 'mongo/operation/shared/executable_transaction_label' require 'mongo/operation/shared/polymorphic_lookup' -require 'mongo/operation/shared/polymorphic_operation' require 'mongo/operation/shared/polymorphic_result' require 'mongo/operation/shared/read_preference_supported' require 'mongo/operation/shared/bypass_document_validation' @@ -22,8 +21,7 @@ require 'mongo/operation/shared/specifiable' require 'mongo/operation/shared/validatable' require 'mongo/operation/shared/object_id_generator' -require 'mongo/operation/shared/op_msg_or_command' -require 'mongo/operation/shared/op_msg_or_find_command' +require 'mongo/operation/shared/op_msg_executable' require 'mongo/operation/op_msg_base' require 'mongo/operation/command' diff --git a/lib/mongo/operation/aggregate.rb b/lib/mongo/operation/aggregate.rb index cef9575f3a..98084df678 100644 --- a/lib/mongo/operation/aggregate.rb +++ b/lib/mongo/operation/aggregate.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/aggregate/command' require 'mongo/operation/aggregate/op_msg' require 'mongo/operation/aggregate/result' @@ -33,7 +32,7 @@ module Operation # @since 2.0.0 class Aggregate include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/aggregate/command.rb b/lib/mongo/operation/aggregate/command.rb deleted file mode 100644 index 9239d2feaa..0000000000 --- a/lib/mongo/operation/aggregate/command.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Aggregate - - # A MongoDB aggregate operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include PolymorphicResult - include ReadPreferenceSupported - include WriteConcernSupported - include Limited - - private - - def selector(connection) - super.tap do |selector| - if selector[:collation] && !connection.features.collation_enabled? - raise Error::UnsupportedCollation - end - end - end - - def write_concern_supported?(connection) - connection.features.collation_enabled? - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/collections_info.rb b/lib/mongo/operation/collections_info.rb index 371ace3177..370334f2a7 100644 --- a/lib/mongo/operation/collections_info.rb +++ b/lib/mongo/operation/collections_info.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/collections_info/command' require 'mongo/operation/collections_info/result' module Mongo @@ -28,23 +27,12 @@ module Operation # @since 2.0.0 class CollectionsInfo include Specifiable - include PolymorphicOperation - include PolymorphicLookup + include OpMsgExecutable private - def final_operation(connection) - op_class = if connection.features.list_collections_enabled? - if connection.features.op_msg_enabled? - ListCollections::OpMsg - else - ListCollections::Command - end - else - CollectionsInfo::Command - end - - op_class.new(spec) + def final_operation + ListCollections::OpMsg.new(spec) end end end diff --git a/lib/mongo/operation/collections_info/command.rb b/lib/mongo/operation/collections_info/command.rb deleted file mode 100644 index fba3b1b886..0000000000 --- a/lib/mongo/operation/collections_info/command.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class CollectionsInfo - - # A MongoDB collectionInfo operation sent as a command message. - # - # @api private - class Command - include Specifiable - include Executable - include ReadPreferenceSupported - include PolymorphicResult - - private - - def get_result(connection, context, options = {}) - # This is a Mongo::Operation::CollectionsInfo::Result - Result.new(*dispatch_message(connection, context), db_name) - end - - def selector(connection) - {} - end - - def message(connection) - Protocol::Query.new(db_name, Database::NAMESPACES, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/command.rb b/lib/mongo/operation/command.rb index 2939247266..998f58899b 100644 --- a/lib/mongo/operation/command.rb +++ b/lib/mongo/operation/command.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/command/command' require 'mongo/operation/command/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.0.0 class Command include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/command/command.rb b/lib/mongo/operation/command/command.rb deleted file mode 100644 index 88e0ea444b..0000000000 --- a/lib/mongo/operation/command/command.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Command - - # A MongoDB command operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - - private - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/count.rb b/lib/mongo/operation/count.rb index 2ebc6c0e12..92601c8086 100644 --- a/lib/mongo/operation/count.rb +++ b/lib/mongo/operation/count.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/count/command' require 'mongo/operation/count/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.0.0 class Count include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/count/command.rb b/lib/mongo/operation/count/command.rb deleted file mode 100644 index a2d38016bd..0000000000 --- a/lib/mongo/operation/count/command.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Count - - # A MongoDB count operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - - private - - def selector(connection) - selector = spec[:selector] - selector = apply_collation(selector, connection, spec[:collation]) - selector - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/create.rb b/lib/mongo/operation/create.rb index e5371f940c..af266f268a 100644 --- a/lib/mongo/operation/create.rb +++ b/lib/mongo/operation/create.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/create/command' require 'mongo/operation/create/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.0.0 class Create include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/create/command.rb b/lib/mongo/operation/create/command.rb deleted file mode 100644 index 2cef393c9d..0000000000 --- a/lib/mongo/operation/create/command.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Create - - # A MongoDB create collection operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include WriteConcernSupported - - private - - def selector(connection) - selector = spec[:selector] - selector = apply_collation(selector, connection, spec[:collation]) - selector - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/create_index.rb b/lib/mongo/operation/create_index.rb index 0f4482118e..900f3025d1 100644 --- a/lib/mongo/operation/create_index.rb +++ b/lib/mongo/operation/create_index.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/create_index/command' require 'mongo/operation/create_index/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.0.0 class CreateIndex include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/create_index/command.rb b/lib/mongo/operation/create_index/command.rb deleted file mode 100644 index 5bbf6a8c5b..0000000000 --- a/lib/mongo/operation/create_index/command.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class CreateIndex - - # A MongoDB createindex operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include WriteConcernSupported - - private - - def selector(connection) - indexes.each do |index| - if index[:collation] && !connection.features.collation_enabled? - raise Error::UnsupportedCollation - end - end - - { - createIndexes: coll_name, - indexes: indexes, - }.tap do |selector| - if commit_quorum = spec[:commit_quorum] - unless connection.features.commit_quorum_enabled? - raise Error::UnsupportedOption.commit_quorum_error - end - selector[:commitQuorum] = commit_quorum - end - end - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/create_user.rb b/lib/mongo/operation/create_user.rb index d426dc7324..e27ec9d61e 100644 --- a/lib/mongo/operation/create_user.rb +++ b/lib/mongo/operation/create_user.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/create_user/command' require 'mongo/operation/create_user/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.0.0 class CreateUser include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/create_user/command.rb b/lib/mongo/operation/create_user/command.rb deleted file mode 100644 index 032cd6c629..0000000000 --- a/lib/mongo/operation/create_user/command.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class CreateUser - - # A MongoDB createuser operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include WriteConcernSupported - - private - - def selector(connection) - { :createUser => user.name }.merge(user.spec) - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/delete.rb b/lib/mongo/operation/delete.rb index df18238836..dc2c6f9e02 100644 --- a/lib/mongo/operation/delete.rb +++ b/lib/mongo/operation/delete.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/delete/command' require 'mongo/operation/delete/op_msg' require 'mongo/operation/delete/result' require 'mongo/operation/delete/bulk_result' diff --git a/lib/mongo/operation/delete/command.rb b/lib/mongo/operation/delete/command.rb deleted file mode 100644 index e1e3ea485d..0000000000 --- a/lib/mongo/operation/delete/command.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Delete - - # A MongoDB delete operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include WriteConcernSupported - include ExecutableNoValidate - include PolymorphicResult - include Validatable - - private - - def selector(connection) - { - delete: coll_name, - deletes: validate_updates(connection, send(IDENTIFIER)), - ordered: ordered?, - } - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/distinct.rb b/lib/mongo/operation/distinct.rb index 2653a74088..46995b4ef3 100644 --- a/lib/mongo/operation/distinct.rb +++ b/lib/mongo/operation/distinct.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/distinct/command' require 'mongo/operation/distinct/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.5.0 class Distinct include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/distinct/command.rb b/lib/mongo/operation/distinct/command.rb deleted file mode 100644 index 091ae0586c..0000000000 --- a/lib/mongo/operation/distinct/command.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Distinct - - # A MongoDB distinct operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - - private - - def selector(connection) - selector = spec[:selector] - selector = apply_collation(selector, connection, spec[:collation]) - selector - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/drop.rb b/lib/mongo/operation/drop.rb index b7467a5ba3..b5b29a26b3 100644 --- a/lib/mongo/operation/drop.rb +++ b/lib/mongo/operation/drop.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/drop/command' require 'mongo/operation/drop/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.4.0 class Drop include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/drop/command.rb b/lib/mongo/operation/drop/command.rb deleted file mode 100644 index 862bb2ff97..0000000000 --- a/lib/mongo/operation/drop/command.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Drop - - # A MongoDB drop collection operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include WriteConcernSupported - - private - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/drop_database.rb b/lib/mongo/operation/drop_database.rb index be75a27fb5..db00a738c3 100644 --- a/lib/mongo/operation/drop_database.rb +++ b/lib/mongo/operation/drop_database.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/drop_database/command' require 'mongo/operation/drop_database/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.4.0 class DropDatabase include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/drop_database/command.rb b/lib/mongo/operation/drop_database/command.rb deleted file mode 100644 index 983090af79..0000000000 --- a/lib/mongo/operation/drop_database/command.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class DropDatabase - - # A MongoDB dropdatabase operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include WriteConcernSupported - - private - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/drop_index.rb b/lib/mongo/operation/drop_index.rb index ae70369525..493a911f38 100644 --- a/lib/mongo/operation/drop_index.rb +++ b/lib/mongo/operation/drop_index.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/drop_index/command' require 'mongo/operation/drop_index/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.0.0 class DropIndex include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/drop_index/command.rb b/lib/mongo/operation/drop_index/command.rb deleted file mode 100644 index f78c634f8b..0000000000 --- a/lib/mongo/operation/drop_index/command.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class DropIndex - - # A MongoDB dropindex operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include WriteConcernSupported - - private - - def selector(connection) - { :dropIndexes => coll_name, :index => index_name } - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/explain.rb b/lib/mongo/operation/explain.rb index e4757e19cc..50a61f2fcf 100644 --- a/lib/mongo/operation/explain.rb +++ b/lib/mongo/operation/explain.rb @@ -17,8 +17,6 @@ require 'mongo/operation/explain/result' require 'mongo/operation/explain/op_msg' -require 'mongo/operation/explain/command' -require 'mongo/operation/explain/legacy' module Mongo module Operation @@ -30,7 +28,7 @@ module Operation # @since 2.5.0 class Explain include Specifiable - include OpMsgOrFindCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/explain/command.rb b/lib/mongo/operation/explain/command.rb deleted file mode 100644 index 05c2783be8..0000000000 --- a/lib/mongo/operation/explain/command.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Explain - - # A MongoDB explain command operation sent as a command message. - # - # @api private - # - # @since 2.0.0 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include PolymorphicResult - - private - - def selector(connection) - # The mappings are BSON::Documents and as such store keys as - # strings, the spec here has symbol keys. - spec = BSON::Document.new(self.spec) - - if spec[:collation] && !connection.features.collation_enabled? - raise Error::UnsupportedCollation - end - - { - explain: { - find: coll_name, - }.update(Find::Builder::Command.selector(spec, connection)), - }.update(spec[:explain] || {}) - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/explain/legacy.rb b/lib/mongo/operation/explain/legacy.rb deleted file mode 100644 index cd54bc6b2f..0000000000 --- a/lib/mongo/operation/explain/legacy.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2015-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Explain - - # A MongoDB explain command operation sent as a legacy wire protocol message. - # - # @api private - # - # @since 2.0.0 - class Legacy - include Specifiable - include Executable - include ReadPreferenceSupported - include PolymorphicResult - - private - - def message(connection) - if spec[:collation] && !connection.features.collation_enabled? - raise Error::UnsupportedCollation - end - - Protocol::Query.new( - db_name, - coll_name, - Find::Builder::Legacy.selector(spec, connection), - options(connection).update( - Find::Builder::Legacy.query_options(spec, connection), - ), - ) - end - end - end - end -end diff --git a/lib/mongo/operation/find.rb b/lib/mongo/operation/find.rb index a4528c015a..533a6e00a6 100644 --- a/lib/mongo/operation/find.rb +++ b/lib/mongo/operation/find.rb @@ -15,9 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/find/command' require 'mongo/operation/find/op_msg' -require 'mongo/operation/find/legacy' require 'mongo/operation/find/result' require 'mongo/operation/find/builder' @@ -31,7 +29,7 @@ module Operation # @since 2.0.0 class Find include Specifiable - include OpMsgOrFindCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/find/builder.rb b/lib/mongo/operation/find/builder.rb index c1d8b1fea6..552a93d35e 100644 --- a/lib/mongo/operation/find/builder.rb +++ b/lib/mongo/operation/find/builder.rb @@ -17,5 +17,4 @@ require 'mongo/operation/find/builder/command' require 'mongo/operation/find/builder/flags' -require 'mongo/operation/find/builder/legacy' require 'mongo/operation/find/builder/modifiers' diff --git a/lib/mongo/operation/find/builder/legacy.rb b/lib/mongo/operation/find/builder/legacy.rb deleted file mode 100644 index 47a2e59ad1..0000000000 --- a/lib/mongo/operation/find/builder/legacy.rb +++ /dev/null @@ -1,123 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2015-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Find - module Builder - - # Builds a legacy OP_QUERY specification from options. - # - # @api private - module Legacy - - # Mappings from driver options to legacy server values. - # - # @since 2.2.0 - DRIVER_MAPPINGS = { - comment: '$comment', - explain: '$explain', - hint: '$hint', - max_scan: '$maxScan', - max_time_ms: '$maxTimeMS', - max_value: '$max', - min_value: '$min', - show_disk_loc: '$showDiskLoc', - snapshot: '$snapshot', - sort: '$orderby', - return_key: '$returnKey', - }.freeze - - module_function def selector(spec, connection) - if Lint.enabled? - if spec.keys.any? { |k| String === k } - raise Error::LintError, "The spec must contain symbol keys only" - end - end - - # Server versions that do not have the find command feature - # (versions older than 3.2) do not support the allow_disk_use option - # but perform no validation and will not raise an error if it is - # specified. If the allow_disk_use option is specified, raise an error - # to alert the user. - unless spec[:allow_disk_use].nil? - raise Error::UnsupportedOption.allow_disk_use_error - end - - if spec[:collation] && !connection.features.collation_enabled? - raise Error::UnsupportedCollation - end - - modifiers = {} - DRIVER_MAPPINGS.each do |k, server_k| - unless (value = spec[k]).nil? - modifiers[server_k] = value - end - end - - selector = spec[:filter] || BSON::Document.new - # Write nil into rp if not talking to mongos, rather than false - rp = if connection.description.mongos? - read_pref_formatted(spec) - end - if modifiers.any? || rp - selector = {'$query' => selector}.update(modifiers) - - if rp - selector['$readPreference'] = rp - end - end - - selector - end - - module_function def query_options(spec, connection) - query_options = { - project: spec[:projection], - skip: spec[:skip], - limit: spec[:limit], - # batch_size is converted to batchSize by Mongo::Protocol::Query. - batch_size: spec[:batch_size], - } - - unless (flags = Builder::Flags.map_flags(spec)).empty? - query_options[:flags] = ((query_options[:flags] || []) + flags).uniq - end - - query_options - end - - private - - module_function def read_pref_formatted(spec) - if spec[:read_preference] - raise ArgumentError, "Spec cannot include :read_preference here, use :read" - end - - if read = spec[:read] - read_pref = ServerSelector.get(read).to_mongos - Mongo::Lint.validate_camel_case_read_preference(read_pref) - read_pref - else - nil - end - end - end - end - end - end -end diff --git a/lib/mongo/operation/find/command.rb b/lib/mongo/operation/find/command.rb deleted file mode 100644 index 181b557c10..0000000000 --- a/lib/mongo/operation/find/command.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Find - - # A MongoDB find operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include PolymorphicResult - - private - - def selector(connection) - # The mappings are BSON::Documents and as such store keys as - # strings, the spec here has symbol keys - spec = BSON::Document.new(self.spec) - { - find: coll_name, - }.update(Find::Builder::Command.selector(spec, connection)) - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/find/legacy.rb b/lib/mongo/operation/find/legacy.rb deleted file mode 100644 index 33989d1b8d..0000000000 --- a/lib/mongo/operation/find/legacy.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'mongo/operation/find/legacy/result' - -module Mongo - module Operation - class Find - - # A MongoDB find operation sent as a legacy wire protocol message. - # - # @api private - # - # @since 2.5.2 - class Legacy - include Specifiable - include Executable - include ReadPreferenceSupported - include PolymorphicResult - - private - - def message(connection) - selector = Find::Builder::Legacy.selector(spec, connection) - options = options(connection).update( - Find::Builder::Legacy.query_options(spec, connection), - ) - Protocol::Query.new( - db_name, - coll_name, - selector, - options, - ) - end - end - end - end -end diff --git a/lib/mongo/operation/find/legacy/result.rb b/lib/mongo/operation/find/legacy/result.rb deleted file mode 100644 index d4d0068661..0000000000 --- a/lib/mongo/operation/find/legacy/result.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2014-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Find - class Legacy - - # Defines custom behavior of results for a query. - # - # @since 2.1.0 - # @api semiprivate - class Result < Operation::Result - include Operation::Result::UseLegacyErrorParser - - # Determine if the query was a success. - # - # @example Was the query successful? - # result.successful? - # - # @return [ true, false ] If the query was successful. - # - # @since 2.0.0 - # @api public - def successful? - !query_failure? - end - end - end - end - end -end diff --git a/lib/mongo/operation/get_more.rb b/lib/mongo/operation/get_more.rb index 455f8f6394..b7ae7ae5c7 100644 --- a/lib/mongo/operation/get_more.rb +++ b/lib/mongo/operation/get_more.rb @@ -16,9 +16,7 @@ # limitations under the License. require 'mongo/operation/get_more/command_builder' -require 'mongo/operation/get_more/command' require 'mongo/operation/get_more/op_msg' -require 'mongo/operation/get_more/legacy' require 'mongo/operation/get_more/result' module Mongo @@ -31,7 +29,7 @@ module Operation # @since 2.5.0 class GetMore include Specifiable - include OpMsgOrFindCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/get_more/command.rb b/lib/mongo/operation/get_more/command.rb deleted file mode 100644 index 9188d20e10..0000000000 --- a/lib/mongo/operation/get_more/command.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class GetMore - - # A MongoDB getMore operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include PolymorphicResult - include CommandBuilder - - private - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/get_more/legacy.rb b/lib/mongo/operation/get_more/legacy.rb deleted file mode 100644 index e3ac61485f..0000000000 --- a/lib/mongo/operation/get_more/legacy.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class GetMore - - # A MongoDB getMore operation sent as a legacy wire protocol message. - # - # @api private - # - # @since 2.5.2 - class Legacy - include Specifiable - include Executable - - private - - def message(connection) - Protocol::GetMore.new(db_name, coll_name, to_return, cursor_id) - end - end - end - end -end diff --git a/lib/mongo/operation/indexes.rb b/lib/mongo/operation/indexes.rb index e1ead670f1..779bee85aa 100644 --- a/lib/mongo/operation/indexes.rb +++ b/lib/mongo/operation/indexes.rb @@ -15,9 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/indexes/command' require 'mongo/operation/indexes/op_msg' -require 'mongo/operation/indexes/legacy' require 'mongo/operation/indexes/result' module Mongo @@ -30,21 +28,7 @@ module Operation # @since 2.0.0 class Indexes include Specifiable - include PolymorphicOperation - include PolymorphicLookup - - private - - def final_operation(connection) - cls = if connection.features.op_msg_enabled? - polymorphic_class(self.class.name, :OpMsg) - elsif connection.features.list_indexes_enabled? - polymorphic_class(self.class.name, :Command) - else - polymorphic_class(self.class.name, :Legacy) - end - cls.new(spec) - end + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/indexes/command.rb b/lib/mongo/operation/indexes/command.rb deleted file mode 100644 index 0fdf19ee20..0000000000 --- a/lib/mongo/operation/indexes/command.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Indexes - - # A MongoDB indexes operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include PolymorphicResult - - private - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/indexes/legacy.rb b/lib/mongo/operation/indexes/legacy.rb deleted file mode 100644 index 27ef213f0f..0000000000 --- a/lib/mongo/operation/indexes/legacy.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2014-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Indexes - - # A MongoDB indexes operation sent as a legacy wire protocol message. - # - # @api private - # - # @since 2.5.2 - class Legacy - include Specifiable - include Executable - include ReadPreferenceSupported - - private - - def selector(connection) - { ns: namespace } - end - - def message(connection) - Protocol::Query.new(db_name, Index::COLLECTION, command(connection), options(connection)) - end - - def result_class - Operation::Result - end - end - end - end -end diff --git a/lib/mongo/operation/insert.rb b/lib/mongo/operation/insert.rb index e9df182ccd..948351e5b0 100644 --- a/lib/mongo/operation/insert.rb +++ b/lib/mongo/operation/insert.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/insert/command' require 'mongo/operation/insert/op_msg' require 'mongo/operation/insert/result' require 'mongo/operation/insert/bulk_result' diff --git a/lib/mongo/operation/insert/command.rb b/lib/mongo/operation/insert/command.rb deleted file mode 100644 index c403d7c80c..0000000000 --- a/lib/mongo/operation/insert/command.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Insert - - # A MongoDB insert operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include ExecutableNoValidate - include Idable - include Limited - include WriteConcernSupported - include BypassDocumentValidation - - private - - def get_result(connection, context, options = {}) - # This is a Mongo::Operation::Insert::Result - Result.new(*dispatch_message(connection, context), @ids) - end - - def selector(connection) - { insert: coll_name, - documents: send(IDENTIFIER), - ordered: ordered? } - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/kill_cursors.rb b/lib/mongo/operation/kill_cursors.rb index fef0cb0597..17c5b1742c 100644 --- a/lib/mongo/operation/kill_cursors.rb +++ b/lib/mongo/operation/kill_cursors.rb @@ -16,7 +16,6 @@ # limitations under the License. require 'mongo/operation/kill_cursors/command_builder' -require 'mongo/operation/kill_cursors/command' require 'mongo/operation/kill_cursors/op_msg' module Mongo @@ -29,7 +28,7 @@ module Operation # @since 2.0.0 class KillCursors include Specifiable - include OpMsgOrFindCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/kill_cursors/command.rb b/lib/mongo/operation/kill_cursors/command.rb deleted file mode 100644 index 2e12d3ef69..0000000000 --- a/lib/mongo/operation/kill_cursors/command.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class KillCursors - - # A MongoDB killcursors operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include CommandBuilder - - private - - def selector(connection) - { - killCursors: coll_name, - cursors: int64_cursor_ids, - } - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, selector(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/list_collections.rb b/lib/mongo/operation/list_collections.rb index 8e50d8884d..9a2b81c440 100644 --- a/lib/mongo/operation/list_collections.rb +++ b/lib/mongo/operation/list_collections.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/list_collections/command' require 'mongo/operation/list_collections/op_msg' require 'mongo/operation/list_collections/result' @@ -29,7 +28,7 @@ module Operation # @since 2.0.0 class ListCollections include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/list_collections/command.rb b/lib/mongo/operation/list_collections/command.rb deleted file mode 100644 index de89b72617..0000000000 --- a/lib/mongo/operation/list_collections/command.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class ListCollections - - # A MongoDB listcollections operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include PolymorphicResult - - private - - def selector(connection) - (spec[SELECTOR] || {}).merge(listCollections: 1) - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/map_reduce.rb b/lib/mongo/operation/map_reduce.rb index d77b37a076..90f2af74f6 100644 --- a/lib/mongo/operation/map_reduce.rb +++ b/lib/mongo/operation/map_reduce.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/map_reduce/command' require 'mongo/operation/map_reduce/op_msg' require 'mongo/operation/map_reduce/result' @@ -29,7 +28,7 @@ module Operation # @since 2.5.0 class MapReduce include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/map_reduce/command.rb b/lib/mongo/operation/map_reduce/command.rb deleted file mode 100644 index 116dc64601..0000000000 --- a/lib/mongo/operation/map_reduce/command.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class MapReduce - - # A MongoDB mapreduce operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include WriteConcernSupported - include PolymorphicResult - - private - - def selector(connection) - super.tap do |selector| - if selector[:collation] && !connection.features.collation_enabled? - raise Error::UnsupportedCollation - end - end - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/parallel_scan.rb b/lib/mongo/operation/parallel_scan.rb index a435bdc922..3e9aea4106 100644 --- a/lib/mongo/operation/parallel_scan.rb +++ b/lib/mongo/operation/parallel_scan.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/parallel_scan/command' require 'mongo/operation/parallel_scan/op_msg' require 'mongo/operation/parallel_scan/result' @@ -29,7 +28,7 @@ module Operation # @since 2.0.0 class ParallelScan include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/parallel_scan/command.rb b/lib/mongo/operation/parallel_scan/command.rb deleted file mode 100644 index c6b36b8dc9..0000000000 --- a/lib/mongo/operation/parallel_scan/command.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class ParallelScan - - # A MongoDB parallelscan operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include PolymorphicResult - - private - - def selector(connection) - sel = { :parallelCollectionScan => coll_name, :numCursors => cursor_count } - if read_concern - sel[:readConcern] = Options::Mapper.transform_values_to_strings( - read_concern) - end - sel[:maxTimeMS] = max_time_ms if max_time_ms - add_read_preference_legacy(sel, connection) - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end - - - - - diff --git a/lib/mongo/operation/remove_user.rb b/lib/mongo/operation/remove_user.rb index f17358c918..e704a02ed8 100644 --- a/lib/mongo/operation/remove_user.rb +++ b/lib/mongo/operation/remove_user.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/remove_user/command' require 'mongo/operation/remove_user/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.0.0 class RemoveUser include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/remove_user/command.rb b/lib/mongo/operation/remove_user/command.rb deleted file mode 100644 index 121078223e..0000000000 --- a/lib/mongo/operation/remove_user/command.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class RemoveUser - - # A MongoDB removeuser operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include WriteConcernSupported - - private - - def selector(connection) - { :dropUser => user_name } - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/shared/polymorphic_operation.rb b/lib/mongo/operation/shared/op_msg_executable.rb similarity index 82% rename from lib/mongo/operation/shared/polymorphic_operation.rb rename to lib/mongo/operation/shared/op_msg_executable.rb index 1786bd4c4d..345e905f13 100644 --- a/lib/mongo/operation/shared/polymorphic_operation.rb +++ b/lib/mongo/operation/shared/op_msg_executable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # encoding: utf-8 -# Copyright (C) 2021 MongoDB Inc. +# Copyright (C) 2018-2020 MongoDB Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,11 +18,11 @@ module Mongo module Operation - # Shared behavior of implementing an operation differently based on - # the server that will be executing the operation. + # Shared behavior of executing the operation as an OpMsg. # # @api private - module PolymorphicOperation + module OpMsgExecutable + include PolymorphicLookup # Execute the operation. # @@ -46,8 +46,13 @@ def execute(server, context:, options: {}) # # @return [ Mongo::Operation::Result ] The operation result. def execute_with_connection(connection, context:, options: {}) - operation = final_operation(connection) - operation.execute(connection, context: context, options: options) + final_operation.execute(connection, context: context, options: options) + end + + private + + def final_operation + polymorphic_class(self.class.name, :OpMsg).new(spec) end end end diff --git a/lib/mongo/operation/shared/op_msg_or_command.rb b/lib/mongo/operation/shared/op_msg_or_command.rb deleted file mode 100644 index 6716237e53..0000000000 --- a/lib/mongo/operation/shared/op_msg_or_command.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - - # Shared behavior of executing the operation as an OpMsg when supported - # or as a Command otherwise. - # - # @api private - module OpMsgOrCommand - include PolymorphicOperation - include PolymorphicLookup - - private - - def final_operation(connection) - cls = if connection.features.op_msg_enabled? - polymorphic_class(self.class.name, :OpMsg) - else - polymorphic_class(self.class.name, :Command) - end - cls.new(spec) - end - end - end -end diff --git a/lib/mongo/operation/shared/op_msg_or_find_command.rb b/lib/mongo/operation/shared/op_msg_or_find_command.rb deleted file mode 100644 index 6084d299ba..0000000000 --- a/lib/mongo/operation/shared/op_msg_or_find_command.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - - # Shared behavior of executing the operation as an OpMsg when supported, - # as a Command when find command is supported by the server, and as - # Legacy otherwise. - # - # @api private - module OpMsgOrFindCommand - include PolymorphicOperation - include PolymorphicLookup - - private - - def final_operation(connection) - cls = if connection.features.op_msg_enabled? - polymorphic_class(self.class.name, :OpMsg) - elsif connection.features.find_command_enabled? - polymorphic_class(self.class.name, :Command) - else - polymorphic_class(self.class.name, :Legacy) - end - cls.new(spec) - end - end - end -end diff --git a/lib/mongo/operation/update.rb b/lib/mongo/operation/update.rb index c97db17aa5..9b4aa12602 100644 --- a/lib/mongo/operation/update.rb +++ b/lib/mongo/operation/update.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/update/command' require 'mongo/operation/update/op_msg' require 'mongo/operation/update/result' require 'mongo/operation/update/bulk_result' diff --git a/lib/mongo/operation/update/command.rb b/lib/mongo/operation/update/command.rb deleted file mode 100644 index 8e02bbbfd2..0000000000 --- a/lib/mongo/operation/update/command.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class Update - - # A MongoDB update operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include WriteConcernSupported - include BypassDocumentValidation - include ExecutableNoValidate - include PolymorphicResult - include Validatable - - private - - def selector(connection) - { - update: coll_name, - updates: validate_updates(connection, send(IDENTIFIER)), - ordered: ordered?, - } - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/update_user.rb b/lib/mongo/operation/update_user.rb index 498db1a9dc..598f1f1552 100644 --- a/lib/mongo/operation/update_user.rb +++ b/lib/mongo/operation/update_user.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/update_user/command' require 'mongo/operation/update_user/op_msg' module Mongo @@ -28,7 +27,7 @@ module Operation # @since 2.0.0 class UpdateUser include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/update_user/command.rb b/lib/mongo/operation/update_user/command.rb deleted file mode 100644 index 874c9837ca..0000000000 --- a/lib/mongo/operation/update_user/command.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class UpdateUser - - # A MongoDB updateuser operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include WriteConcernSupported - - private - - def selector(connection) - { :updateUser => user.name }.merge(user.spec) - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/users_info.rb b/lib/mongo/operation/users_info.rb index 71cae7f82a..3a1e63422c 100644 --- a/lib/mongo/operation/users_info.rb +++ b/lib/mongo/operation/users_info.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/users_info/command' require 'mongo/operation/users_info/op_msg' require 'mongo/operation/users_info/result' @@ -29,7 +28,7 @@ module Operation # @since 2.0.0 class UsersInfo include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/users_info/command.rb b/lib/mongo/operation/users_info/command.rb deleted file mode 100644 index 9da27cca65..0000000000 --- a/lib/mongo/operation/users_info/command.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2018-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class UsersInfo - - # A MongoDB usersinfo operation sent as a command message. - # - # @api private - # - # @since 2.5.2 - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include PolymorphicResult - - private - - def selector(connection) - { :usersInfo => user_name } - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/operation/write_command.rb b/lib/mongo/operation/write_command.rb index 3d882f6157..6e322e4d94 100644 --- a/lib/mongo/operation/write_command.rb +++ b/lib/mongo/operation/write_command.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'mongo/operation/write_command/command' require 'mongo/operation/write_command/op_msg' module Mongo @@ -26,7 +25,7 @@ module Operation # @api private class WriteCommand include Specifiable - include OpMsgOrCommand + include OpMsgExecutable end end end diff --git a/lib/mongo/operation/write_command/command.rb b/lib/mongo/operation/write_command/command.rb deleted file mode 100644 index b97e47d8f1..0000000000 --- a/lib/mongo/operation/write_command/command.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2021 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Operation - class WriteCommand - - # A MongoDB write command operation sent as a command message. - # - # @api private - class Command - include Specifiable - include Executable - include Limited - include ReadPreferenceSupported - include Validatable - - private - - def selector(connection) - super.tap do |selector| - if selector.key?(:findAndModify) - validate_find_options(connection, selector) - end - if wc = spec[:write_concern] - selector[:writeConcern] = wc.options - end - end - end - - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end - end - end - end -end diff --git a/lib/mongo/protocol.rb b/lib/mongo/protocol.rb index b09097f3cd..4289a38835 100644 --- a/lib/mongo/protocol.rb +++ b/lib/mongo/protocol.rb @@ -10,12 +10,9 @@ # Client Requests require 'mongo/protocol/compressed' -require 'mongo/protocol/delete' require 'mongo/protocol/get_more' -require 'mongo/protocol/insert' require 'mongo/protocol/kill_cursors' require 'mongo/protocol/query' -require 'mongo/protocol/update' require 'mongo/protocol/msg' # Server Responses diff --git a/lib/mongo/protocol/delete.rb b/lib/mongo/protocol/delete.rb deleted file mode 100644 index 67478395cb..0000000000 --- a/lib/mongo/protocol/delete.rb +++ /dev/null @@ -1,172 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2014-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Protocol - - # MongoDB Wire protocol Delete message. - # - # This is a client request message that is sent to the server in order - # to delete selected documents in the specified namespace. - # - # The operation, by default, operates on many documents. Setting - # the +:single_remove+ flag allows for a single matching document - # to be removed. - # - # @api semipublic - class Delete < Message - - # Creates a new Delete message - # - # @example Remove all users named Tyler. - # Query.new('xgen', 'users', {:name => 'Tyler'}) - # - # @param database [String, Symbol] The database to remove from. - # @param collection [String, Symbol] The collection to remove from. - # @param selector [Hash] The query used to select doc(s) to remove. - # @param options [Hash] The additional delete options. - # - # @option options :flags [Array] The flags for the delete message. - # - # Supported flags: +:single_remove+ - def initialize(database, collection, selector, options = {}) - @database = database - @namespace = "#{database}.#{collection}" - @selector = selector - @flags = options[:flags] || [] - @upconverter = Upconverter.new(collection, selector, options) - super - end - - # Return the event payload for monitoring. - # - # @example Return the event payload. - # message.payload - # - # @return [ BSON::Document ] The event payload. - # - # @since 2.1.0 - def payload - BSON::Document.new( - command_name: 'delete', - database_name: @database, - command: upconverter.command, - request_id: request_id - ) - end - - protected - - attr_reader :upconverter - - private - - # The operation code required to specify a Delete message. - # @return [Fixnum] the operation code. - # - # @since 2.5.0 - OP_CODE = 2006 - - # Available flags for a Delete message. - FLAGS = [:single_remove] - - # Field representing Zero encoded as an Int32. - field :zero, Zero - - # @!attribute - # @return [String] The namespace for this Delete message. - field :namespace, CString - - # @!attribute - # @return [Array] The flags for this Delete message. - field :flags, BitVector.new(FLAGS) - - # @!attribute - # @return [Hash] The selector for this Delete message. - field :selector, Document - - # Converts legacy delete messages to the appropriare OP_COMMAND style - # message. - # - # @since 2.1.0 - class Upconverter - - # The delete command constant. - # - # @since 2.2.0 - DELETE = 'delete'.freeze - - # The deletes command constant. - # - # @since 2.2.0 - DELETES = 'deletes'.freeze - - # @return [ String ] collection The name of the collection. - attr_reader :collection - - # @return [ BSON::Document, Hash ] filter The query filter or command. - attr_reader :filter - - # @return [ Hash ] options The options. - attr_reader :options - - # Instantiate the upconverter. - # - # @example Instantiate the upconverter. - # Upconverter.new('users', { name: 'test' }) - # - # @param [ String ] collection The name of the collection. - # @param [ BSON::Document, Hash ] filter The filter or command. - # - # @since 2.1.0 - def initialize(collection, filter, options) - @collection = collection - @filter = filter - @options = options - end - - # Get the upconverted command. - # - # @example Get the command. - # upconverter.command - # - # @return [ BSON::Document ] The upconverted command. - # - # @since 2.1.0 - def command - document = BSON::Document.new - document.store(DELETE, collection) - document.store(DELETES, [ BSON::Document.new(Message::Q => filter, Message::LIMIT => limit) ]) - document.store(Message::ORDERED, true) - document - end - - private - - def limit - if options.key?(:flags) - options[:flags].include?(:single_remove) ? 1 : 0 - else - 0 - end - end - end - - Registry.register(OP_CODE, self) - end - end -end diff --git a/lib/mongo/protocol/insert.rb b/lib/mongo/protocol/insert.rb deleted file mode 100644 index 6b02daf84b..0000000000 --- a/lib/mongo/protocol/insert.rb +++ /dev/null @@ -1,181 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2014-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Protocol - - # MongoDB Wire protocol Insert message. - # - # This is a client request message that is sent to the server in order - # to insert documents within a namespace. - # - # The operation only has one flag +:continue_on_error+ which the user - # can use to instruct the database server to continue processing a bulk - # insertion if one happens to fail (e.g. due to duplicate IDs). This makes - # builk insert behave similarly to a seires of single inserts, except - # lastError will be set if any insert fails, not just the last one. - # - # If multiple errors occur, only the most recent will be reported by the - # getLastError mechanism. - # - # @api semipublic - class Insert < Message - - # Creates a new Insert message - # - # @example Insert a user document - # Insert.new('xgen', 'users', [{:name => 'Tyler'}]) - # - # @example Insert serveral user documents and continue on errors - # Insert.new('xgen', 'users', users, :flags => [:continue_on_error]) - # - # @param database [String, Symbol] The database to insert into. - # @param collection [String, Symbol] The collection to insert into. - # @param documents [Array] The documents to insert. - # @param options [Hash] Additional options for the insertion. - # - # @option options :flags [Array] The flags for the insertion message. - # Supported flags: +:continue_on_error+ - # @option options [ true, false ] validating_keys Whether keys should be - # validated for being valid document keys (i.e. not begin with $ and - # not contain dots). - def initialize(database, collection, documents, options = {}) - @database = database - @namespace = "#{database}.#{collection}" - @documents = documents - @flags = options[:flags] || [] - @upconverter = Upconverter.new(collection, documents, options) - @options = options - super - end - - # Return the event payload for monitoring. - # - # @example Return the event payload. - # message.payload - # - # @return [ BSON::Document ] The event payload. - # - # @since 2.1.0 - def payload - BSON::Document.new( - command_name: 'insert', - database_name: @database, - command: upconverter.command, - request_id: request_id - ) - end - - protected - - attr_reader :upconverter - - private - - def validating_keys? - @options.fetch(:validating_keys, true) - end - - # The operation code required to specify an Insert message. - # @return [Fixnum] the operation code. - # - # @since 2.5.0 - OP_CODE = 2002 - - # Available flags for an Insert message. - FLAGS = [:continue_on_error] - - # @!attribute - # @return [Array] The flags for this Insert message. - field :flags, BitVector.new(FLAGS) - - # @!attribute - # @return [String] The namespace for this Insert message. - field :namespace, CString - - # @!attribute - # @return [Array] The documents to insert. - field :documents, Document, true - - # Converts legacy insert messages to the appropriare OP_COMMAND style - # message. - # - # @since 2.1.0 - class Upconverter - - # Insert field constant. - # - # @since 2.1.0 - INSERT = 'insert'.freeze - - # Documents field constant. - # - # @since 2.1.0 - DOCUMENTS = 'documents'.freeze - - # Write concern field constant. - # - # @since 2.1.0 - WRITE_CONCERN = 'writeConcern'.freeze - - # @return [ String ] collection The name of the collection. - attr_reader :collection - - # @return [ Array ] documents The documents to insert. - attr_reader :documents - - # @return [ Hash ] options The options. - attr_reader :options - - # Instantiate the upconverter. - # - # @example Instantiate the upconverter. - # Upconverter.new('users', documents) - # - # @param [ String ] collection The name of the collection. - # @param [ Array ] documents The documents. - # @param [ Hash ] options The options. - # - # @since 2.1.0 - def initialize(collection, documents, options) - @collection = collection - @documents = documents - @options = options - end - - # Get the upconverted command. - # - # @example Get the command. - # upconverter.command - # - # @return [ BSON::Document ] The upconverted command. - # - # @since 2.1.0 - def command - document = BSON::Document.new - document.store(INSERT, collection) - document.store(DOCUMENTS, documents) - document.store(Message::ORDERED, options.fetch(:ordered, true)) - document.merge!(WRITE_CONCERN => options[:write_concern].options) if options[:write_concern] - document - end - end - - Registry.register(OP_CODE, self) - end - end -end diff --git a/lib/mongo/protocol/update.rb b/lib/mongo/protocol/update.rb deleted file mode 100644 index f74c0208af..0000000000 --- a/lib/mongo/protocol/update.rb +++ /dev/null @@ -1,214 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -# Copyright (C) 2014-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Protocol - - # MongoDB Wire protocol Update message. - # - # This is a client request message that is sent to the server in order - # to update documents matching the provided query. - # - # The default is to update a single document. In order to update many at - # a time users should set the +:multi_update+ flag for the update. - # - # If an upsert (update or insert) is desired, users can set the +:upsert+ - # flag in order to indicate they would like to insert the merged selector - # and update if no document matching the update query currently exists. - # - # @api semipublic - class Update < Message - - # Creates a new Update message - # - # @example Update single document - # Update.new('xgen', 'users', {:name => 'Tyler'}, {:name => 'Bob'}) - # - # @example Perform a multi update - # Update.new('xgen', 'users', - # {:age => 20}, {:age => 21}, :flags => [:multi_update]) - # - # @example Perform an upsert - # Update.new('xgen', 'users', {:name => 'Tyler'}, :flags => [:upsert]) - # - # @param database [String, Symbol] The database to update. - # @param collection [String, Symbol] The collection to update. - # @param selector [Hash] The update selector. - # @param update [Hash] The update to perform. - # @param options [Hash] The additional query options. - # - # @option options :flags [Array] The flags for the update message. - # - # Supported flags: +:upsert+, +:multi_update+ - def initialize(database, collection, selector, update, options = {}) - @database = database - @collection = collection - @namespace = "#{database}.#{collection}" - @selector = selector - @update = update - @flags = options[:flags] || [] - @upconverter = Upconverter.new(collection, selector, update, flags) - super - end - - # Return the event payload for monitoring. - # - # @example Return the event payload. - # message.payload - # - # @return [ BSON::Document ] The event payload. - # - # @since 2.1.0 - def payload - BSON::Document.new( - command_name: 'update', - database_name: @database, - command: upconverter.command, - request_id: request_id - ) - end - - protected - - attr_reader :upconverter - - private - - # The operation code required to specify an Update message. - # @return [Fixnum] the operation code. - # - # @since 2.5.0 - OP_CODE = 2001 - - # Available flags for an Update message. - FLAGS = [:upsert, :multi_update] - - # Field representing Zero encoded as an Int32. - field :zero, Zero - - # @!attribute - # @return [String] The namespace for this Update message. - field :namespace, CString - - # @!attribute - # @return [Array] The flags for this Update message. - field :flags, BitVector.new(FLAGS) - - # @!attribute - # @return [Hash] The selector for this Update message. - field :selector, Document - - # @!attribute - # @return [Hash] The update for this Delete message. - field :update, Document - - # Converts legacy update messages to the appropriare OP_COMMAND style - # message. - # - # @since 2.1.0 - class Upconverter - - # The multi constant. - # - # @since 2.2.0 - MULTI = 'multi'.freeze - - # The u constant. - # - # @since 2.2.0 - U = 'u'.freeze - - # The update constant. - # - # @since 2.2.0 - UPDATE = 'update'.freeze - - # The updates constant. - # - # @since 2.2.0 - UPDATES = 'updates'.freeze - - # The upsert constant. - # - # @since 2.2.0 - UPSERT = 'upsert'.freeze - - # @return [ String ] collection The name of the collection. - attr_reader :collection - - # @return [ Hash ] filter The filter. - attr_reader :filter - - # @return [ Hash ] update The update. - attr_reader :update - - # @return [ Array ] flags The flags. - attr_reader :flags - - # Instantiate the upconverter. - # - # @example Instantiate the upconverter. - # Upconverter.new( - # 'users', - # { name: 'test' }, - # { '$set' => { 'name' => 't' }}, - # [] - # ) - # - # @param [ String ] collection The name of the collection. - # @param [ Hash ] filter The filter. - # @param [ Hash ] update The update. - # @param [ Array ] flags The flags. - # - # @since 2.1.0 - def initialize(collection, filter, update, flags) - @collection = collection - @filter = filter - @update = update - @flags = flags - end - - # Get the upconverted command. - # - # @example Get the command. - # upconverter.command - # - # @return [ BSON::Document ] The upconverted command. - # - # @since 2.1.0 - def command - document = BSON::Document.new - updates = BSON::Document.new - updates.store(Message::Q, filter) - updates.store(U, update) - if flags.include?(:multi_update) - updates.store(MULTI, true) - end - if flags.include?(:upsert) - updates.store(UPSERT, true) - end - document.store(UPDATE, collection) - document.store(Message::ORDERED, true) - document.store(UPDATES, [ updates ]) - document - end - end - - Registry.register(OP_CODE, self) - end - end -end diff --git a/spec/integration/command_spec.rb b/spec/integration/command_spec.rb index 9b3237df72..8f78137823 100644 --- a/spec/integration/command_spec.rb +++ b/spec/integration/command_spec.rb @@ -12,7 +12,7 @@ let(:payload) do server.with_connection do |connection| - command.send(:final_operation, connection).send(:message, connection).payload.dup.tap do |payload| + command.send(:final_operation).send(:message, connection).payload.dup.tap do |payload| if payload['request_id'].is_a?(Integer) payload['request_id'] = 42 end @@ -150,28 +150,6 @@ expect(payload).to eq(expected_payload) end end - - # Servers using legacy wire protocol message do not have $db in payload. - # $db is added to the payload later when the command monitoring event is - # published. - context 'pre-OP_MSG servers' do - max_server_version '3.4' - - let(:expected_payload) do - { - 'command' => { - 'find' => 'collection_name', - }, - 'command_name' => 'find', - 'database_name' => 'foo', - 'request_id' => 42, - } - end - - it 'returns expected payload' do - expect(payload).to eq(expected_payload) - end - end end end diff --git a/spec/mongo/operation/delete/command_spec.rb b/spec/mongo/operation/delete/command_spec.rb deleted file mode 100644 index c189306d8c..0000000000 --- a/spec/mongo/operation/delete/command_spec.rb +++ /dev/null @@ -1,115 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'spec_helper' - -describe Mongo::Operation::Delete::Command do - - let(:write_concern) do - Mongo::WriteConcern.get(w: :majority) - end - - let(:session) { nil } - let(:deletes) { [{:q => { :foo => 1 }, :limit => 1}] } - let(:spec) do - { :deletes => deletes, - :db_name => authorized_collection.database.name, - :coll_name => authorized_collection.name, - :write_concern => write_concern, - :ordered => true, - :session => session - } - end - - let(:op) { described_class.new(spec) } - - describe '#initialize' do - - context 'spec' do - - it 'sets the spec' do - expect(op.spec).to eq(spec) - end - end - end - - describe '#==' do - - context 'spec' do - - context 'when two ops have the same specs' do - let(:other) { described_class.new(spec) } - - it 'returns true' do - expect(op).to eq(other) - end - end - - context 'when two ops have different specs' do - let(:other_deletes) { [{:q => { :bar => 1 }, :limit => 1}] } - let(:other_spec) do - { :deletes => other_deletes, - :db_name => authorized_collection.database.name, - :coll_name => authorized_collection.name, - :write_concern => write_concern, - :ordered => true - } - end - let(:other) { described_class.new(other_spec) } - - it 'returns false' do - expect(op).not_to eq(other) - end - end - end - end - - describe 'write concern' do - - context 'when write concern is not specified' do - - let(:spec) do - { :deletes => deletes, - :db_name => authorized_collection.database.name, - :coll_name => authorized_collection.name, - :ordered => true - } - end - - it 'does not include write concern in the selector' do - expect(op.send(:command, double('server'))[:writeConcern]).to be_nil - end - end - - context 'when write concern is specified' do - - it 'includes write concern in the selector' do - expect(op.send(:command, double('server'))[:writeConcern]).to eq(write_concern.options) - end - end - end - - describe '#message' do - - context 'when the server does not support OP_MSG' do - max_server_version '3.4' - - let(:expected_selector) do - { - :delete => authorized_collection.name, - :deletes => deletes, - :writeConcern => write_concern.options, - :ordered => true - } - end - - it 'creates the correct query wire protocol message' do - expect(Mongo::Protocol::Query).to receive(:new).with(authorized_collection.database.name, - '$cmd', - expected_selector, - { limit: -1 } ) - op.send(:message, authorized_primary) - end - end - end -end diff --git a/spec/mongo/operation/find/legacy_spec.rb b/spec/mongo/operation/find/legacy_spec.rb deleted file mode 100644 index f9cf973f3b..0000000000 --- a/spec/mongo/operation/find/legacy_spec.rb +++ /dev/null @@ -1,131 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'spec_helper' - -describe Mongo::Operation::Find::Legacy do - max_server_version '3.4' - - let(:context) { Mongo::Operation::Context.new } - - let(:selector) { { foo: 1 } } - let(:query_options) { {} } - let(:spec) do - { :selector => selector, - :options => query_options, - :db_name => authorized_collection.database.name, - :coll_name => authorized_collection.name, - :read => Mongo::ServerSelector.get - } - end - let(:op) { described_class.new(spec) } - - describe '#initialize' do - - context 'query spec' do - it 'sets the query spec' do - expect(op.spec).to be(spec) - end - end - end - - describe '#==' do - - context 'when two ops have different specs' do - let(:other_spec) do - { :selector => { :a => 1 }, - :options => query_options, - :db_name => authorized_collection.database.name, - :coll_name => authorized_collection.name - } - end - let(:other) { described_class.new(other_spec) } - - it 'returns false' do - expect(op).not_to eq(other) - end - end - end - - describe '#message' do - - let(:query_options) do - { :flags => [ :no_cursor_timeout ]} - end - - let(:query) do - described_class.new(spec) - end - - let(:cluster_single) do - double('cluster').tap do |c| - allow(c).to receive(:single?).and_return(true) - end - end - - let(:message) do - authorized_primary.with_connection do |connection| - query.send(:message, connection) - end - end - - it 'applies the correct flags' do - expect(message.flags).to eq(query_options[:flags]) - end - - context 'when the server is a secondary' do - let(:description) do - double('description').tap do |description| - expect(description).to receive(:mongos?) { false } - expect(description).to receive(:standalone?) { false } - #expect(description).to receive(:load_balancer?) { false } - end - end - - let(:connection) do - double('connection').tap do |conn| - expect(conn).to receive(:description).at_least(:once).and_return(description) - end - end - - let(:secondary_server_single) do - double('secondary_server').tap do |server| - expect(server).to receive(:with_connection).and_yield(connection) - expect(server).to receive(:cluster) { cluster_single } - end - end - - let(:message) do - secondary_server_single.with_connection do |connection| - query.send(:message, connection) - end - end - - before do - allow(connection).to receive(:server) { secondary_server_single } - end - - it 'applies the correct flags' do - expect(message.flags).to eq([ :no_cursor_timeout, :secondary_ok ]) - end - end - - context "when the document contains an 'ok' field" do - - before do - authorized_collection.insert_one(ok: false) - end - - after do - authorized_collection.delete_many - end - - it 'does not raise an exception' do - authorized_primary.with_connection do |connection| - expect(op.execute(connection, context: context)).to be_a(Mongo::Operation::Find::Legacy::Result) - end - end - end - end -end - diff --git a/spec/mongo/operation/get_more_spec.rb b/spec/mongo/operation/get_more_spec.rb deleted file mode 100644 index 7c5bd9bb19..0000000000 --- a/spec/mongo/operation/get_more_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'spec_helper' - -describe Mongo::Operation::GetMore::Legacy do - - let(:to_return) do - 50 - end - - let(:cursor_id) do - 1 - end - - let(:spec) do - { :db_name => SpecConfig.instance.test_db, - :coll_name => TEST_COLL, - :to_return => to_return, - :cursor_id => cursor_id } - end - - let(:op) { described_class.new(spec) } - - let(:context) { Mongo::Operation::Context.new } - - describe '#initialize' do - - it 'sets the spec' do - expect(op.spec).to be(spec) - end - end - - describe '#==' do - - context ' when two ops have different specs' do - let(:other_spec) do - { :db_name => 'test_db', - :coll_name => 'test_coll', - :to_return => 50, - :cursor_id => 2 } - end - let(:other) { described_class.new(other_spec) } - - it 'returns false' do - expect(op).not_to eq(other) - end - end - end - - describe '#message' do - - it 'creates a get more wire protocol message with correct specs' do - expect(Mongo::Protocol::GetMore).to receive(:new).with(SpecConfig.instance.test_db, TEST_COLL, to_return, cursor_id).and_call_original - begin - authorized_primary.with_connection do |connection| - op.execute(connection, context: context) - end - rescue - end - end - end -end diff --git a/spec/mongo/operation/insert/command_spec.rb b/spec/mongo/operation/insert/command_spec.rb deleted file mode 100644 index 930c8fdfa5..0000000000 --- a/spec/mongo/operation/insert/command_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'spec_helper' - -describe Mongo::Operation::Insert::Command do - - let(:documents) { [{ :_id => 1, :foo => 1 }] } - let(:session) { nil } - let(:spec) do - { :documents => documents, - :db_name => authorized_collection.database.name, - :coll_name => authorized_collection.name, - :write_concern => write_concern, - :ordered => true, - :session => session - } - end - - let(:write_concern) do - Mongo::WriteConcern.get(w: :majority) - end - - let(:op) { described_class.new(spec) } - - describe '#initialize' do - - context 'spec' do - - it 'sets the spec' do - expect(op.spec).to eq(spec) - end - end - end - - describe '#==' do - - context 'spec' do - - context 'when two ops have the same specs' do - let(:other) { described_class.new(spec) } - - it 'returns true' do - expect(op).to eq(other) - end - end - - context 'when two ops have different specs' do - let(:other_documents) { [{ :bar => 1 }] } - let(:other_spec) do - { :documents => other_documents, - :db_name => authorized_collection.database.name, - :insert => authorized_collection.name, - :write_concern => write_concern.options, - :ordered => true - } - end - let(:other) { described_class.new(other_spec) } - - it 'returns false' do - expect(op).not_to eq(other) - end - end - end - end - - describe 'write concern' do - # https://jira.mongodb.org/browse/RUBY-2224 - require_no_linting - - context 'when write concern is not specified' do - - let(:spec) do - { :documents => documents, - :db_name => authorized_collection.database.name, - :coll_name => authorized_collection.name, - :ordered => true - } - end - - it 'does not include write concern in the selector' do - expect(op.send(:command, double('server'))[:writeConcern]).to be_nil - end - end - - context 'when write concern is specified' do - - it 'includes write concern in the selector' do - expect(op.send(:command, double('server'))[:writeConcern]).to eq(write_concern.options) - end - end - end - - describe '#message' do - # https://jira.mongodb.org/browse/RUBY-2224 - require_no_linting - - context 'when the server does not support OP_MSG' do - max_server_version '3.4' - - let(:expected_selector) do - { :documents => documents, - :insert => authorized_collection.name, - :writeConcern => write_concern.options, - :ordered => true - } - end - - it 'creates a query wire protocol message with correct specs' do - expect(Mongo::Protocol::Query).to receive(:new).with(authorized_collection.database.name, - '$cmd', - expected_selector, - { limit: -1, validating_keys: true }) - op.send(:message, authorized_primary) - end - end - end -end diff --git a/spec/mongo/operation/update/command_spec.rb b/spec/mongo/operation/update/command_spec.rb deleted file mode 100644 index f2b0c6de46..0000000000 --- a/spec/mongo/operation/update/command_spec.rb +++ /dev/null @@ -1,122 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'spec_helper' - -describe Mongo::Operation::Update::Command do - - let(:updates) { [{:q => { :foo => 1 }, - :u => { :$set => { :bar => 1 } }, - :multi => true, - :upsert => false }] } - - let(:write_concern) do - Mongo::WriteConcern.get(w: :majority) - end - let(:session) { nil } - let(:spec) do - { :updates => updates, - :db_name => SpecConfig.instance.test_db, - :coll_name => TEST_COLL, - :write_concern => write_concern, - :ordered => true, - :session => session - } - end - - let(:op) { described_class.new(spec) } - - describe '#initialize' do - - context 'spec' do - - it 'sets the spec' do - expect(op.spec).to eq(spec) - end - end - end - - describe '#==' do - - context 'spec' do - - context 'when two ops have the same specs' do - let(:other) { described_class.new(spec) } - - it 'returns true' do - expect(op).to eq(other) - end - end - - context 'when two ops have different specs' do - let(:other_updates) { [{:q => { :bar => 1 }, - :u => { :$set => { :bar => 2 } }, - :multi => true, - :upsert => false }] } - let(:other_spec) do - { :updates => other_updates, - :db_name => SpecConfig.instance.test_db, - :coll_name => TEST_COLL, - :write_concern => Mongo::WriteConcern.get(w: :majority), - :ordered => true - } - end - let(:other) { described_class.new(other_spec) } - - it 'returns false' do - expect(op).not_to eq(other) - end - end - end - end - - describe 'write concern' do - # https://jira.mongodb.org/browse/RUBY-2224 - require_no_linting - - context 'when write concern is not specified' do - - let(:spec) do - { :updates => updates, - :db_name => SpecConfig.instance.test_db, - :coll_name => TEST_COLL, - :ordered => true - } - end - - it 'does not include write concern in the selector' do - expect(op.send(:command, double('server'))[:writeConcern]).to be_nil - end - end - - context 'when write concern is specified' do - - it 'includes write concern in the selector' do - expect(op.send(:command, double('server'))[:writeConcern]).to eq(write_concern.options) - end - end - end - - describe '#message' do - # https://jira.mongodb.org/browse/RUBY-2224 - require_no_linting - - context 'when the server does not support OP_MSG' do - max_server_version '3.4' - - let(:expected_selector) do - { - :update => TEST_COLL, - :updates => updates, - :ordered => true, - :writeConcern => write_concern.options - } - end - - it 'creates the correct Command message' do - expect(Mongo::Protocol::Query).to receive(:new).with(SpecConfig.instance.test_db, '$cmd', expected_selector, { limit: -1 }) - op.send(:message, authorized_primary) - end - end - end -end diff --git a/spec/mongo/protocol/delete_spec.rb b/spec/mongo/protocol/delete_spec.rb deleted file mode 100644 index d3bb0566c6..0000000000 --- a/spec/mongo/protocol/delete_spec.rb +++ /dev/null @@ -1,185 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'lite_spec_helper' -require 'support/shared/protocol' - -describe Mongo::Protocol::Delete do - - let(:opcode) { 2006 } - let(:db) { SpecConfig.instance.test_db } - let(:collection_name) { 'protocol-test' } - let(:ns) { "#{db}.#{collection_name}" } - let(:selector) { { :name => 'Tyler' } } - let(:options) { Hash.new } - - let(:message) do - described_class.new(db, collection_name, selector, options) - end - - describe '#initialize' do - - it 'sets the namepsace' do - expect(message.namespace).to eq(ns) - end - - it 'sets the selector' do - expect(message.selector).to eq(selector) - end - - context 'when options are provided' do - - context 'when flags are provided' do - let(:options) { { :flags => [:single_remove] } } - - it 'sets the flags' do - expect(message.flags).to eq(options[:flags]) - end - end - end - end - - describe '#==' do - - context 'when the other is a delete' do - - context 'when the fields are equal' do - let(:other) do - described_class.new(db, collection_name, selector, options) - end - - it 'returns true' do - expect(message).to eq(other) - end - end - - context 'when the database is not equal' do - let(:other) do - described_class.new('tyler', collection_name, selector, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the collection is not equal' do - let(:other) do - described_class.new(db, 'tyler', selector, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the selector is not equal' do - let(:other) do - described_class.new(db, collection_name, { :a => 1 }, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the options are not equal' do - let(:other) do - described_class.new(db, collection_name, selector, :flags => [:single_remove]) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - end - - context 'when the other is not a delete' do - let(:other) do - expect(message).not_to eq('test') - end - end - end - - describe '#hash' do - let(:values) do - message.send(:fields).map do |field| - message.instance_variable_get(field[:name]) - end - end - - it 'returns a hash of the field values' do - expect(message.hash).to eq(values.hash) - end - end - - describe '#replyable?' do - - it 'returns false' do - expect(message).to_not be_replyable - end - end - - describe '#serialize' do - let(:bytes) { message.serialize } - - include_examples 'message with a header' - - describe 'zero' do - let(:field) { bytes.to_s[16..19] } - - it 'serializes a zero' do - expect(field).to be_int32(0) - end - end - - describe 'namespace' do - let(:field) { bytes.to_s[20..36] } - it 'serializes the namespace' do - expect(field).to be_cstring(ns) - end - end - - describe 'flags' do - let(:field) { bytes.to_s[37..40] } - - context 'when no flags are provided' do - it 'does not set any bits' do - expect(field).to be_int32(0) - end - end - - context 'when flags are provided' do - let(:options) { { :flags => flags } } - - context 'single remove flag' do - let(:flags) { [:single_remove] } - it 'sets the first bit' do - expect(field).to be_int32(1) - end - end - end - end - - describe 'selector' do - let(:field) { bytes.to_s[41..-1] } - it 'serializes the selector' do - expect(field).to be_bson(selector) - end - end - end - - describe '#registry' do - - context 'when the class is loaded' do - - it 'registers the op code in the Protocol Registry' do - expect(Mongo::Protocol::Registry.get(described_class::OP_CODE)).to be(described_class) - end - - it 'creates an #op_code instance method' do - expect(message.op_code).to eq(described_class::OP_CODE) - end - end - end -end diff --git a/spec/mongo/protocol/insert_spec.rb b/spec/mongo/protocol/insert_spec.rb deleted file mode 100644 index 804963a17a..0000000000 --- a/spec/mongo/protocol/insert_spec.rb +++ /dev/null @@ -1,179 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'lite_spec_helper' -require 'support/shared/protocol' - -describe Mongo::Protocol::Insert do - - let(:opcode) { 2002 } - let(:db) { SpecConfig.instance.test_db } - let(:collection_name) { 'protocol-test' } - let(:ns) { "#{db}.#{collection_name}" } - let(:doc1) { { :name => 'Tyler' } } - let(:doc2) { { :name => 'Brandon' } } - let(:docs) { [doc1, doc2] } - let(:options) { Hash.new } - - let(:message) do - described_class.new(db, collection_name, docs, options) - end - - describe '#initialize' do - - it 'sets the namepsace' do - expect(message.namespace).to eq(ns) - end - - it 'sets the documents' do - expect(message.documents).to eq(docs) - end - - context 'when options are provided' do - - context 'when flags are provided' do - let(:options) { { :flags => [:continue_on_error] } } - - it 'sets the flags' do - expect(message.flags).to eq(options[:flags]) - end - end - end - end - - describe '#==' do - - context 'when the other is an insert' do - - context 'when the fields are equal' do - let(:other) do - described_class.new(db, collection_name, docs, options) - end - - it 'returns true' do - expect(message).to eq(other) - end - end - - context 'when the database is not equal' do - let(:other) do - described_class.new('tyler', collection_name, docs, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the collection is not equal' do - let(:other) do - described_class.new(db, 'tyler', docs, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the documents are not equal' do - let(:other) do - described_class.new(db, collection_name, docs[1..1], options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the options are not equal' do - let(:other) do - described_class.new(db, collection_name, docs, :flags => [:continue_on_error]) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - end - - context 'when the other is not an insert' do - let(:other) do - expect(message).not_to eq('test') - end - end - end - - describe '#hash' do - let(:values) do - message.send(:fields).map do |field| - message.instance_variable_get(field[:name]) - end - end - - it 'returns a hash of the field values' do - expect(message.hash).to eq(values.hash) - end - end - - describe '#replyable?' do - - it 'returns false' do - expect(message).to_not be_replyable - end - end - - describe '#serialize' do - let(:bytes) { message.serialize } - - include_examples 'message with a header' - - describe 'flags' do - let(:field) { bytes.to_s[16..19] } - - context 'when no flags are provided' do - it 'does not set any bits' do - expect(field).to be_int32(0) - end - end - - context 'when flags are provided' do - let(:options) { { :flags => flags } } - - context 'continue on error flag' do - let(:flags) { [:continue_on_error] } - it 'sets the first bit' do - expect(field).to be_int32(1) - end - end - end - end - - describe 'namespace' do - let(:field) { bytes.to_s[20..36] } - it 'serializes the namespace' do - expect(field).to be_cstring(ns) - end - end - - describe 'documents' do - let(:field) { bytes.to_s[37..-1] } - it 'serializes the documents' do - expect(field).to be_bson_sequence(docs) - end - end - end - - describe '#registry' do - - context 'when the class is loaded' do - - it 'registers the op code in the Protocol Registry' do - expect(Mongo::Protocol::Registry.get(described_class::OP_CODE)).to be(described_class) - end - - it 'creates an #op_code instance method' do - expect(message.op_code).to eq(described_class::OP_CODE) - end - end - end -end diff --git a/spec/mongo/protocol/update_spec.rb b/spec/mongo/protocol/update_spec.rb deleted file mode 100644 index 98412f1db8..0000000000 --- a/spec/mongo/protocol/update_spec.rb +++ /dev/null @@ -1,204 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'lite_spec_helper' -require 'support/shared/protocol' - -describe Mongo::Protocol::Update do - - let(:opcode) { 2001 } - let(:db) { SpecConfig.instance.test_db } - let(:collection_name) { 'protocol-test' } - let(:ns) { "#{db}.#{collection_name}" } - let(:selector) { { :name => 'Tyler' } } - let(:update_doc) { { :name => 'Bob' } } - let(:options) { Hash.new } - - let(:message) do - described_class.new(db, collection_name, selector, update_doc, options) - end - - describe '#initialize' do - - it 'sets the namespace' do - expect(message.namespace).to eq(ns) - end - - it 'sets the selector' do - expect(message.selector).to eq(selector) - end - - it 'sets the update document' do - expect(message.update).to eq(update_doc) - end - end - - describe '#==' do - - context 'when the other is an update' do - - context 'when the fields are equal' do - let(:other) do - described_class.new(db, collection_name, selector, update_doc, options) - end - - it 'returns true' do - expect(message).to eq(other) - end - end - - context 'when the database is not equal' do - let(:other) do - described_class.new('tyler', collection_name, selector, update_doc, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the collection is not equal' do - let(:other) do - described_class.new(db, 'tyler', selector, update_doc, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the selector is not equal' do - let(:other) do - described_class.new(db, collection_name, { :a => 1 }, update_doc, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the update document is not equal' do - let(:other) do - described_class.new(db, collection_name, selector, { :a => 1 }, options) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - - context 'when the options are not equal' do - let(:other) do - described_class.new(db, collection_name, selector, update_doc, - :flags => :upsert) - end - - it 'returns false' do - expect(message).not_to eq(other) - end - end - end - - context 'when the other is not a query' do - let(:other) do - expect(message).not_to eq('test') - end - end - end - - describe '#hash' do - let(:values) do - message.send(:fields).map do |field| - message.instance_variable_get(field[:name]) - end - end - - it 'returns a hash of the field values' do - expect(message.hash).to eq(values.hash) - end - end - - describe '#replyable?' do - - it 'returns false' do - expect(message).to_not be_replyable - end - end - - describe '#serialize' do - let(:bytes) { message.serialize } - - include_examples 'message with a header' - - describe 'zero' do - let(:field) { bytes.to_s[16..19] } - - it 'serializes a zero' do - expect(field).to be_int32(0) - end - end - - describe 'namespace' do - let(:field) { bytes.to_s[20..36] } - it 'serializes the namespace' do - expect(field).to be_cstring(ns) - end - end - - describe 'flags' do - let(:field) { bytes.to_s[37..40] } - - context 'when no flags are provided' do - it 'does not set any bits' do - expect(field).to be_int32(0) - end - end - - context 'when flags are provided' do - let(:options) { { :flags => flags } } - - context 'upsert flag' do - let(:flags) { [:upsert] } - it 'sets the first bit' do - expect(field).to be_int32(1) - end - end - - context 'multi update' do - let(:flags) { [:multi_update] } - it 'sets the second bit' do - expect(field).to be_int32(2) - end - end - end - end - - describe 'selector' do - let(:field) { bytes.to_s[41..61] } - it 'serializes the selector' do - expect(field).to be_bson(selector) - end - end - - describe 'update' do - let(:field) { bytes.to_s[62..80] } - it 'serializes the update' do - expect(field).to be_bson(update_doc) - end - end - end - - describe '#registry' do - - context 'when the class is loaded' do - - it 'registers the op code in the Protocol Registry' do - expect(Mongo::Protocol::Registry.get(described_class::OP_CODE)).to be(described_class) - end - - it 'creates an #op_code instance method' do - expect(message.op_code).to eq(described_class::OP_CODE) - end - end - end -end From 31fbe5cd68a0d6bc02adecb7abde9cfda66410b8 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> Date: Mon, 29 Aug 2022 12:24:18 -0400 Subject: [PATCH 027/198] Attempt to unflake the integration map/reduce test on sharded clusters (#2601) Co-authored-by: Oleg Pudeyev --- spec/integration/map_reduce_spec.rb | 4 ++++ spec/support/mongos_macros.rb | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/spec/integration/map_reduce_spec.rb b/spec/integration/map_reduce_spec.rb index 634d9e9cae..16feb1ddb5 100644 --- a/spec/integration/map_reduce_spec.rb +++ b/spec/integration/map_reduce_spec.rb @@ -17,6 +17,10 @@ before do collection.insert_one(test: 1) + + # Ensure all mongoses are aware of the collection. + maybe_run_mongos_distincts(collection.database.name, collection.name) + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) end diff --git a/spec/support/mongos_macros.rb b/spec/support/mongos_macros.rb index 84b450dba4..b8450016ab 100644 --- a/spec/support/mongos_macros.rb +++ b/spec/support/mongos_macros.rb @@ -14,4 +14,10 @@ def run_mongos_distincts(db_name, collection='test') direct_client.use(db_name)[collection].distinct('foo').to_a end end + + def maybe_run_mongos_distincts(db_name, collection='test') + if ClusterConfig.instance.topology == :sharded + run_mongos_distincts(db_name, collection) + end + end end From de589206fcfcab51c66fa3bb9d8106c44eacefb0 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Tue, 30 Aug 2022 12:14:51 -0400 Subject: [PATCH 028/198] RUBY-3068 Expose rich field information in the updateDescription (#2604) --- .../change-streams-disambiguatedPaths.yml | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml b/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml new file mode 100644 index 0000000000..674c876b02 --- /dev/null +++ b/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml @@ -0,0 +1,102 @@ +description: "disambiguatedPaths" +schemaVersion: "1.3" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "6.1.0" + topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] + +initialData: + - collectionName: *collection0 + databaseName: *database0 + documents: [] + +tests: + - description: "disambiguatedPaths is not present when showExpandedEvents is false/unset" + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1, 'a': { '1': 1 } } + - name: createChangeStream + object: *collection0 + arguments: { pipeline: [] } + saveResultAsEntity: &changeStream0 changeStream0 + - name: updateOne + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { 'a.1': 2 } } + - name: iterateUntilDocumentOrError + object: *changeStream0 + expectResult: + operationType: "update" + ns: { db: *database0, coll: *collection0 } + updateDescription: + updatedFields: { $$exists: true } + removedFields: { $$exists: true } + truncatedArrays: { $$exists: true } + disambiguatedPaths: { $$exists: false } + + - description: "disambiguatedPaths is present on updateDescription when an ambiguous path is present" + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1, 'a': { '1': 1 } } + - name: createChangeStream + object: *collection0 + arguments: { pipeline: [], showExpandedEvents: true } + saveResultAsEntity: &changeStream0 changeStream0 + - name: updateOne + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { 'a.1': 2 } } + - name: iterateUntilDocumentOrError + object: *changeStream0 + expectResult: + operationType: "update" + ns: { db: *database0, coll: *collection0 } + updateDescription: + updatedFields: { $$exists: true } + removedFields: { $$exists: true } + truncatedArrays: { $$exists: true } + disambiguatedPaths: { 'a.1': ['a', '1'] } + + - description: "disambiguatedPaths returns array indices as integers" + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1, 'a': [{'1': 1 }] } + - name: createChangeStream + object: *collection0 + arguments: { pipeline: [], showExpandedEvents: true } + saveResultAsEntity: &changeStream0 changeStream0 + - name: updateOne + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { 'a.0.1': 2 } } + - name: iterateUntilDocumentOrError + object: *changeStream0 + expectResult: + operationType: "update" + ns: { db: *database0, coll: *collection0 } + updateDescription: + updatedFields: { $$exists: true } + removedFields: { $$exists: true } + truncatedArrays: { $$exists: true } + disambiguatedPaths: { 'a.0.1': ['a', { $$type: 'int' }, '1'] } From a4a2d2a392a6d9aa632c25eddf85d00635adaa26 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 1 Sep 2022 18:09:26 -0400 Subject: [PATCH 029/198] RUBY-3095 write tests, docs for option mapper (#2606) * RUBY-3095 write tests, docs for option mapper * Update lib/mongo/uri/options_mapper.rb * RUBY-3095 convert back to identity * RUBY-3095 answer comments * RUBY-3095 fix test --- lib/mongo/uri/options_mapper.rb | 201 +++-- spec/mongo/uri/options_mapper_spec.rb | 1183 +++++++++++++++++++++++++ 2 files changed, 1321 insertions(+), 63 deletions(-) create mode 100644 spec/mongo/uri/options_mapper_spec.rb diff --git a/lib/mongo/uri/options_mapper.rb b/lib/mongo/uri/options_mapper.rb index c6f2e2900b..d3e431c420 100644 --- a/lib/mongo/uri/options_mapper.rb +++ b/lib/mongo/uri/options_mapper.rb @@ -171,9 +171,9 @@ def ruby_to_smc(opts) # Applies URI value transformation by either using the default cast # or a transformation appropriate for the given type. # - # @param key [String] URI option name. - # @param value [String] The value to be transformed. - # @param type [Symbol] The transform method. + # @param [ String ] key URI option name. + # @param [ String ] value The value to be transformed. + # @param [ Symbol ] type The transform method. def apply_transform(key, value, type) if type send("convert_#{type}", key, value) @@ -190,9 +190,9 @@ def apply_transform(key, value, type) # Specifically required to append an additional tag set # to the array of tag sets without overwriting the original. # - # @param target [Hash] The destination. - # @param value [Object] The value to be merged. - # @param name [Symbol] The name of the option. + # @param [ Hash ] target The destination. + # @param [ Object ] value The value to be merged. + # @param [ Symbol ] name The name of the option. def merge_uri_option(target, value, name) if target.key?(name) if REPEATABLE_OPTIONS.include?(name) @@ -214,11 +214,11 @@ def merge_uri_option(target, value, name) # Simple internal dsl to register a MongoDB URI option in the URI_OPTION_MAP. # - # @param uri_key [String] The MongoDB URI option to register. - # @param name [Symbol] The name of the option in the driver. - # @param extra [Hash] Extra options. - # * :group [Symbol] Nested hash where option will go. - # * :type [Symbol] Name of function to transform value. + # @param [ String ] uri_key The MongoDB URI option to register. + # @param [ Symbol ] name The name of the option in the driver. + # @param [ Hash ] extra Extra options. + # * :group [ Symbol ] Nested hash where option will go. + # * :type [ Symbol ] Name of function to transform value. def self.uri_option(uri_key, name, **extra) URI_OPTION_MAP[uri_key.downcase] = { name: name }.update(extra) URI_OPTION_CANONICAL_NAMES[uri_key.downcase] = uri_key @@ -290,7 +290,7 @@ def self.uri_option(uri_key, name, **extra) # Returns true for 'true', false for 'false', otherwise nil. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String | true | false ] URI option value. + # @param [ String | true | false ] value URI option value. # # @return [ true | false | nil ] Converted value. def convert_bool(name, value) @@ -305,21 +305,31 @@ def convert_bool(name, value) end end + # Reverts a boolean type. + # + # @param [ true | false | nil ] value The boolean to revert. + # + # @return [ true | false | nil ] The passed value. def revert_bool(value) value end # Converts the value into a boolean and returns it wrapped in an array. # - # @param name [ String ] Name of the URI option being processed. - # @param value [ String ] URI option value. + # @param [ String ] name Name of the URI option being processed. + # @param [ String ] value URI option value. # - # @return [ Array ] The boolean value parsed and wraped + # @return [ Array | nil ] The boolean value parsed and wraped # in an array. def convert_repeated_bool(name, value) [convert_bool(name, value)] end + # Reverts a repeated boolean type. + # + # @param [ Array | nil ] value The repeated boolean to revert. + # + # @return [ Array | nil ] The passed value. def revert_repeated_bool(value) value end @@ -327,9 +337,9 @@ def revert_repeated_bool(value) # Parses a boolean value and returns its inverse. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String | true | false ] The URI option value. + # @param [ String | true | false ] value The URI option value. # - # @return [ true | false | nil ] The inverse of the boolean value parsed out, otherwise nil + # @return [ true | false | nil ] The inverse of the boolean value parsed out, otherwise nil # (and a warning will be logged). def convert_inverse_bool(name, value) b = convert_bool(name, value) @@ -341,16 +351,21 @@ def convert_inverse_bool(name, value) end end + # Reverts and inverts a boolean type. + # + # @param [ true | false | nil ] value The boolean to revert and invert. + # + # @return [ true | false | nil ] The inverted boolean. def revert_inverse_bool(value) - !value + value.nil? ? nil : !value end - # Converts +value+ into an integer. + # Converts +value+ into an integer. Only converts positive integers. # # If the value is not a valid integer, warns and returns nil. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String | Integer ] URI option value. + # @param [ String | Integer ] value URI option value. # # @return [ nil | Integer ] Converted value. def convert_integer(name, value) @@ -362,6 +377,11 @@ def convert_integer(name, value) value.to_i end + # Reverts an integer. + # + # @param [ Integer | nil ] value The integer. + # + # @return [ Integer | nil ] The passed value. def revert_integer(value) value end @@ -371,7 +391,7 @@ def revert_integer(value) # options are always in MS so we provide an easy conversion type. # # @param [ String ] name Name of the URI option being processed. - # @param [ String | Integer ] value The millisecond value. + # @param [ String | Integer | Float ] value The millisecond value. # # @return [ Float ] The seconds value. # @@ -387,18 +407,23 @@ def convert_ms(name, value) log_warn("#{name} cannot be a negative number") return nil end - when Integer + when Integer, Float if value < 0 log_warn("#{name} cannot be a negative number") return nil end else - raise ArgumentError, "Can only convert Strings or Integers to ms. Given: #{value.class}" + raise ArgumentError, "Can only convert Strings, Integers, or Floats to ms. Given: #{value.class}" end value.to_f / 1000 end + # Reverts an ms. + # + # @param [ Float ] value The float. + # + # @return [ Integer ] The number multiplied by 1000 as an integer. def revert_ms(value) (value * 1000).round end @@ -406,13 +431,18 @@ def revert_ms(value) # Converts +value+ into a symbol. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] URI option value. + # @param [ String | Symbol ] value URI option value. # # @return [ Symbol ] Converted value. def convert_symbol(name, value) value.to_sym end + # Reverts a symbol. + # + # @param [ Symbol ] value The symbol. + # + # @return [ String ] The passed value as a string. def revert_symbol(value) value.to_s end @@ -422,11 +452,16 @@ def revert_symbol(value) # @param [ String ] name Name of the URI option being processed. # @param [ String ] value The string to build an array from. # - # @return [ Array ] The array built from the string. + # @return [ Array ] The array built from the string. def convert_array(name, value) value.split(',') end + # Reverts an array. + # + # @param [ Array ] value An array of strings. + # + # @return [ Array ] The passed value. def revert_array(value) value end @@ -434,15 +469,21 @@ def revert_array(value) # Authentication mechanism transformation. # # @param [ String ] name Name of the URI option being processed. - # @param value [String] The authentication mechanism. + # @param [ String ] value The authentication mechanism. # - # @return [Symbol] The transformed authentication mechanism. + # @return [ Symbol ] The transformed authentication mechanism. def convert_auth_mech(name, value) - (AUTH_MECH_MAP[value.upcase] || value).tap do |mech| - log_warn("#{value} is not a valid auth mechanism") unless mech + auth_mech = AUTH_MECH_MAP[value.upcase] + (auth_mech || value).tap do |mech| + log_warn("#{value} is not a valid auth mechanism") unless auth_mech end end + # Reverts auth mechanism. + # + # @param [ Symbol ] value The auth mechanism. + # + # @return [ String ] The auth mechanism as a string. def revert_auth_mech(value) found = AUTH_MECH_MAP.detect do |k, v| v == value @@ -457,9 +498,9 @@ def revert_auth_mech(value) # Auth mechanism properties extractor. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] The auth mechanism properties string. + # @param [ String ] value The auth mechanism properties string. # - # @return [ Hash ] The auth mechanism properties hash. + # @return [ Hash | nil ] The auth mechanism properties hash. def convert_auth_mech_props(name, value) properties = hash_extractor('authMechanismProperties', value) if properties @@ -472,6 +513,11 @@ def convert_auth_mech_props(name, value) properties end + # Reverts auth mechanism properties. + # + # @param [ Hash | nil ] value The auth mech properties. + # + # @return [ Hash | nil ] The passed value. def revert_auth_mech_props(value) value end @@ -480,30 +526,39 @@ def revert_auth_mech_props(value) # greater or equal to 90. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] The max staleness string. + # @param [ String | Integer ] value The max staleness string. # # @return [ Integer | nil ] The max staleness integer parsed out if it is valid, otherwise nil # (and a warning will be logged). def convert_max_staleness(name, value) - if /\A-?\d+\z/ =~ value - int = value.to_i + int = if value.is_a?(String) && /\A-?\d+\z/ =~ value + value.to_i + elsif value.is_a?(Integer) + value + end - if int == -1 - int = nil - end + if int.nil? + log_warn("Invalid max staleness value: #{value}") + return nil + end - if int && (int >= 0 && int < 90 || int < 0) - log_warn("max staleness should be either 0 or greater than 90: #{value}") - int = nil - end + if int == -1 + int = nil + end - return int + if int && (int > 0 && int < 90 || int < 0) + log_warn("max staleness should be either 0 or greater than 90: #{value}") + int = nil end - log_warn("Invalid max staleness value: #{value}") - nil + int end + # Reverts max staleness. + # + # @param [ Integer | nil ] value The max staleness. + # + # @return [ Integer | nil ] The passed value. def revert_max_staleness(value) value end @@ -511,13 +566,18 @@ def revert_max_staleness(value) # Read preference mode transformation. # # @param [ String ] name Name of the URI option being processed. - # @param value [String] The read mode string value. + # @param [ String ] value The read mode string value. # - # @return [Symbol] The read mode symbol. + # @return [ Symbol | String ] The read mode. def convert_read_mode(name, value) READ_MODE_MAP[value.downcase] || value end + # Reverts read mode. + # + # @param [ Symbol | String ] value The read mode. + # + # @return [ String ] The read mode as a string. def revert_read_mode(value) value.to_s.gsub(/_(\w)/) { $1.upcase } end @@ -525,9 +585,9 @@ def revert_read_mode(value) # Read preference tags transformation. # # @param [ String ] name Name of the URI option being processed. - # @param value [String] The string representing tag set. + # @param [ String ] value The string representing tag set. # - # @return [Array] Array with tag set. + # @return [ Array | nil ] Array with tag set. def convert_read_tags(name, value) converted = convert_read_set(name, value) if converted @@ -537,6 +597,11 @@ def convert_read_tags(name, value) end end + # Reverts read tags. + # + # @param [ Array | nil ] value The read tags. + # + # @return [ Array | nil ] The passed value. def revert_read_tags(value) value end @@ -544,9 +609,9 @@ def revert_read_tags(value) # Read preference tag set extractor. # # @param [ String ] name Name of the URI option being processed. - # @param value [String] The tag set string. + # @param [ String ] value The tag set string. # - # @return [Hash] The tag set hash. + # @return [ Hash ] The tag set hash. def convert_read_set(name, value) hash_extractor('readPreferenceTags', value) end @@ -558,7 +623,7 @@ def convert_read_set(name, value) # Otherwise returns the string +value+ unchanged. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] URI option value. + # @param [ String | Integer ] value URI option value. # # @return [ Integer | Symbol | String ] Converted value. def convert_w(name, value) @@ -572,6 +637,11 @@ def convert_w(name, value) end end + # Reverts write concern. + # + # @param [ Integer | Symbol | String ] value The write concern. + # + # @return [ String ] The write concern as a string. def revert_w(value) case value when Symbol @@ -584,23 +654,30 @@ def revert_w(value) # Parses the zlib compression level. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] The zlib compression level string. + # @param [ String | Integer ] value The zlib compression level string. # # @return [ Integer | nil ] The compression level value if it is between -1 and 9 (inclusive), # otherwise nil (and a warning will be logged). def convert_zlib_compression_level(name, value) - if /\A-?\d+\z/ =~ value - i = value.to_i - - if i >= -1 && i <= 9 - return i - end + i = if value.is_a?(String) && /\A-?\d+\z/ =~ value + value.to_i + elsif value.is_a?(Integer) + value end - log_warn("#{value} is not a valid zlibCompressionLevel") - nil + if i && (i >= -1 && i <= 9) + i + else + log_warn("#{value} is not a valid zlibCompressionLevel") + nil + end end + # Reverts zlib compression level + # + # @param [ Integer | nil ] value The write concern. + # + # @return [ Integer | nil ] The passed value. def revert_zlib_compression_level(value) value end @@ -608,7 +685,7 @@ def revert_zlib_compression_level(value) # Extract values from the string and put them into a nested hash. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] The string to build a hash from. + # @param [ String ] value The string to build a hash from. # # @return [ Hash ] The hash built from the string. def hash_extractor(name, value) @@ -628,8 +705,6 @@ def hash_extractor(name, value) h end end - end - end end diff --git a/spec/mongo/uri/options_mapper_spec.rb b/spec/mongo/uri/options_mapper_spec.rb new file mode 100644 index 0000000000..67e421cbfb --- /dev/null +++ b/spec/mongo/uri/options_mapper_spec.rb @@ -0,0 +1,1183 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'spec_helper' + +describe Mongo::URI::OptionsMapper do + + let(:options_mapper) { described_class.new } + let(:converted) { options_mapper.send(method, name, value) } + let(:reverted) { options_mapper.send(method, value) } + let(:name) { "name" } + + describe "#convert_bool" do + + let(:method) { :convert_bool } + + context "when providing false" do + let(:value) { false } + + it "returns false" do + expect(converted).to be false + end + end + + context "when providing true" do + let(:value) { true } + + it "returns true" do + expect(converted).to be true + end + end + + context "when providing a true string" do + let(:value) { "true" } + + it "returns true" do + expect(converted).to be true + end + end + + context "when providing a capital true string" do + let(:value) { "TRUE" } + + it "returns true" do + expect(converted).to be true + end + end + + context "when providing a false string" do + let(:value) { "false" } + + it "returns false" do + expect(converted).to be false + end + end + + context "when providing a false string" do + let(:value) { "FALSE" } + + it "returns false" do + expect(converted).to be false + end + end + + context "when providing a different string" do + let(:value) { "hello" } + + it "returns nil" do + expect(converted).to be nil + end + end + + context "when providing a different type" do + let(:value) { :hello } + + it "returns nil" do + expect(converted).to be nil + end + end + end + + describe "#revert_bool" do + + let(:method) { :revert_bool } + + context "when passing a boolean" do + let(:value) { true } + + it "returns the boolean" do + expect(reverted).to eq(value) + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + + describe "#convert_repeated_bool" do + + let(:method) { :convert_repeated_bool } + let(:value) { true } + + it "wraps the result in an array" do + expect(converted).to eq([ true ]) + end + end + + describe "#revert_repeated_bool" do + + let(:method) { :revert_repeated_bool } + + context "when passing a boolean list" do + let(:value) { [ true ] } + + it "returns the passed value" do + expect(reverted).to eq(value) + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + + describe "#convert_inverse_bool" do + + let(:method) { :convert_inverse_bool } + + context "when providing false" do + let(:value) { false } + + it "returns false" do + expect(converted).to be true + end + end + + context "when providing true" do + let(:value) { true } + + it "returns true" do + expect(converted).to be false + end + end + + context "when providing a true string" do + let(:value) { "true" } + + it "returns true" do + expect(converted).to be false + end + end + + context "when providing a capital true string" do + let(:value) { "TRUE" } + + it "returns true" do + expect(converted).to be false + end + end + + context "when providing a false string" do + let(:value) { "false" } + + it "returns false" do + expect(converted).to be true + end + end + + context "when providing a false string" do + let(:value) { "FALSE" } + + it "returns false" do + expect(converted).to be true + end + end + + context "when providing a different string" do + let(:value) { "hello" } + + it "returns nil" do + expect(converted).to be nil + end + end + + context "when providing a different type" do + let(:value) { :hello } + + it "returns nil" do + expect(converted).to be nil + end + end + end + + describe "#revert_inverse_bool" do + + let(:method) { :revert_inverse_bool } + + context "when passing true" do + let(:value) { true } + + it "returns false" do + expect(reverted).to be false + end + end + + context "when passing false" do + let(:value) { false } + + it "returns true" do + expect(reverted).to be true + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + + describe "#convert_integer" do + + let(:method) { :convert_integer } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns as an integer" do + expect(converted).to eq(1) + end + end + + context "when passing an integer string" do + let(:value) { "42" } + + it "returns as an integer" do + expect(converted).to eq(42) + end + end + + context "when passing an invalid string" do + let(:value) { "hello" } + + it "returns nil" do + expect(converted).to be nil + end + end + end + + describe "#revert_integer" do + + let(:method) { :revert_integer } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns the passed value" do + expect(reverted).to eq(value) + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + + describe "#convert_ms" do + + let(:method) { :convert_ms } + + context "when passing an integer" do + let(:value) { 1000 } + + it "returns a float divided by 1000" do + expect(converted).to eq(1.0) + end + end + + context "when passing a negative integer" do + let(:value) { -1000 } + + it "returns a float divided by 1000" do + expect(converted).to be nil + end + end + + context "when passing an integer string" do + let(:value) { "1000" } + + it "returns a float divided by 1000" do + expect(converted).to eq(1.0) + end + end + + context "when passing a negative integer string" do + let(:value) { "-1000" } + + it "returns a float divided by 1000" do + expect(converted).to be nil + end + end + + context "when passing a float string" do + let(:value) { "1000.5" } + + it "returns a float divided by 1000" do + expect(converted).to eq(1.0005) + end + end + + context "when passing a negative float string" do + let(:value) { "-1000.5" } + + it "returns a float divided by 1000" do + expect(converted).to be nil + end + end + + context "when passing a float" do + let(:value) { 1000.5 } + + it "returns a float divided by 1000" do + expect(converted).to eq(1.0005) + end + end + + context "when passing a negative float" do + let(:value) { -1000.5 } + + it "returns a float divided by 1000" do + expect(converted).to be nil + end + end + end + + describe "#revert_ms" do + + let(:method) { :revert_ms } + + context "when passing a float" do + let(:value) { 1.000005 } + + it "returns an integer" do + expect(reverted).to eq(1000) + end + end + end + + describe "#convert_symbol" do + + let(:method) { :convert_symbol } + + context "when passing a string" do + let(:value) { "hello" } + + it "returns a symbol" do + expect(converted).to eq(:hello) + end + end + + context "when passing a symbol" do + let(:value) { :hello } + + it "returns a symbol" do + expect(converted).to eq(:hello) + end + end + end + + describe "#revert_symbol" do + + let(:method) { :revert_symbol } + + context "when passing a symbol" do + let(:value) { :hello } + + it "returns it as a string" do + expect(reverted).to eq("hello") + end + end + end + + describe "#convert_array" do + + let(:method) { :convert_array } + + context "when passing a string with no commas" do + let(:value) { "hello" } + + it "returns one element" do + expect(converted).to eq([ "hello" ]) + end + end + + context "when passing a string with commas" do + let(:value) { "1,2,3" } + + it "returns multiple elements" do + expect(converted).to eq([ '1', '2', '3' ]) + end + end + end + + describe "#revert_array" do + + let(:method) { :revert_array } + + context "when passing one value" do + let(:value) { [ "hello" ] } + + it "returns the value" do + expect(reverted).to eq(value) + end + end + + context "when passing multiple value" do + let(:value) { [ "1", "2", "3" ] } + + it "returns the value" do + expect(reverted).to eq(value) + end + end + end + + describe "#convert_auth_mech" do + + let(:method) { :convert_auth_mech } + + context "when passing GSSAPI" do + let(:value) { "GSSAPI" } + + it "returns it as a symbol" do + expect(converted).to eq(:gssapi) + end + end + + context "when passing MONGODB-AWS" do + let(:value) { "MONGODB-AWS" } + + it "returns it as a symbol" do + expect(converted).to eq(:aws) + end + end + + context "when passing MONGODB-CR" do + let(:value) { "MONGODB-CR" } + + it "returns it as a symbol" do + expect(converted).to eq(:mongodb_cr) + end + end + + context "when passing MONGODB-X509" do + let(:value) { "MONGODB-X509" } + + it "returns it as a symbol" do + expect(converted).to eq(:mongodb_x509) + end + end + + context "when passing PLAIN" do + let(:value) { "PLAIN" } + + it "returns it as a symbol" do + expect(converted).to eq(:plain) + end + end + + context "when passing SCRAM-SHA-1" do + let(:value) { "SCRAM-SHA-1" } + + it "returns it as a symbol" do + expect(converted).to eq(:scram) + end + end + + context "when passing SCRAM-SHA-256" do + let(:value) { "SCRAM-SHA-256" } + + it "returns it as a symbol" do + expect(converted).to eq(:scram256) + end + end + + context "when passing a bogus value" do + let(:value) { "hello" } + + it "returns the value" do + expect(converted).to eq("hello") + end + + it "warns" do + expect(options_mapper).to receive(:log_warn).once + converted + end + end + end + + describe "#revert_auth_mech" do + + let(:method) { :revert_auth_mech } + + context "when passing GSSAPI" do + let(:value) { :gssapi } + + it "returns it as a symbol" do + expect(reverted).to eq("GSSAPI") + end + end + + context "when passing MONGODB-AWS" do + let(:value) { :aws } + + it "returns it as a symbol" do + expect(reverted).to eq("MONGODB-AWS") + end + end + + context "when passing MONGODB-CR" do + let(:value) { :mongodb_cr } + + it "returns it as a symbol" do + expect(reverted).to eq("MONGODB-CR") + end + end + + context "when passing MONGODB-X509" do + let(:value) { :mongodb_x509 } + + it "returns it as a symbol" do + expect(reverted).to eq("MONGODB-X509") + end + end + + context "when passing PLAIN" do + let(:value) { :plain } + + it "returns it as a symbol" do + expect(reverted).to eq("PLAIN") + end + end + + context "when passing SCRAM-SHA-1" do + let(:value) { :scram } + + it "returns it as a symbol" do + expect(reverted).to eq("SCRAM-SHA-1") + end + end + + context "when passing SCRAM-SHA-256" do + let(:value) { :scram256 } + + it "returns it as a symbol" do + expect(reverted).to eq("SCRAM-SHA-256") + end + end + + context "when passing a bogus value" do + let(:value) { "hello" } + + it "raises an error" do + expect do + reverted + end.to raise_error(ArgumentError, "Unknown auth mechanism hello") + end + end + + context "when passing nil" do + let(:value) { nil } + + it "raises an error" do + expect do + reverted + end.to raise_error(ArgumentError, "Unknown auth mechanism #{nil}") + end + end + end + + describe "#convert_auth_mech_props" do + + let(:method) { :convert_auth_mech_props } + + context "when including one item" do + let(:value) { "key:value" } + + it "returns a one element hash" do + expect(converted).to eq(key: "value") + end + end + + context "when including multiple items" do + let(:value) { "k1:v1,k2:v2" } + + it "returns a multiple element hash" do + expect(converted).to eq(k1: "v1", k2: "v2") + end + end + + context "when including items without a colon" do + let(:value) { "k1:v1,k2,v2" } + + it "drops those items" do + expect(converted).to eq(k1: "v1") + end + + it "warns" do + expect(options_mapper).to receive(:log_warn).twice + converted + end + end + + context "when giving the empty string" do + let(:value) { "" } + + it "returns nil" do + expect(converted).to be nil + end + end + + context "when giving no valid options" do + let(:value) { "k1,k2" } + + it "returns nil" do + expect(converted).to be nil + end + end + + context "when passing CANONICALIZE_HOST_NAME" do + + context "when passing true" do + let(:value) { "CANONICALIZE_HOST_NAME:true" } + + it "returns true as a boolean" do + expect(converted).to eq(CANONICALIZE_HOST_NAME: true) + end + end + + context "when passing uppercase true" do + let(:value) { "CANONICALIZE_HOST_NAME:TRUE" } + + it "returns true as a boolean" do + expect(converted).to eq(CANONICALIZE_HOST_NAME: true) + end + end + + context "when passing false" do + let(:value) { "CANONICALIZE_HOST_NAME:false" } + + it "returns false as a boolean" do + expect(converted).to eq(CANONICALIZE_HOST_NAME: false) + end + end + + context "when passing bogus" do + let(:value) { "CANONICALIZE_HOST_NAME:bogus" } + + it "returns false as a boolean" do + expect(converted).to eq(CANONICALIZE_HOST_NAME: false) + end + end + end + end + + describe "#revert_auth_mech_props" do + + let(:method) { :revert_auth_mech_props } + + context "when including one item" do + let(:value) { { key: "value" } } + + it "returns a one element hash" do + expect(reverted).to eq(value) + end + end + + context "when including multiple items" do + let(:value) { { k1: "v1", k2: "v2" } } + + it "returns a multiple element hash" do + expect(reverted).to eq(value) + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + + describe "#convert_max_staleness" do + + let(:method) { :convert_max_staleness } + + context "when passing a string" do + + context "when passing a positive integer" do + let(:value) { "100" } + + it "returns an integer" do + expect(converted).to eq(100) + end + end + + context "when passing a negative integer" do + let(:value) { "-100" } + + it "returns an integer" do + expect(converted).to be nil + end + + it "warns" do + expect(options_mapper).to receive(:log_warn).once + converted + end + end + + context "when passing a bogus value" do + let(:value) { "hello" } + + it "returns an integer" do + expect(converted).to be nil + end + end + end + + context "when passing an integer" do + + context "when passing a positive integer" do + let(:value) { 100 } + + it "returns an integer" do + expect(converted).to eq(100) + end + end + + context "when passing a negative integer" do + let(:value) { -100 } + + it "returns an integer" do + expect(converted).to be nil + end + + it "warns" do + expect(options_mapper).to receive(:log_warn).once + converted + end + end + + context "when passing negative 1" do + let(:value) { -1 } + + it "returns an integer" do + expect(converted).to be nil + end + + it "doesn't warn" do + expect(options_mapper).to receive(:log_warn).never + converted + end + end + + context "when passing 0" do + let(:value) { 0 } + + it "returns 0" do + expect(converted).to eq(0) + end + + it "doesn't warn" do + expect(options_mapper).to receive(:log_warn).never + converted + end + end + + context "when passing a number less than 90" do + let(:value) { 50 } + + it "returns nil" do + expect(converted).to be nil + end + end + end + + context "when passing a bogus value" do + let(:value) { :hello } + + it "returns nil" do + expect(converted).to be nil + end + end + end + + describe "#revert_max_staleness" do + + let(:method) { :revert_max_staleness } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns the integer" do + expect(reverted).to eq(1) + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + + describe "#convert_read_mode" do + + let(:method) { :convert_read_mode } + + context "when passing primary" do + let(:value) { "primary" } + + it "returns it as a symbol" do + expect(converted).to eq(:primary) + end + end + + context "when passing primarypreferred" do + let(:value) { "primarypreferred" } + + it "returns it as a symbol" do + expect(converted).to eq(:primary_preferred) + end + end + + context "when passing secondary" do + let(:value) { "secondary" } + + it "returns it as a symbol" do + expect(converted).to eq(:secondary) + end + end + + context "when passing secondarypreferred" do + let(:value) { "secondarypreferred" } + + it "returns it as a symbol" do + expect(converted).to eq(:secondary_preferred) + end + end + + context "when passing nearest" do + let(:value) { "nearest" } + + it "returns it as a symbol" do + expect(converted).to eq(:nearest) + end + end + + context "when passing capitalized primary" do + let(:value) { "Primary" } + + it "returns it as a symbol" do + expect(converted).to eq(:primary) + end + end + + context "when passing a bogus string" do + let(:value) { "hello" } + + it "returns the string" do + expect(converted).to eq(value) + end + end + end + + describe "#revert_read_mode" do + + let(:method) { :revert_read_mode } + + context "when passing primary" do + let(:value) { :primary } + + it "returns it as a symbol" do + expect(reverted).to eq("primary") + end + end + + context "when passing primarypreferred" do + let(:value) { :primary_preferred } + + it "returns it as a symbol" do + expect(reverted).to eq("primaryPreferred") + end + end + + context "when passing secondary" do + let(:value) { :secondary } + + it "returns it as a symbol" do + expect(reverted).to eq("secondary") + end + end + + context "when passing secondarypreferred" do + let(:value) { :secondary_preferred } + + it "returns it as a symbol" do + expect(reverted).to eq("secondaryPreferred") + end + end + + context "when passing nearest" do + let(:value) { :nearest } + + it "returns it as a symbol" do + expect(reverted).to eq("nearest") + end + end + + context "when passing a bogus string" do + let(:value) { "hello" } + + it "returns the string" do + expect(reverted).to eq("hello") + end + end + end + + describe "#convert_read_tags" do + + let(:method) { :convert_read_tags } + + context "when including one item" do + let(:value) { "key:value" } + + it "returns a one element hash" do + expect(converted).to eq([{ key: "value" }]) + end + end + + context "when including multiple items" do + let(:value) { "k1:v1,k2:v2" } + + it "returns a multiple element hash" do + expect(converted).to eq([{ k1: "v1", k2: "v2" }]) + end + end + + context "when including items without a colon" do + let(:value) { "k1:v1,k2,v2" } + + it "drops those items" do + expect(converted).to eq([{ k1: "v1" }]) + end + + it "warns" do + expect(options_mapper).to receive(:log_warn).twice + converted + end + end + + context "when giving the empty string" do + let(:value) { "" } + + it "returns nil" do + expect(converted).to be nil + end + end + + context "when giving no valid options" do + let(:value) { "k1,k2" } + + it "returns nil" do + expect(converted).to be nil + end + end + end + + describe "#revert_read_tags" do + + let(:method) { :revert_read_tags } + + context "when including one item" do + let(:value) { [ { key: "value" } ] } + + it "returns a one element hash" do + expect(reverted).to eq(value) + end + end + + context "when including multiple items" do + let(:value) { [ { k1: "v1", k2: "v2" } ] } + + it "returns a multiple element hash" do + expect(reverted).to eq(value) + end + end + + context "when including multiple hashes" do + let(:value) { [ { k1: "v1", k2: "v2" }, { k3: "v3", k4: "v4" } ] } + + it "returns a multiple element hash" do + expect(reverted).to eq(value) + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + + describe "#convert_w" do + + let(:method) { :convert_w } + + context "when passing majority" do + let(:value) { 'majority' } + + it "returns it as a symbol" do + expect(converted).to eq(:majority) + end + end + + context "when passing an integer string" do + let(:value) { '42' } + + it "returns it as an integer" do + expect(converted).to eq(42) + end + end + + context "when passing a bogus string" do + let(:value) { 'hello' } + + it "returns the string" do + expect(converted).to eq(value) + end + end + end + + describe "#revert_w" do + + let(:method) { :revert_w } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns an integer" do + expect(reverted).to eq(1) + end + end + + context "when passing a symbol" do + let(:value) { :majority } + + it "returns a string" do + expect(reverted).to eq("majority") + end + end + + context "when passing a string" do + let(:value) { "hello" } + + it "returns a string" do + expect(reverted).to eq(value) + end + end + end + + describe "#convert_zlib_compression_level" do + + let(:method) { :convert_zlib_compression_level } + + context "when passing an integer string" do + let(:value) { "1" } + + it "returns it as an integer" do + expect(converted).to eq(1) + end + end + + context "when passing a negative integer string" do + let(:value) { "-1" } + + it "returns it as an integer" do + expect(converted).to eq(-1) + end + end + + context "when passing a bogus string" do + let(:value) { "hello" } + + it "returns nil" do + expect(converted).to be nil + end + end + + context "when passing an integer" do + let(:value) { 1 } + + it "returns the integer" do + expect(converted).to eq(value) + end + end + + context "when passing a negative integer" do + let(:value) { -1 } + + it "returns the integer" do + expect(converted).to eq(value) + end + end + + context "when passing a out of range integer" do + let(:value) { 10 } + + it "returns nil" do + expect(converted).to be nil + end + + it "warns" do + expect(options_mapper).to receive(:log_warn).once + converted + end + end + + context "when passing a out of range negative integer" do + let(:value) { -2 } + + it "returns nil" do + expect(converted).to be nil + end + + it "warns" do + expect(options_mapper).to receive(:log_warn).once + converted + end + end + end + + describe "#revert_zlib_compression_level" do + + let(:method) { :revert_zlib_compression_level } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns an integer" do + expect(reverted).to eq(1) + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end +end From 948ebdb012f90466e7b88eff3aeaf248b47e1fe8 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 2 Sep 2022 15:51:13 +0200 Subject: [PATCH 030/198] RUBY-2989 Obtain AWS credentials for CSFLE (#2605) --- docs/reference/authentication.txt | 4 +- docs/reference/client-side-encryption.txt | 43 +++++- docs/release-notes.txt | 11 ++ lib/mongo/auth/aws/credentials_retriever.rb | 12 +- lib/mongo/crypt/binding.rb | 131 ++++++++++++++++-- lib/mongo/crypt/context.rb | 23 ++- lib/mongo/crypt/handle.rb | 10 +- lib/mongo/crypt/kms/credentials.rb | 17 ++- .../aws_credentials_retriever_spec.rb | 2 +- .../client_construction_aws_auth_spec.rb | 2 +- .../on_demand_aws_credentials_spec.rb | 49 +++++++ spec/mongo/crypt/handle_spec.rb | 19 ++- 12 files changed, 299 insertions(+), 24 deletions(-) create mode 100644 spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb diff --git a/docs/reference/authentication.txt b/docs/reference/authentication.txt index f6e7e33619..c19a3472a5 100644 --- a/docs/reference/authentication.txt +++ b/docs/reference/authentication.txt @@ -328,6 +328,8 @@ The temporary credentials can also be provided via a URI: client = Mongo::Client.new( 'mongodb://:@mongodb.example.com/mydb?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:') +.. _auto-retrieve-aws-credentials: + Automatically Retrieving Credentials ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -475,7 +477,7 @@ for more information. # Authenticate as appuser@MYREALM: client = Mongo::Client.new("mongodb://appuser%40MYREALM@myserver.mycompany.com:27017/mydb?authMechanism=GSSAPI") - + # Authenticate as myapp/appuser@MYREALM: client = Mongo::Client.new("mongodb://myapp%2Fappuser%40MYREALM@myserver.mycompany.com:27017/mydb?authMechanism=GSSAPI") diff --git a/docs/reference/client-side-encryption.txt b/docs/reference/client-side-encryption.txt index 8191c226fb..359e421a16 100644 --- a/docs/reference/client-side-encryption.txt +++ b/docs/reference/client-side-encryption.txt @@ -853,7 +853,7 @@ There is no default key vault namespace, and this option must be provided. ``:kms_providers`` ~~~~~~~~~~~~~~~~~~ -A Hash that contains KMP provider names as keys, and provider options as values. +A Hash that contains KMS provider names as keys, and provider options as values. .. code-block:: ruby @@ -864,11 +864,52 @@ A Hash that contains KMP provider names as keys, and provider options as values. aws: { access_key_id: 'IAM-ACCESS-KEY-ID', secret_access_key: 'IAM-SECRET-ACCESS-KEY' + }, + azure: { + tenant_id: 'AZURE-TENANT-ID', + client_id: 'AZURE-CLIENT-ID', + client_secret: 'AZURE-CLIENT-SECRET' + }, + gcp: { + email: 'GCP-EMAIL', + # :private_key value should be GCP private key as base64 encoded + # DER RSA private key, or PEM RSA private key, if you are using MRI Ruby. + private_key: 'GCP-PRIVATE-KEY', + }, + kmip: { + # KMIP server endpoint may include port. + endpoint: 'KMIP-SERVER-HOST' + }, + # TLS options to connect to KMIP server. + kms_tls_options: { + kmip: { + ssl_ca_cert: 'PATH-TO-CA-FILE', + ssl_cert: 'PATH-TO-CLIENT-CERT-FILE', + ssl_key: 'PATH-TO-CLIENT-KEY-FILE' + } } } } ) +The client can retrieve AWS credentials from the environment or from EC2 or ECS +metadata endpoints. To retrieve credentials automatically, specify an empty Hash +as KMS provider options for AWS: + +.. code-block:: ruby + + Mongo::Client.new(['localhost:27017'], + auto_encryption_options: { + key_vault_namespace: 'encryption.__keyVault', + kms_providers: { + aws: {} + } + } + ) + +See :ref:`"Automatically Retrieving Credentials" ` +for more detailed information about the credential retrieval. + ``:kms_tls_options`` ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/release-notes.txt b/docs/release-notes.txt index a4fa945f7b..a8687efe20 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -18,6 +18,17 @@ for the complete list of changes, including those internal to the driver and its test suite. +.. _release-notes-2.19: + +2.19 +==== + +This release includes the following new features: + +- Added support for automatic AWS credentials retrieval when AWS KMS is used for + client side encryption. + + .. _release-notes-2.18: 2.18 diff --git a/lib/mongo/auth/aws/credentials_retriever.rb b/lib/mongo/auth/aws/credentials_retriever.rb index 68092aff9c..4943665bb7 100644 --- a/lib/mongo/auth/aws/credentials_retriever.rb +++ b/lib/mongo/auth/aws/credentials_retriever.rb @@ -19,6 +19,15 @@ module Mongo module Auth class Aws + # Raised when trying to authorize with an invalid configuration + # + # @api private + class CredentialsNotFound < Mongo::Error::AuthError + def initialize + super("Could not locate AWS credentials (checked Client URI and Ruby options, environment variables, ECS and EC2 metadata)") + end + end + # Retrieves AWS credentials from a variety of sources. # # This class provides for AWS credentials retrieval from: @@ -98,8 +107,7 @@ def credentials return credentials end - raise Auth::InvalidConfiguration, - "Could not locate AWS credentials (checked Client URI and Ruby options, environment variables, ECS and EC2 metadata)" + raise Auth::Aws::CredentialsNotFound end private diff --git a/lib/mongo/crypt/binding.rb b/lib/mongo/crypt/binding.rb index de0746792b..b0a6cbd6ad 100644 --- a/lib/mongo/crypt/binding.rb +++ b/lib/mongo/crypt/binding.rb @@ -775,13 +775,14 @@ def self.ctx_explicit_decrypt_init(context, doc) # An enum labeling different libmognocrypt state machine states enum :mongocrypt_ctx_state, [ - :error, 0, - :need_mongo_collinfo, 1, - :need_mongo_markings, 2, - :need_mongo_keys, 3, - :need_kms, 4, - :ready, 5, - :done, 6, + :error, 0, + :need_mongo_collinfo, 1, + :need_mongo_markings, 2, + :need_mongo_keys, 3, + :need_kms, 4, + :ready, 5, + :done, 6, + :need_kms_credentials, 7, ] # @!method self.mongocrypt_ctx_state(ctx) @@ -1463,19 +1464,129 @@ def self.setopt_set_crypt_shared_lib_path_override(handle, path) end end - # MONGOCRYPT_EXPORT - # uint64_t - # mongocrypt_crypt_shared_lib_version (const mongocrypt_t *crypt); + # @!method self.mongocrypt_crypt_shared_lib_version(crypt) + # @api private + # + # Obtain a 64-bit constant encoding the version of the loaded + # crypt_shared library, if available. + # + # The version is encoded as four 16-bit numbers, from high to low: + # + # - Major version + # - Minor version + # - Revision + # - Reserved + # + # For example, version 6.2.1 would be encoded as: 0x0006'0002'0001'0000 + # + # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object. + # + # @return [int64] A 64-bit encoded version number, with the version encoded as four + # sixteen-bit integers, or zero if no crypt_shared library was loaded. attach_function( :mongocrypt_crypt_shared_lib_version, [ :pointer ], :uint64 ) + # Obtain a 64-bit constant encoding the version of the loaded + # crypt_shared library, if available. + # + # The version is encoded as four 16-bit numbers, from high to low: + # + # - Major version + # - Minor version + # - Revision + # - Reserved + # + # For example, version 6.2.1 would be encoded as: 0x0006'0002'0001'0000 + # + # @param [ Mongo::Crypt::Handle ] handle + # + # @return [ Integer ] A 64-bit encoded version number, with the version encoded as four + # sixteen-bit integers, or zero if no crypt_shared library was loaded. def self.crypt_shared_lib_version(handle) mongocrypt_crypt_shared_lib_version(handle.ref) end + # @!method self.mongocrypt_setopt_use_need_kms_credentials_state(crypt) + # @api private + # + # Opt-into handling the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state. + # + # If set, before entering the MONGOCRYPT_CTX_NEED_KMS state, + # contexts may enter the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state + # and then wait for credentials to be supplied through + # `mongocrypt_ctx_provide_kms_providers`. + # + # A context will only enter MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS + # if an empty document was set for a KMS provider in + # `mongocrypt_setopt_kms_providers`. + # + # @param [ FFI::Pointer ] crypt A pointer to a mongocrypt_t object. + attach_function( + :mongocrypt_setopt_use_need_kms_credentials_state, + [ :pointer ], + :void + ) + + # Opt-into handling the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state. + # + # If set, before entering the MONGOCRYPT_CTX_NEED_KMS state, + # contexts may enter the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state + # and then wait for credentials to be supplied through + # `mongocrypt_ctx_provide_kms_providers`. + # + # A context will only enter MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS + # if an empty document was set for a KMS provider in + # `mongocrypt_setopt_kms_providers`. + # + # @param [ Mongo::Crypt::Handle ] handle + def self.setopt_use_need_kms_credentials_state(handle) + mongocrypt_setopt_use_need_kms_credentials_state(handle.ref) + end + + # @!method self.mongocrypt_ctx_provide_kms_providers(ctx, kms_providers) + # @api private + # + # Call in response to the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state + # to set per-context KMS provider settings. These follow the same format + # as `mongocrypt_setopt_kms_providers``. If no keys are present in the + # BSON input, the KMS provider settings configured for the mongocrypt_t + # at initialization are used. + # + # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object. + # @param [ FFI::Pointer ] kms_providers A pointer to a + # mongocrypt_binary_t object that references a BSON document mapping + # the KMS provider names to credentials. + # + # @returns [ true | false ] Returns whether the options was set successfully. + attach_function( + :mongocrypt_ctx_provide_kms_providers, + [ :pointer, :pointer ], + :bool + ) + + # Call in response to the MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS state + # to set per-context KMS provider settings. These follow the same format + # as `mongocrypt_setopt_kms_providers``. If no keys are present in the + # BSON input, the KMS provider settings configured for the mongocrypt_t + # at initialization are used. + # + # @param [ Mongo::Crypt::Context ] context Encryption context. + # @param [ BSON::Document ] kms_providers BSON document mapping + # the KMS provider names to credentials. + # + # @raise [ Mongo::Error::CryptError ] If the option is not set successfully. + def self.ctx_provide_kms_providers(context, kms_providers) + validate_document(kms_providers) + data = kms_providers.to_bson.to_s + Binary.wrap_string(data) do |data_p| + check_ctx_status(context) do + mongocrypt_ctx_provide_kms_providers(context.ctx_p, data_p) + end + end + end # @!method self.mongocrypt_ctx_setopt_query_type(ctx, mongocrypt_query_type) # @api private diff --git a/lib/mongo/crypt/context.rb b/lib/mongo/crypt/context.rb index 99d936f94b..c5167e3619 100644 --- a/lib/mongo/crypt/context.rb +++ b/lib/mongo/crypt/context.rb @@ -27,6 +27,10 @@ module Crypt # # @api private class Context + extend Forwardable + + def_delegators :@mongocrypt_handle, :kms_providers + # Create a new Context object # # @param [ Mongo::Crypt::Handle ] mongocrypt_handle A handle to libmongocrypt @@ -110,11 +114,24 @@ def run_state_machine end Binding.ctx_kms_done(self) + when :need_kms_credentials + providers = {} + if kms_providers.aws&.empty? + begin + aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials + rescue Auth::Aws::CredentialsNotFound + raise Error::CryptError.new( + "Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)" + ) + end + providers[:aws] = aws_credentials.to_h + end + Binding.ctx_provide_kms_providers( + self, + KMS::Credentials.new(providers).to_document + ) else raise Error::CryptError.new( - # TODO: fix CryptError to improve this API -- the first argument - # in the initializer should not be optional - nil, "State #{state} is not supported by Mongo::Crypt::Context" ) end diff --git a/lib/mongo/crypt/handle.rb b/lib/mongo/crypt/handle.rb index 47626184a8..596c24e02b 100644 --- a/lib/mongo/crypt/handle.rb +++ b/lib/mongo/crypt/handle.rb @@ -28,6 +28,9 @@ module Crypt # @api private class Handle + # @returns [ Crypt::KMS::Credentials ] Credentials for KMS providers. + attr_reader :kms_providers + # Creates a new Handle object and initializes it with options # # @param [ Crypt::KMS::Credentials ] kms_providers Credentials for KMS providers. @@ -69,6 +72,7 @@ def initialize(kms_providers, kms_tls_options, options={}) Binding.method(:mongocrypt_destroy) ) + @kms_providers = kms_providers @kms_tls_options = kms_tls_options maybe_set_schema_map(options) @@ -92,7 +96,11 @@ def initialize(kms_providers, kms_tls_options, options={}) set_crypto_hooks - Binding.setopt_kms_providers(self, kms_providers.to_document) + Binding.setopt_kms_providers(self, @kms_providers.to_document) + + if @kms_providers.aws&.empty? + Binding.setopt_use_need_kms_credentials_state(self) + end initialize_mongocrypt diff --git a/lib/mongo/crypt/kms/credentials.rb b/lib/mongo/crypt/kms/credentials.rb index 4c3cae173f..d7dd0432a3 100644 --- a/lib/mongo/crypt/kms/credentials.rb +++ b/lib/mongo/crypt/kms/credentials.rb @@ -24,6 +24,21 @@ module KMS # @api private class Credentials + # @return [ Credentials::AWS | nil ] AWS KMS credentials. + attr_reader :aws + + # @return [ Credentials::Azure | nil ] Azure KMS credentials. + attr_reader :azure + + # @return [ Credentials::GCP | nil ] GCP KMS credentials. + attr_reader :gcp + + # @return [ Credentials::KMIP | nil ] KMIP KMS credentials. + attr_reader :kmip + + # @return [ Credentials::Local | nil ] Local KMS credentials. + attr_reader :local + # Creates a KMS credentials object form a parameters hash. # # @param [ Hash ] kms_providers A hash that contains credential for @@ -67,7 +82,7 @@ def initialize(kms_providers) # # @return [ BSON::Document ] Credentials as BSON document. def to_document - BSON::Document.new({}).tap do |bson| + BSON::Document.new.tap do |bson| bson[:aws] = @aws.to_document if @aws bson[:azure] = @azure.to_document if @azure bson[:gcp] = @gcp.to_document if @gcp diff --git a/spec/integration/aws_credentials_retriever_spec.rb b/spec/integration/aws_credentials_retriever_spec.rb index 939e711f12..f9d66cb5a0 100644 --- a/spec/integration/aws_credentials_retriever_spec.rb +++ b/spec/integration/aws_credentials_retriever_spec.rb @@ -70,7 +70,7 @@ it 'raises an error' do lambda do credentials - end.should raise_error(Mongo::Auth::InvalidConfiguration, /Could not locate AWS credentials/) + end.should raise_error(Mongo::Auth::Aws::CredentialsNotFound, /Could not locate AWS credentials/) end end diff --git a/spec/integration/client_construction_aws_auth_spec.rb b/spec/integration/client_construction_aws_auth_spec.rb index 170d5c4914..e1a1da069b 100644 --- a/spec/integration/client_construction_aws_auth_spec.rb +++ b/spec/integration/client_construction_aws_auth_spec.rb @@ -125,7 +125,7 @@ it 'does not connect' do lambda do client['foo'].insert_one(test: true) - end.should raise_error(Mongo::Auth::InvalidConfiguration, /Could not locate AWS credentials/) + end.should raise_error(Mongo::Auth::Aws::CredentialsNotFound, /Could not locate AWS credentials/) end end diff --git a/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb b/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb new file mode 100644 index 0000000000..94423e0b3e --- /dev/null +++ b/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'spec_helper' + +describe 'On-demand AWS Credentials' do + require_libmongocrypt + include_context 'define shared FLE helpers' + include_context 'with AWS kms_providers' + + let(:client) { ClientRegistry.instance.new_local_client(SpecConfig.instance.addresses) } + + let(:client_encryption_opts) do + { + kms_providers: { aws: {} }, + kms_tls_options: kms_tls_options, + key_vault_namespace: key_vault_namespace + } + end + + let(:client_encryption) do + Mongo::ClientEncryption.new( + client, + client_encryption_opts + ) + end + + context 'when credentials are available' do + it 'authenticates successfully' do + expect do + client_encryption.create_data_key('aws', data_key_options) + end.not_to raise_error + end + end + + context 'when credentials are not available' do + it 'raises an error' do + expect_any_instance_of( + Mongo::Auth::Aws::CredentialsRetriever + ).to receive(:credentials).with(no_args).once.and_raise( + Mongo::Auth::Aws::CredentialsNotFound + ) + + expect do + client_encryption.create_data_key('aws', data_key_options) + end.to raise_error(Mongo::Error::CryptError, /Could not locate AWS credentials/) + end + end +end diff --git a/spec/mongo/crypt/handle_spec.rb b/spec/mongo/crypt/handle_spec.rb index 6a2d31dde6..208aaae4b2 100644 --- a/spec/mongo/crypt/handle_spec.rb +++ b/spec/mongo/crypt/handle_spec.rb @@ -3,7 +3,7 @@ require 'mongo' require 'base64' -require 'lite_spec_helper' +require 'spec_helper' describe Mongo::Crypt::Handle do require_libmongocrypt @@ -156,7 +156,7 @@ end it 'does not load the crypt shared lib' do - expect_any_instance_of(Binding).not_to receive(:setopt_append_crypt_shared_lib_search_path) + expect(Mongo::Crypt::Binding).not_to receive(:setopt_append_crypt_shared_lib_search_path) expect(handle.crypt_shared_lib_version).to eq(0) end @@ -170,7 +170,7 @@ end it 'does not load the crypt shared lib' do - expect_any_instance_of(Binding).not_to receive(:setopt_append_crypt_shared_lib_search_path) + expect(Mongo::Crypt::Binding).not_to receive(:setopt_append_crypt_shared_lib_search_path) expect(handle.crypt_shared_lib_version).to eq(0) end @@ -204,6 +204,19 @@ include_context 'with AWS kms_providers' it_behaves_like 'a functioning Mongo::Crypt::Handle' end + + context 'with empty AWS kms_providers' do + let(:kms_providers) do + { + aws: {} + } + end + + it 'instructs libmongocrypt to handle empty AWS credentials' do + expect(Mongo::Crypt::Binding).to receive(:setopt_use_need_kms_credentials_state).once + handle + end + end end context 'Azure' do From 6c5cdcf8d6a2d7a90791f128847e88e1ab4a1eed Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> Date: Fri, 2 Sep 2022 11:32:44 -0400 Subject: [PATCH 031/198] RUBY-3095 remove readConcernLevel special case (#2609) Co-authored-by: Oleg Pudeyev --- lib/mongo/uri/options_mapper.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/mongo/uri/options_mapper.rb b/lib/mongo/uri/options_mapper.rb index d3e431c420..5243e0c331 100644 --- a/lib/mongo/uri/options_mapper.rb +++ b/lib/mongo/uri/options_mapper.rb @@ -102,10 +102,6 @@ def smc_to_ruby(opts) uri_options end - if key == 'readConcernLevel' - value = value.to_sym - end - value = apply_transform(key, value, strategy[:type]) # Sometimes the value here would be nil, for example if we are processing # read preference tags or auth mechanism properties and all of the From a71064f7cb1b59d262cf013104f45615eefe846a Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Sun, 4 Sep 2022 11:30:16 -0400 Subject: [PATCH 032/198] RUBY-3106 add spec test for ChangeStreamEvent type definition to include clusterTime (#2613) --- .../change-streams-clusterTime.yml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml b/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml new file mode 100644 index 0000000000..0a547bac7d --- /dev/null +++ b/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml @@ -0,0 +1,40 @@ +description: "change-streams-clusterTime" +schemaVersion: "1.3" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] + +initialData: + - collectionName: *collection0 + databaseName: *database0 + documents: [] + +tests: + - description: "clusterTime is present" + operations: + - name: createChangeStream + object: *collection0 + arguments: { pipeline: [] } + saveResultAsEntity: &changeStream0 changeStream0 + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1 } + - name: iterateUntilDocumentOrError + object: *changeStream0 + expectResult: + ns: { db: *database0, coll: *collection0 } + clusterTime: { $$exists: true } From 050f408f5f42690c515c4ff85453bac77b84745b Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Sun, 4 Sep 2022 11:30:32 -0400 Subject: [PATCH 033/198] RUBY-3103 Exclude create events from change stream events when testing showExpandedEvents (#2612) --- .../change-streams-showExpandedEvents.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml b/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml index fa4360c074..c0443d0e22 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml @@ -160,7 +160,15 @@ tests: - name: createChangeStream object: *collection0 arguments: - pipeline: [] + pipeline: + # On sharded clusters, the create command run when loading initial + # data sometimes is still reported in the change stream. To avoid + # this, we exclude the create command when creating the change + # stream, but specifically don't exclude other events to still catch + # driver errors. + - $match: + operationType: + $ne: create showExpandedEvents: true saveResultAsEntity: &changeStream0 changeStream0 - name: createIndex @@ -259,9 +267,7 @@ tests: arguments: command: collMod: *collection0 - # Added here to fix our tests as we require a commandName but don't - # do anything with it. - commandName: modify_collection + commandName: collMod - name: iterateUntilDocumentOrError object: *changeStream0 expectResult: @@ -292,6 +298,7 @@ tests: shardCollection: shardedDb.shardedCollection key: _id: 1 + commandName: shardCollection - name: iterateUntilDocumentOrError object: *changeStream0 expectResult: From ede6e1092a2bc12530714e7e4e15da6884adcc26 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 6 Sep 2022 11:32:32 +0200 Subject: [PATCH 034/198] RUBY-3099 Add Ruby 3.1 to compat tables (#2614) --- docs/reference/driver-compatibility.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/reference/driver-compatibility.txt b/docs/reference/driver-compatibility.txt index ce0af48381..0a65033a24 100644 --- a/docs/reference/driver-compatibility.txt +++ b/docs/reference/driver-compatibility.txt @@ -331,6 +331,7 @@ for that Ruby version is deprecated. :class: compatibility-large no-padding * - Ruby Driver + - Ruby 3.1 - Ruby 3.0 - Ruby 2.7 - Ruby 2.6 @@ -350,6 +351,7 @@ for that Ruby version is deprecated. - |checkmark| - |checkmark| - |checkmark| + - |checkmark| - - - @@ -365,6 +367,7 @@ for that Ruby version is deprecated. - |checkmark| - |checkmark| - |checkmark| + - |checkmark| - - - @@ -376,6 +379,7 @@ for that Ruby version is deprecated. - * - 2.16 + - - |checkmark| - |checkmark| - |checkmark| @@ -391,6 +395,7 @@ for that Ruby version is deprecated. - * - 2.15 + - - |checkmark| - |checkmark| - |checkmark| @@ -406,6 +411,7 @@ for that Ruby version is deprecated. - * - 2.14 + - - - |checkmark| - |checkmark| @@ -421,6 +427,7 @@ for that Ruby version is deprecated. - * - 2.13 + - - - |checkmark| - |checkmark| @@ -436,6 +443,7 @@ for that Ruby version is deprecated. - * - 2.12 + - - - |checkmark| - |checkmark| @@ -451,6 +459,7 @@ for that Ruby version is deprecated. - * - 2.11 + - - - |checkmark| - |checkmark| @@ -466,6 +475,7 @@ for that Ruby version is deprecated. - * - 2.10 + - - - |checkmark| - |checkmark| @@ -481,6 +491,7 @@ for that Ruby version is deprecated. - |checkmark| * - 2.9 + - - - - |checkmark| @@ -496,6 +507,7 @@ for that Ruby version is deprecated. - |checkmark| * - 2.8 + - - - - |checkmark| @@ -511,6 +523,7 @@ for that Ruby version is deprecated. - |checkmark| * - 2.7 + - - - - |checkmark| @@ -526,6 +539,7 @@ for that Ruby version is deprecated. - |checkmark| * - 2.6 + - - - - |checkmark| From 3b2c5b5d34331ac9fb6b9ce160d475680d0b631a Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Tue, 6 Sep 2022 13:58:17 -0400 Subject: [PATCH 035/198] RUBY-3104 support all options in collection create (#2610) * RUBY-3104 support all options in collection create * RUBY-3104 fix test failures * RUBY-3104 update comment --- lib/mongo/collection.rb | 10 +-- spec/mongo/collection_ddl_spec.rb | 104 ++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index b763824d3f..734185f90b 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -372,13 +372,15 @@ def capped? # @since 2.0.0 def create(opts = {}) # Passing read options to create command causes it to break. - # Filter the read options out. + # Filter the read options out. Session is also excluded here as it gets + # used by the call to with_session and should not be part of the + # operation. If it gets passed to the operation it would fail BSON + # serialization. # TODO put the list of read options in a class-level constant when # we figure out what the full set of them is. - options = Hash[self.options.reject do |key, value| - %w(read read_preference read_concern).include?(key.to_s) + options = Hash[self.options.merge(opts).reject do |key, value| + %w(read read_preference read_concern session).include?(key.to_s) end] - options.update(opts.slice(*CREATE_COLLECTION_OPTIONS.keys)) # Converting Ruby options to server style. CREATE_COLLECTION_OPTIONS.each do |ruby_key, server_key| if options.key?(ruby_key) diff --git a/spec/mongo/collection_ddl_spec.rb b/spec/mongo/collection_ddl_spec.rb index b3e31a2569..b07e676317 100644 --- a/spec/mongo/collection_ddl_spec.rb +++ b/spec/mongo/collection_ddl_spec.rb @@ -129,6 +129,47 @@ it_behaves_like 'a validated collection command' end end + + context 'when instantiating a collection using create' do + + before do + authorized_client[:specs].drop + end + + let!(:response) do + authorized_client[:specs].create(options) + end + + let(:collection) do + authorized_client[:specs] + end + + let(:collstats) do + database.read_command(:collstats => :specs).documents.first + end + + let(:options) do + { :capped => true, :size => 4096, :max => 512 } + end + + it 'executes the command' do + expect(response).to be_successful + end + + it 'sets the collection as capped' do + expect(collection).to be_capped + end + + it 'creates the collection in the database' do + expect(database.collection_names).to include('specs') + end + + it "applies the options" do + expect(collstats["capped"]).to be true + expect(collstats["max"]).to eq(512) + expect(collstats["maxSize"]).to eq(4096) + end + end end context 'when the collection has a write concern' do @@ -278,6 +319,47 @@ it_behaves_like 'a collection command with a collation option' end + + context 'when passing the options through create' do + + let(:collection) do + authorized_client[:specs] + end + + let(:response) do + collection.create(options) + end + + let(:options) do + { :collation => { locale: 'fr' } } + end + + let(:collection_info) do + database.list_collections.find { |i| i['name'] == 'specs' } + end + + before do + collection.drop + end + + context 'when the server supports collations' do + min_server_fcv '3.4' + + it 'executes the command' do + expect(response).to be_successful + end + + it 'sets the collection with a collation' do + response + expect(collection_info['options']['collation']['locale']).to eq('fr') + end + + it 'creates the collection in the database' do + response + expect(database.collection_names).to include('specs') + end + end + end end context 'when a session is provided' do @@ -480,6 +562,28 @@ expect(collection.drop).to be(false) end end + + context "when providing a pipeline in create" do + + let(:options) do + { view_on: "specs", pipeline: [ { :'$project' => { "baz": "$bar" } } ] } + end + + before do + authorized_client["my_view"].drop + authorized_client[:specs].drop + end + + it "the pipeline gets passed to the command" do + expect(Mongo::Operation::Create).to receive(:new).and_wrap_original do |m, *args| + expect(args.first.slice(:selector)[:selector]).to have_key(:pipeline) + expect(args.first.slice(:selector)[:selector]).to have_key(:viewOn) + m.call(*args) + end + expect_any_instance_of(Mongo::Operation::Create).to receive(:execute) + authorized_client[:specs].create(options) + end + end end describe '#indexes' do From de292d7c90f62823c05ce1d70efb0135a6645bd6 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Tue, 6 Sep 2022 16:15:30 -0400 Subject: [PATCH 036/198] RUBY-3104 remove tests for server < 3.6 (#2611) --- spec/mongo/collection_ddl_spec.rb | 102 ++++++------------------------ 1 file changed, 21 insertions(+), 81 deletions(-) diff --git a/spec/mongo/collection_ddl_spec.rb b/spec/mongo/collection_ddl_spec.rb index b07e676317..36ae3337ae 100644 --- a/spec/mongo/collection_ddl_spec.rb +++ b/spec/mongo/collection_ddl_spec.rb @@ -109,11 +109,7 @@ end it_behaves_like 'a capped collection command' - - context 'when validators can be set' do - min_server_fcv '3.2' - it_behaves_like 'a validated collection command' - end + it_behaves_like 'a validated collection command' end context 'when instantiating a collection through the database' do @@ -123,11 +119,7 @@ end it_behaves_like 'a capped collection command' - - context 'when validators can be set' do - min_server_fcv '3.2' - it_behaves_like 'a validated collection command' - end + it_behaves_like 'a validated collection command' end context 'when instantiating a collection using create' do @@ -189,7 +181,6 @@ end context 'when the server supports write concern on the create command' do - min_server_fcv '3.4' require_topology :replica_set it 'applies the write concern' do @@ -200,7 +191,6 @@ end context 'when write concern passed in as an option' do - min_server_fcv '3.4' require_topology :replica_set before do @@ -230,14 +220,6 @@ expect(command[:writeConcern][:w]).to eq(2) end end - - context 'when the server does not support write concern on the create command' do - max_server_version '3.2' - - it 'does not apply the write concern' do - expect(collection.create).to be_successful - end - end end context 'when the collection has a collation' do @@ -260,45 +242,18 @@ collection.drop end - context 'when the server supports collations' do - min_server_fcv '3.4' - - it 'executes the command' do - expect(response).to be_successful - end - - it 'sets the collection with a collation' do - response - expect(collection_info['options']['collation']['locale']).to eq('fr') - end - - it 'creates the collection in the database' do - response - expect(database.collection_names).to include('specs') - end + it 'executes the command' do + expect(response).to be_successful end - context 'when the server does not support collations' do - max_server_version '3.2' - - it 'raises an error' do - expect { - response - }.to raise_exception(Mongo::Error::UnsupportedCollation) - end - - context 'when a String key is used' do - - let(:options) do - { 'collation' => { locale: 'fr' } } - end + it 'sets the collection with a collation' do + response + expect(collection_info['options']['collation']['locale']).to eq('fr') + end - it 'raises an exception' do - expect { - response - }.to raise_exception(Mongo::Error::UnsupportedCollation) - end - end + it 'creates the collection in the database' do + response + expect(database.collection_names).to include('specs') end end @@ -342,22 +297,18 @@ collection.drop end - context 'when the server supports collations' do - min_server_fcv '3.4' - - it 'executes the command' do - expect(response).to be_successful - end + it 'executes the command' do + expect(response).to be_successful + end - it 'sets the collection with a collation' do - response - expect(collection_info['options']['collation']['locale']).to eq('fr') - end + it 'sets the collection with a collation' do + response + expect(collection_info['options']['collation']['locale']).to eq('fr') + end - it 'creates the collection in the database' do - response - expect(database.collection_names).to include('specs') - end + it 'creates the collection in the database' do + response + expect(database.collection_names).to include('specs') end end end @@ -394,7 +345,6 @@ end context 'when collation has a strength' do - min_server_fcv '3.4' let(:band_collection) do described_class.new(database, :bands) @@ -500,7 +450,6 @@ end context 'when the server supports write concern on the drop command' do - min_server_fcv '3.4' require_set_write_concern it 'applies the write concern' do @@ -511,7 +460,6 @@ end context 'when write concern passed in as an option' do - min_server_fcv '3.4' require_set_write_concern let(:events) do @@ -537,14 +485,6 @@ expect(command[:writeConcern][:w]).to eq(0) end end - - context 'when the server does not support write concern on the drop command' do - max_server_version '3.2' - - it 'does not apply the write concern' do - expect(collection_with_write_options.drop).to be_successful - end - end end end From 06f654c83bb678f74cf5566668d55909a0a4183e Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 8 Sep 2022 11:35:13 +0200 Subject: [PATCH 037/198] RUBY-3105 Do not raise errors when closing cursor (#2615) Co-authored-by: Neil Shweky --- lib/mongo/cursor.rb | 5 ++--- spec/mongo/cursor_spec.rb | 25 +++++++++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index 9254774ede..0d3f3276d7 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -276,9 +276,6 @@ def closed? # the server. # # @return [ nil ] Always nil. - # - # @raise [ Error::OperationFailure ] If the server cursor close fails. - # @raise [ Error::SocketError | Error::SocketTimeoutError ] When there is a network error. def close return if closed? @@ -294,6 +291,8 @@ def close end nil + rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError + # Errors are swallowed since there is noting can be done by handling them. ensure end_session @cursor_id = 0 diff --git a/spec/mongo/cursor_spec.rb b/spec/mongo/cursor_spec.rb index 64dedbde7e..cc99640afc 100644 --- a/spec/mongo/cursor_spec.rb +++ b/spec/mongo/cursor_spec.rb @@ -697,7 +697,11 @@ describe '#close' do let(:view) do - Mongo::Collection::View.new(authorized_collection) + Mongo::Collection::View.new( + authorized_collection, + {}, + batch_size: 2, + ) end let(:server) do @@ -712,7 +716,17 @@ described_class.new(view, reply, server) end + let(:documents) do + (1..10).map{ |i| { field: "test#{i}" }} + end + + before do + authorized_collection.drop + authorized_collection.insert_many(documents) + end + it 'closes' do + expect(cursor).not_to be_closed cursor.close expect(cursor).to be_closed end @@ -720,17 +734,16 @@ context 'when there is a socket error during close' do clean_slate - before do + it 'does not raise an error' do + cursor server.with_connection do |conn| expect(conn).to receive(:deliver) + .at_least(:once) .and_raise(Mongo::Error::SocketError, "test error") end - end - - it 'raises an error' do expect do cursor.close - end.to raise_error(Mongo::Error::SocketError, "test error") + end.not_to raise_error end end end From 135e299e7bd79e237595ca723f46e75bfef87844 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 9 Sep 2022 14:04:20 -0400 Subject: [PATCH 038/198] RUBY-3079 Propagate Original Error for Write Errors Labeled NoWritesPerformed (#2616) * RUBY-3079 Propagate Original Error for Write Errors Labeled NoWritesPerformed * Update spec/integration/retryable_writes_errors_spec.rb * RUBY-3079 fix test description --- lib/mongo/retryable.rb | 2 +- .../retryable_writes_errors_spec.rb | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/mongo/retryable.rb b/lib/mongo/retryable.rb index f53fc5d472..32aca9b61b 100644 --- a/lib/mongo/retryable.rb +++ b/lib/mongo/retryable.rb @@ -499,7 +499,7 @@ def retry_write(original_error, txn_num, context:, &block) raise e rescue Error::OperationFailure => e e.add_note('modern retry') - if e.label?('RetryableWriteError') + if e.label?('RetryableWriteError') && !e.label?('NoWritesPerformed') e.add_note('attempt 2') raise e else diff --git a/spec/integration/retryable_writes_errors_spec.rb b/spec/integration/retryable_writes_errors_spec.rb index 895d06db48..ef120a98b5 100644 --- a/spec/integration/retryable_writes_errors_spec.rb +++ b/spec/integration/retryable_writes_errors_spec.rb @@ -31,4 +31,61 @@ end end end + + context "when encountering a NoWritesPerformed error after an error with a RetryableWriteError label" do + require_topology :replica_set + require_fail_command + + let(:failpoint1) do + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + writeConcernError: { + code: 91, + errorLabels: ["RetryableWriteError"], + }, + failCommands: ["insert"], + } + } + end + + let(:failpoint2) do + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + errorCode: 10107, + errorLabels: ["RetryableWriteError", "NoWritesPerformed"], + failCommands: ["insert"], + }, + } + end + + let(:subscriber) { Mrss::EventSubscriber.new } + + before do + authorized_client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + authorized_client.use(:admin).command(failpoint1) + + expect(authorized_collection).to receive(:retry_write).once.and_wrap_original do |m, *args, &block| + expect(args.first.code).to eq(91) + authorized_client.use(:admin).command(failpoint2) + m.call(*args, &block) + end + end + + after do + authorized_client.use(:admin).command({ + configureFailPoint: "failCommand", + mode: "off", + }) + end + + it "returns the original error" do + expect do + authorized_collection.insert_one(x: 1) + end.to raise_error(Mongo::Error::OperationFailure, /\[91\]/) + end + end end From b9090dec1b2abd80aabcd378e473903231334b42 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 12 Sep 2022 12:52:03 -0400 Subject: [PATCH 039/198] RUBY-3113 add client param to sdam_proc call (#2619) --- spec/runners/unified/test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index 67132457d1..d121fce8af 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -149,7 +149,7 @@ def generate_entities(es) subscriber = EventSubscriber.new current_proc = opts[:sdam_proc] opts[:sdam_proc] = lambda do |client| - current_proc.call if current_proc + current_proc.call(client) if current_proc if oe = observe_events oe.each do |event| case event From 5e7d71e6f2b6a7b0ca63cca6b41244155182a040 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 12 Sep 2022 12:54:36 -0400 Subject: [PATCH 040/198] RUBY-3108 Forbid Serverless in Change Stream Disambiguated Unified Spec Tests (#2618) --- .../change-streams-disambiguatedPaths.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml b/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml index 674c876b02..a2524d23ce 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml @@ -1,5 +1,5 @@ description: "disambiguatedPaths" -schemaVersion: "1.3" +schemaVersion: "1.4" createEntities: - client: id: &client0 client0 @@ -16,6 +16,7 @@ createEntities: runOnRequirements: - minServerVersion: "6.1.0" topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] + serverless: forbid initialData: - collectionName: *collection0 From 6ffed650d3abf130b32efa1108b652b672ee1c79 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 12 Sep 2022 12:54:48 -0400 Subject: [PATCH 041/198] RUBY-3111 Fix test failures in BSON master cause by the changing of the Illegal Key error (#2617) * RUBY-3111 Fix test failures in BSON master cause by the changing of the Illegal Key error * RUBY-3111 add min server version on retryable_writes_test * RUBY-3111 change to 4.4+ --- spec/integration/retryable_writes_errors_spec.rb | 1 + spec/mongo/protocol/caching_hash_spec.rb | 1 + spec/mongo/protocol/msg_spec.rb | 1 + spec/runners/transactions/operation.rb | 14 ++++++++++---- spec/runners/unified/test.rb | 11 ++++++++++- spec/support/constraints.rb | 14 ++++++++++++++ 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/spec/integration/retryable_writes_errors_spec.rb b/spec/integration/retryable_writes_errors_spec.rb index ef120a98b5..4f1cffdc76 100644 --- a/spec/integration/retryable_writes_errors_spec.rb +++ b/spec/integration/retryable_writes_errors_spec.rb @@ -35,6 +35,7 @@ context "when encountering a NoWritesPerformed error after an error with a RetryableWriteError label" do require_topology :replica_set require_fail_command + min_server_version '4.4' let(:failpoint1) do { diff --git a/spec/mongo/protocol/caching_hash_spec.rb b/spec/mongo/protocol/caching_hash_spec.rb index cbf717faca..56d79bc0ef 100644 --- a/spec/mongo/protocol/caching_hash_spec.rb +++ b/spec/mongo/protocol/caching_hash_spec.rb @@ -36,6 +36,7 @@ end describe "#needs_validation?" do + max_bson_version "4.99.99" before do hash.to_bson(BSON::ByteBuffer.new, validation) diff --git a/spec/mongo/protocol/msg_spec.rb b/spec/mongo/protocol/msg_spec.rb index 09391685ce..6738e085cc 100644 --- a/spec/mongo/protocol/msg_spec.rb +++ b/spec/mongo/protocol/msg_spec.rb @@ -390,6 +390,7 @@ end context 'when the validating_keys option is true with payload 1' do + max_bson_version '4.99.99' let(:sequences) do [ section ] diff --git a/spec/runners/transactions/operation.rb b/spec/runners/transactions/operation.rb index d06f6d4b85..34a0ef300e 100644 --- a/spec/runners/transactions/operation.rb +++ b/spec/runners/transactions/operation.rb @@ -73,10 +73,7 @@ def execute(target, context) 'exception' => e, 'error' => true, } - # We do not have a base class for client side BSON-related errors. - # See https://jira.mongodb.org/browse/RUBY-1806. - # Rescue this particular exception for the time being. - rescue BSON::String::IllegalKey => e + rescue bson_error => e { 'exception' => e, 'clientError' => true, @@ -323,6 +320,15 @@ def wait_for_primary_change(client, context) end end end + + # The error to rescue BSON tests for. If we still define + # BSON::String::IllegalKey then we should rescue that particular error, + # otherwise, rescue an arbitrary BSON::Error + def bson_error + BSON::String.const_defined?(:IllegalKey) ? + BSON::String.const_get(:IllegalKey) : + BSON::Error + end end end end diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index d121fce8af..f730e00d72 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -395,7 +395,7 @@ def execute_operation(op) end public_send(method_name, op) - rescue Mongo::Error, BSON::String::IllegalKey, Mongo::Auth::Unauthorized, ArgumentError => e + rescue Mongo::Error, bson_error, Mongo::Auth::Unauthorized, ArgumentError => e if expected_error.use('isClientError') # isClientError doesn't actually mean a client error. # It means anything other than OperationFailure. DRIVERS-1799 @@ -554,5 +554,14 @@ def create_client(**opts) ).update(opts) Mongo::Client.new(*args) end + + # The error to rescue BSON tests for. If we still define + # BSON::String::IllegalKey then we should rescue that particular error, + # otherwise, rescue an arbitrary BSON::Error + def bson_error + BSON::String.const_defined?(:IllegalKey) ? + BSON::String.const_get(:IllegalKey) : + BSON::Error + end end end diff --git a/spec/support/constraints.rb b/spec/support/constraints.rb index 1bb073489f..c37b7b9b61 100644 --- a/spec/support/constraints.rb +++ b/spec/support/constraints.rb @@ -16,4 +16,18 @@ def require_local_tls end end end + + def max_bson_version(version) + required_version = version.split('.').map(&:to_i) + actual_version = bson_version(required_version.length) + before(:all) do + if (actual_version <=> required_version) > 0 + skip "bson-ruby version #{version} or lower is required" + end + end + end + + def bson_version(precision) + BSON::VERSION.split('.')[0...precision].map(&:to_i) + end end From ad55437afad89d17ce0782e59efa9381b1013ebb Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Tue, 13 Sep 2022 13:39:06 -0400 Subject: [PATCH 042/198] RUBY-2671 remove old drivers, remove rapid releases, update kerberos compat (#2622) --- docs/reference/driver-compatibility.txt | 88 +------------------------ 1 file changed, 1 insertion(+), 87 deletions(-) diff --git a/docs/reference/driver-compatibility.txt b/docs/reference/driver-compatibility.txt index 0a65033a24..179d21181a 100644 --- a/docs/reference/driver-compatibility.txt +++ b/docs/reference/driver-compatibility.txt @@ -39,8 +39,6 @@ version. * - Ruby Driver - MongoDB 6.0 - - MongoDB 5.2 - - MongoDB 5.1 - MongoDB 5.0 - MongoDB 4.4 - MongoDB 4.2 @@ -58,30 +56,24 @@ version. - |checkmark| - |checkmark| - |checkmark| - - |checkmark| - - |checkmark| - - - - * - 2.17 - - - - |checkmark| - |checkmark| - |checkmark| - |checkmark| - |checkmark| - - |checkmark| - - - - * - 2.16 - - - - - - |checkmark| - |checkmark| @@ -94,8 +86,6 @@ version. - D * - 2.15 - - - - - - - |checkmark| @@ -108,8 +98,6 @@ version. - |checkmark| * - 2.14 - - - - - - - |checkmark| @@ -122,8 +110,6 @@ version. - |checkmark| * - 2.13 - - - - - - - |checkmark| [#ocsp]_ @@ -136,8 +122,6 @@ version. - |checkmark| * - 2.12 - - - - - - - @@ -150,8 +134,6 @@ version. - |checkmark| * - 2.11 - - - - - - - @@ -164,8 +146,6 @@ version. - |checkmark| * - 2.10 - - - - - - - @@ -182,8 +162,6 @@ version. - - - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -196,8 +174,6 @@ version. - - - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -210,8 +186,6 @@ version. - - - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -224,8 +198,6 @@ version. - - - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -234,52 +206,6 @@ version. - |checkmark| * - 2.5 - - - - - - - - - - - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - * - 2.4 - - - - - - - - - - - - - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - * - 2.3 - - - - - - - - - - - - - - - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - * - 2.2 - - - - - - - - - - - @@ -288,18 +214,6 @@ version. - |checkmark| - |checkmark| - |checkmark| - - * - 2.0 - - - - - - - - - - - - - - - - - - - - - |checkmark| - |checkmark| @@ -647,7 +561,7 @@ the driver. * - Ruby Driver - mongo_kerberos |nbsp| 2.1 - * - 2.7 - 2.16 + * - 2.7 - 2.18 - |checkmark| From b7131921b8cae55222ef5897620028833757d97a Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Tue, 13 Sep 2022 15:57:53 -0400 Subject: [PATCH 043/198] RUBY-2752 Integration tests for observeSensitiveCommands (#2621) * RUBY-2752 Integration tests for observeSensitiveCommands * Update spec/runners/unified/event_subscriber.rb --- spec/runners/unified/assertions.rb | 2 +- spec/runners/unified/event_subscriber.rb | 10 +- spec/runners/unified/test.rb | 3 +- .../valid-pass/observeSensitiveCommands.yml | 249 ++++++++++++++++++ 4 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml diff --git a/spec/runners/unified/assertions.rb b/spec/runners/unified/assertions.rb index 7e8b1a16ce..d23a57f9ba 100644 --- a/spec/runners/unified/assertions.rb +++ b/spec/runners/unified/assertions.rb @@ -156,7 +156,7 @@ def assert_events else false end - actual_events = subscriber.wanted_events(@observe_sensitive) + actual_events = subscriber.wanted_events(@observe_sensitive[client]) case spec.use('eventType') when nil, 'command' actual_events.select! do |event| diff --git a/spec/runners/unified/event_subscriber.rb b/spec/runners/unified/event_subscriber.rb index 42490b4a6e..f5cb40fd28 100644 --- a/spec/runners/unified/event_subscriber.rb +++ b/spec/runners/unified/event_subscriber.rb @@ -30,8 +30,14 @@ def wanted_events(observe_sensitive = false) events else events.reject do |event| - event.respond_to?(:command_name) && - %w(authenticate getnonce saslStart saslContinue).include?(event.command_name) + if event.respond_to?(:command_name) + # event could be a command started event or command succeeded event + command = event.respond_to?(:command) ? event.command : event.started_event.command + %w(authenticate getnonce saslStart saslContinue).include?(event.command_name) || + # if the command is empty that means we used speculativeAuth and we should + # reject the event. + (%w(hello ismaster isMaster).include?(event.command_name) && command.empty?) + end end end end diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index f730e00d72..94b195a365 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -48,6 +48,7 @@ def initialize(spec, **opts) @multiple_mongoses = mongoses.any? { |v| v } @test_spec.freeze @subscribers = {} + @observe_sensitive = {} @options = opts end @@ -182,7 +183,7 @@ def generate_entities(es) end create_client(**opts).tap do |client| - @observe_sensitive = spec.use('observeSensitiveCommands') + @observe_sensitive[client] = spec.use('observeSensitiveCommands') @subscribers[client] ||= subscriber end when 'database' diff --git a/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml b/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml new file mode 100644 index 0000000000..13db413706 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml @@ -0,0 +1,249 @@ +description: "observeSensitiveCommands" + +schemaVersion: "1.5" + +runOnRequirements: + - auth: false + +createEntities: + - client: + id: &clientObserveSensitiveCommands client0 + observeEvents: + - commandStartedEvent + - commandSucceededEvent + observeSensitiveCommands: true + - client: + id: &clientDoNotObserveSensitiveCommands client1 + observeEvents: + - commandStartedEvent + - commandSucceededEvent + observeSensitiveCommands: false + - client: + id: &clientDoNotObserveSensitiveCommandsByDefault client2 + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - database: + id: &databaseObserveSensitiveCommands database0 + client: *clientObserveSensitiveCommands + databaseName: &databaseName observeSensitiveCommands + - database: + id: &databaseDoNotObserveSensitiveCommands database1 + client: *clientDoNotObserveSensitiveCommands + databaseName: *databaseName + - database: + id: &databaseDoNotObserveSensitiveCommandsByDefault database2 + client: *clientDoNotObserveSensitiveCommandsByDefault + databaseName: *databaseName + +tests: + - description: "getnonce is observed with observeSensitiveCommands=true" + operations: + - name: runCommand + object: *databaseObserveSensitiveCommands + arguments: + commandName: getnonce + command: { getnonce: 1 } + expectEvents: + - client: *clientObserveSensitiveCommands + events: + - commandStartedEvent: + commandName: getnonce + command: { getnonce: { $$exists: false } } + - commandSucceededEvent: + commandName: getnonce + reply: + ok: { $$exists: false } + nonce: { $$exists: false } + + - description: "getnonce is not observed with observeSensitiveCommands=false" + operations: + - name: runCommand + object: *databaseDoNotObserveSensitiveCommands + arguments: + commandName: getnonce + command: { getnonce: 1 } + expectEvents: + - client: *clientDoNotObserveSensitiveCommands + events: [] + + - description: "getnonce is not observed by default" + operations: + - name: runCommand + object: *databaseDoNotObserveSensitiveCommandsByDefault + arguments: + commandName: getnonce + command: { getnonce: 1 } + expectEvents: + - client: *clientDoNotObserveSensitiveCommandsByDefault + events: [] + + - description: "hello with speculativeAuthenticate" + runOnRequirements: + - minServerVersion: "4.9" + operations: + - name: runCommand + object: *databaseObserveSensitiveCommands + arguments: &helloArgs + commandName: hello + command: + hello: 1 + speculativeAuthenticate: { saslStart: 1 } + - name: runCommand + object: *databaseDoNotObserveSensitiveCommands + arguments: *helloArgs + - name: runCommand + object: *databaseDoNotObserveSensitiveCommandsByDefault + arguments: *helloArgs + expectEvents: + - client: *clientObserveSensitiveCommands + events: + - commandStartedEvent: + commandName: hello + command: + # Assert that all fields in command are redacted + hello: { $$exists: false } + speculativeAuthenticate: { $$exists: false } + - commandSucceededEvent: + commandName: hello + reply: + # Assert that all fields in reply are redacted + isWritablePrimary: { $$exists: false } + speculativeAuthenticate: { $$exists: false } + - client: *clientDoNotObserveSensitiveCommands + events: [] + - client: *clientDoNotObserveSensitiveCommandsByDefault + events: [] + + - description: "hello without speculativeAuthenticate is always observed" + runOnRequirements: + - minServerVersion: "4.9" + operations: + - name: runCommand + object: *databaseObserveSensitiveCommands + arguments: &helloArgs + commandName: hello + command: { hello: 1 } + - name: runCommand + object: *databaseDoNotObserveSensitiveCommands + arguments: *helloArgs + - name: runCommand + object: *databaseDoNotObserveSensitiveCommandsByDefault + arguments: *helloArgs + expectEvents: + - client: *clientObserveSensitiveCommands + events: &helloEvents + - commandStartedEvent: + commandName: hello + command: { hello: 1 } + - commandSucceededEvent: + commandName: hello + reply: { isWritablePrimary: { $$exists: true } } + - client: *clientDoNotObserveSensitiveCommands + events: *helloEvents + - client: *clientDoNotObserveSensitiveCommandsByDefault + events: *helloEvents + + - description: "legacy hello with speculativeAuthenticate" + operations: + - name: runCommand + object: *databaseObserveSensitiveCommands + arguments: &ismasterArgs + commandName: ismaster + command: + ismaster: 1 + speculativeAuthenticate: { saslStart: 1 } + - name: runCommand + object: *databaseObserveSensitiveCommands + arguments: &isMasterArgs + commandName: isMaster + command: + isMaster: 1 + speculativeAuthenticate: { saslStart: 1 } + - name: runCommand + object: *databaseDoNotObserveSensitiveCommands + arguments: *ismasterArgs + - name: runCommand + object: *databaseDoNotObserveSensitiveCommands + arguments: *isMasterArgs + - name: runCommand + object: *databaseDoNotObserveSensitiveCommandsByDefault + arguments: *ismasterArgs + - name: runCommand + object: *databaseDoNotObserveSensitiveCommandsByDefault + arguments: *isMasterArgs + expectEvents: + - client: *clientObserveSensitiveCommands + events: + - commandStartedEvent: + commandName: ismaster + command: + # Assert that all fields in command are redacted + ismaster: { $$exists: false } + speculativeAuthenticate: { $$exists: false } + - commandSucceededEvent: + commandName: ismaster + reply: + # Assert that all fields in reply are redacted + ismaster: { $$exists: false } + speculativeAuthenticate: { $$exists: false } + - commandStartedEvent: + commandName: isMaster + command: + # Assert that all fields in command are redacted + isMaster: { $$exists: false } + speculativeAuthenticate: { $$exists: false } + - commandSucceededEvent: + commandName: isMaster + reply: + # Assert that all fields in reply are redacted + ismaster: { $$exists: false } + speculativeAuthenticate: { $$exists: false } + - client: *clientDoNotObserveSensitiveCommands + events: [] + - client: *clientDoNotObserveSensitiveCommandsByDefault + events: [] + + - description: "legacy hello without speculativeAuthenticate is always observed" + operations: + - name: runCommand + object: *databaseObserveSensitiveCommands + arguments: &ismasterArgs + commandName: ismaster + command: { ismaster: 1 } + - name: runCommand + object: *databaseObserveSensitiveCommands + arguments: &isMasterArgs + commandName: isMaster + command: { isMaster: 1 } + - name: runCommand + object: *databaseDoNotObserveSensitiveCommands + arguments: *ismasterArgs + - name: runCommand + object: *databaseDoNotObserveSensitiveCommands + arguments: *isMasterArgs + - name: runCommand + object: *databaseDoNotObserveSensitiveCommandsByDefault + arguments: *ismasterArgs + - name: runCommand + object: *databaseDoNotObserveSensitiveCommandsByDefault + arguments: *isMasterArgs + expectEvents: + - client: *clientObserveSensitiveCommands + events: &ismasterAndisMasterEvents + - commandStartedEvent: + commandName: ismaster + command: { ismaster: 1 } + - commandSucceededEvent: + commandName: ismaster + reply: { ismaster: { $$exists: true } } + - commandStartedEvent: + commandName: isMaster + command: { isMaster: 1 } + - commandSucceededEvent: + commandName: isMaster + reply: { ismaster: { $$exists: true } } + - client: *clientDoNotObserveSensitiveCommands + events: *ismasterAndisMasterEvents + - client: *clientDoNotObserveSensitiveCommandsByDefault + events: *ismasterAndisMasterEvents From 56125682f1dbf46ee19f73e2d4ff6af8ddd45a89 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Tue, 13 Sep 2022 16:39:02 -0400 Subject: [PATCH 044/198] RUBY-3117 investigate and fix test failures as a result of removing validating_keys (#2620) * RUBY-3117 investigate and fix test failures as a result of removing validating_keys * RUBY-3117 remove all references to BSON::Config.validating_keys? * Update gemfiles/bson_master.gemfile Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> --- lib/mongo/grid/file/chunk.rb | 3 +- lib/mongo/grid/file/info.rb | 3 +- lib/mongo/protocol/bit_vector.rb | 4 ++- lib/mongo/protocol/caching_hash.rb | 23 ++---------- lib/mongo/protocol/message.rb | 12 +++---- lib/mongo/protocol/msg.rb | 1 + lib/mongo/protocol/serializers.rb | 41 ++++++++++++--------- spec/mongo/protocol/caching_hash_spec.rb | 46 ------------------------ spec/mongo/protocol/msg_spec.rb | 6 ++-- 9 files changed, 41 insertions(+), 98 deletions(-) diff --git a/lib/mongo/grid/file/chunk.rb b/lib/mongo/grid/file/chunk.rb index 9ac8754f8b..c9a5d418e4 100644 --- a/lib/mongo/grid/file/chunk.rb +++ b/lib/mongo/grid/file/chunk.rb @@ -133,11 +133,12 @@ def initialize(document) # # @param [ BSON::ByteBuffer ] buffer The encoded BSON buffer to append to. # @param [ true, false ] validating_keys Whether keys should be validated when serializing. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ String ] The raw BSON data. # # @since 2.0.0 - def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = BSON::Config.validating_keys?) + def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = nil) document.to_bson(buffer) end diff --git a/lib/mongo/grid/file/info.rb b/lib/mongo/grid/file/info.rb index 1ef41744b4..3a6890be77 100644 --- a/lib/mongo/grid/file/info.rb +++ b/lib/mongo/grid/file/info.rb @@ -228,11 +228,12 @@ def update_md5(bytes) # # @param [ BSON::ByteBuffer ] buffer The encoded BSON buffer to append to. # @param [ true, false ] validating_keys Whether keys should be validated when serializing. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ String ] The raw BSON data. # # @since 2.0.0 - def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = BSON::Config.validating_keys?) + def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = nil) if @client_md5 && !document[:md5] document[:md5] = @client_md5.hexdigest end diff --git a/lib/mongo/protocol/bit_vector.rb b/lib/mongo/protocol/bit_vector.rb index 3f32fb10ff..998d3d1e42 100644 --- a/lib/mongo/protocol/bit_vector.rb +++ b/lib/mongo/protocol/bit_vector.rb @@ -39,9 +39,11 @@ def initialize(layout) # # @param buffer [ String ] Buffer to receive the serialized vector # @param value [ Array ] Array of flags to encode + # @param [ true, false ] validating_keys Whether keys should be validated when serializing. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ String ] Buffer that received the serialized vector - def serialize(buffer, value, validating_keys = BSON::Config.validating_keys?) + def serialize(buffer, value, validating_keys = nil) bits = 0 value.each { |flag| bits |= (@masks[flag] || 0) } buffer.put_int32(bits) diff --git a/lib/mongo/protocol/caching_hash.rb b/lib/mongo/protocol/caching_hash.rb index 93694a2875..2087d2df5e 100644 --- a/lib/mongo/protocol/caching_hash.rb +++ b/lib/mongo/protocol/caching_hash.rb @@ -38,32 +38,15 @@ def bson_type # # @param [ BSON::ByteBuffer ] buffer The encoded BSON buffer to append to. # @param [ true, false ] validating_keys Whether keys should be validated when serializing. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ BSON::ByteBuffer ] The buffer with the encoded object. - def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = BSON::Config.validating_keys?) + def to_bson(buffer = BSON::ByteBuffer.new, validating_keys = nil) if !@bytes - @bytes = @hash.to_bson(BSON::ByteBuffer.new, validating_keys).to_s - elsif needs_validation?(validating_keys) - @validated = true - return @hash.to_bson(buffer, validating_keys) + @bytes = @hash.to_bson(BSON::ByteBuffer.new).to_s end - @validated ||= validating_keys buffer.put_bytes(@bytes) end - - private - - # Checks the current value for validating keys, and whether or not this - # bson has been validated in the past, and decides if we need to recalculate - # the to_bson to check the validations. - # - # @param [ true, false ] validating_keys Whether keys should be validated when serializing. - # - # @return [ true, false ] Whether or not the bson needs to be recalculated - # with validation. - def needs_validation?(validating_keys) - !@validated && validating_keys - end end end end diff --git a/lib/mongo/protocol/message.rb b/lib/mongo/protocol/message.rb index ed5674e0d9..438f740454 100644 --- a/lib/mongo/protocol/message.rb +++ b/lib/mongo/protocol/message.rb @@ -354,16 +354,16 @@ def serialize_fields(buffer, max_bson_size = nil) if field[:multi] value.each do |item| if field[:type].respond_to?(:size_limited?) - field[:type].serialize(buffer, item, max_bson_size, validating_keys?) + field[:type].serialize(buffer, item, max_bson_size) else - field[:type].serialize(buffer, item, validating_keys?) + field[:type].serialize(buffer, item) end end else if field[:type].respond_to?(:size_limited?) - field[:type].serialize(buffer, value, max_bson_size, validating_keys?) + field[:type].serialize(buffer, value, max_bson_size) else - field[:type].serialize(buffer, value, validating_keys?) + field[:type].serialize(buffer, value) end end end @@ -456,10 +456,6 @@ def self.deserialize_field(message, io, field, options = {}) field[:type].deserialize(io, options) ) end - - def validating_keys? - @options[:validating_keys] if @options - end end end end diff --git a/lib/mongo/protocol/msg.rb b/lib/mongo/protocol/msg.rb index 876884b500..194de8ea6f 100644 --- a/lib/mongo/protocol/msg.rb +++ b/lib/mongo/protocol/msg.rb @@ -58,6 +58,7 @@ class Msg < Message # @option options [ true, false ] validating_keys Whether keys should be # validated for being valid document keys (i.e. not begin with $ and # not contain dots). + # This option is deprecated and will not be used. It will removed in version 3.0. # # @api private # diff --git a/lib/mongo/protocol/serializers.rb b/lib/mongo/protocol/serializers.rb index e021b9a0c7..cc1c40bd1b 100644 --- a/lib/mongo/protocol/serializers.rb +++ b/lib/mongo/protocol/serializers.rb @@ -51,9 +51,11 @@ module Header # # @param buffer [ String ] Buffer to receive the serialized value. # @param value [ String ] Header value to be serialized. + # @param [ true, false ] validating_keys Whether keys should be validated when serializing. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ String ] Buffer with serialized value. - def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, validating_keys = nil) buffer.put_bytes(value.pack(HEADER_PACK)) end @@ -80,7 +82,7 @@ module CString # @param value [ String ] The string to be serialized. # # @return [ String ] Buffer with serialized value. - def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, validating_keys = nil) buffer.put_cstring(value) end end @@ -96,7 +98,7 @@ module Zero # @param value [ Fixnum ] Ignored value. # # @return [ String ] Buffer with serialized value. - def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, validating_keys = nil) buffer.put_int32(ZERO) end end @@ -112,7 +114,7 @@ module Int32 # @param value [ Integer | BSON::Int32 ] 32-bit integer to be serialized. # # @return [String] Buffer with serialized value. - def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, validating_keys = nil) if value.is_a?(BSON::Int32) if value.respond_to?(:value) # bson-ruby >= 4.6.0 @@ -146,7 +148,7 @@ module Int64 # @param value [ Integer | BSON::Int64 ] 64-bit integer to be serialized. # # @return [ String ] Buffer with serialized value. - def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, validating_keys = nil) if value.is_a?(BSON::Int64) if value.respond_to?(:value) # bson-ruby >= 4.6.0 @@ -182,19 +184,20 @@ module Sections # @param [ Array ] value The sections to be serialized. # @param [ Fixnum ] max_bson_size The max bson size of documents in the sections. # @param [ true, false ] validating_keys Whether to validate document keys. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ BSON::ByteBuffer ] Buffer with serialized value. # # @since 2.5.0 - def self.serialize(buffer, value, max_bson_size = nil, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, max_bson_size = nil, validating_keys = nil) value.each do |section| case section[:type] when PayloadZero::TYPE - PayloadZero.serialize(buffer, section[:payload], max_bson_size, false) + PayloadZero.serialize(buffer, section[:payload], max_bson_size) when nil - PayloadZero.serialize(buffer, section[:payload], max_bson_size, false) + PayloadZero.serialize(buffer, section[:payload], max_bson_size) when PayloadOne::TYPE - PayloadOne.serialize(buffer, section[:payload], max_bson_size, validating_keys) + PayloadOne.serialize(buffer, section[:payload], max_bson_size) else raise Error::UnknownPayloadType.new(section[:type]) end @@ -259,13 +262,14 @@ module PayloadZero # @param [ BSON::Document, Hash ] value The object to serialize. # @param [ Fixnum ] max_bson_size The max bson size of documents in the section. # @param [ true, false ] validating_keys Whether to validate document keys. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ BSON::ByteBuffer ] Buffer with serialized value. # # @since 2.5.0 - def self.serialize(buffer, value, max_bson_size = nil, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, max_bson_size = nil, validating_keys = nil) buffer.put_byte(TYPE_BYTE) - Serializers::Document.serialize(buffer, value, max_bson_size, validating_keys) + Serializers::Document.serialize(buffer, value, max_bson_size) end # Deserializes a section of payload type 0 of an OP_MSG from the IO stream. @@ -307,17 +311,18 @@ module PayloadOne # @param [ BSON::Document, Hash ] value The object to serialize. # @param [ Fixnum ] max_bson_size The max bson size of documents in the section. # @param [ true, false ] validating_keys Whether to validate document keys. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ BSON::ByteBuffer ] Buffer with serialized value. # # @since 2.5.0 - def self.serialize(buffer, value, max_bson_size = nil, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, max_bson_size = nil, validating_keys = nil) buffer.put_byte(TYPE_BYTE) start = buffer.length buffer.put_int32(0) # hold for size buffer.put_cstring(value[:identifier]) value[:sequence].each do |document| - Document.serialize(buffer, document, max_bson_size, validating_keys) + Document.serialize(buffer, document, max_bson_size) end buffer.replace_int32(start, buffer.length - start) end @@ -356,9 +361,9 @@ module Document # @param value [ Hash ] Document to serialize as BSON. # # @return [ String ] Buffer with serialized value. - def self.serialize(buffer, value, max_bson_size = nil, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, max_bson_size = nil, validating_keys = nil) start_size = buffer.length - value.to_bson(buffer, validating_keys) + value.to_bson(buffer) serialized_size = buffer.length - start_size if max_bson_size && serialized_size > max_bson_size raise Error::MaxBSONSize, @@ -401,11 +406,12 @@ module Byte # @param [ BSON::ByteBuffer ] buffer Buffer to receive the single byte. # @param [ String ] value The byte to write to the buffer. # @param [ true, false ] validating_keys Whether to validate keys. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ BSON::ByteBuffer ] Buffer with serialized value. # # @since 2.5.0 - def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, validating_keys = nil) buffer.put_byte(value) end @@ -432,11 +438,12 @@ module Bytes # @param [ BSON::ByteBuffer ] buffer Buffer to receive the bytes. # @param [ String ] value The bytes to write to the buffer. # @param [ true, false ] validating_keys Whether to validate keys. + # This option is deprecated and will not be used. It will removed in version 3.0. # # @return [ BSON::ByteBuffer ] Buffer with serialized value. # # @since 2.5.0 - def self.serialize(buffer, value, validating_keys = BSON::Config.validating_keys?) + def self.serialize(buffer, value, validating_keys = nil) buffer.put_bytes(value) end diff --git a/spec/mongo/protocol/caching_hash_spec.rb b/spec/mongo/protocol/caching_hash_spec.rb index 56d79bc0ef..e92c756679 100644 --- a/spec/mongo/protocol/caching_hash_spec.rb +++ b/spec/mongo/protocol/caching_hash_spec.rb @@ -34,50 +34,4 @@ end end end - - describe "#needs_validation?" do - max_bson_version "4.99.99" - - before do - hash.to_bson(BSON::ByteBuffer.new, validation) - end - - let(:needs_validation?) do - hash.send(:needs_validation?, validating_keys) - end - - context "when calling to_bson without validation" do - let(:validation) { false } - - context "validating_keys is true" do - let(:validating_keys) { true } - - it "is true" do - expect(needs_validation?).to be true - end - end - - context "validating_keys is false" do - let(:validating_keys) { false } - - it "is false" do - expect(needs_validation?).to be false - end - end - end - - context "when calling to_bson with validation" do - let(:validation) { true } - - [true, false].each do |b| - context "validating_keys is #{b}" do - let(:validating_keys) { b } - - it "is false" do - expect(needs_validation?).to be false - end - end - end - end - end end diff --git a/spec/mongo/protocol/msg_spec.rb b/spec/mongo/protocol/msg_spec.rb index 6738e085cc..2a23a95a1b 100644 --- a/spec/mongo/protocol/msg_spec.rb +++ b/spec/mongo/protocol/msg_spec.rb @@ -404,10 +404,8 @@ { validating_keys: true } end - it 'checks the sequence document keys' do - expect { - message.serialize - }.to raise_exception(BSON::String::IllegalKey) + it 'does not check the sequence document keys' do + expect(message.serialize).to be_a(BSON::ByteBuffer) end end From ae385ca7650dd3a1f4d6e39f863176824fbae532 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 15 Sep 2022 13:06:15 -0400 Subject: [PATCH 045/198] RUBY-2716 Note what command events/payloads are not published (#2629) * RUBY-2716 Note what command events/payloads are not published * Update docs/reference/monitoring.txt Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> * Update docs/reference/monitoring.txt Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> * update docs Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> --- docs/reference/monitoring.txt | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/reference/monitoring.txt b/docs/reference/monitoring.txt index 840a32a213..ab3b2ccee1 100644 --- a/docs/reference/monitoring.txt +++ b/docs/reference/monitoring.txt @@ -435,3 +435,35 @@ To turn off monitoring, set the client monitoring option to ``false``: .. code-block:: ruby client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'test', :monitoring => false ) + + +Excluded and Redacted Events +============================ + +The Ruby driver does not pubish, and occasionaly redacts, some events via the +command monitoring mechanism: + +1. If the command belongs to a particular subset of redacted commands, or + contains keys that trigger payload redaction, an empty payload will be + provided for security reasons. The full payload can be accessed by setting + the ``MONGO_RUBY_DRIVER_UNREDACT_EVENTS`` environment variable to ``1``, ``true`` or ``yes``. The + following commands are redacted: + + - ``authenticate`` + - ``saslStart`` + - ``saslContinue`` + - ``getnonce`` + - ``createUser`` + - ``updateUser`` + - ``copydbgetnonce`` + - ``copydbsaslstart`` + - ``copydb`` +2. If the command is a handshake command, either ``ismaster`` or ``hello``, on + a non-monitoring connection, no event is published at all. +3. Commands sent over monitoring connections (such as ismaster and hello) do + not publish command monitoring events. Instead, every time a server is + checked a server heartbeat event is published. The server heartbeat events + do not include command or reply payloads. +4. If the command is a handshake command, and the ``speculativeAuthenticate`` + options is ``true``, the command will be redacted, and an empty payload will + be provided. From d7bf75db5482692b8d5059992d274014b17cf88a Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 19 Sep 2022 17:25:15 -0400 Subject: [PATCH 046/198] RUBY-2609 Enable TCP_USER_TIMEOUT by default when possible (#2632) --- lib/mongo/socket.rb | 3 +++ spec/mongo/address_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/lib/mongo/socket.rb b/lib/mongo/socket.rb index 707a857022..60feb79100 100644 --- a/lib/mongo/socket.rb +++ b/lib/mongo/socket.rb @@ -414,11 +414,14 @@ def unix_socket?(sock) DEFAULT_TCP_KEEPIDLE = 120 + DEFAULT_TCP_USER_TIMEOUT = 210 + def set_keepalive_opts(sock) sock.setsockopt(SOL_SOCKET, SO_KEEPALIVE, true) set_option(sock, :TCP_KEEPINTVL, DEFAULT_TCP_KEEPINTVL) set_option(sock, :TCP_KEEPCNT, DEFAULT_TCP_KEEPCNT) set_option(sock, :TCP_KEEPIDLE, DEFAULT_TCP_KEEPIDLE) + set_option(sock, :TCP_USER_TIMEOUT, DEFAULT_TCP_USER_TIMEOUT) rescue # JRuby 9.2.13.0 and lower do not define TCP_KEEPINTVL etc. constants. # JRuby 9.2.14.0 defines the constants but does not allow to get or diff --git a/spec/mongo/address_spec.rb b/spec/mongo/address_spec.rb index 826dacb869..d710ff05b8 100644 --- a/spec/mongo/address_spec.rb +++ b/spec/mongo/address_spec.rb @@ -293,6 +293,12 @@ expect(socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_KEEPIDLE).int).to be <= 120 end end + + if Socket.const_defined?(:TCP_USER_TIMEOUT) + it 'sets the socket TCP_KEEPIDLE option' do + expect(socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_USER_TIMEOUT).int).to be <= 210 + end + end end end From e8b4f8f2ae998e42f51cafc9b05eee2a6be736f6 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 19 Sep 2022 17:25:25 -0400 Subject: [PATCH 047/198] RUBY-3114 FLE - maxWireVersion should run on Mongo Server 4.0.x (#2631) --- spec/spec_tests/data/client_side_encryption/maxWireVersion.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_tests/data/client_side_encryption/maxWireVersion.yml b/spec/spec_tests/data/client_side_encryption/maxWireVersion.yml index 8df590d809..87c4c993f9 100644 --- a/spec/spec_tests/data/client_side_encryption/maxWireVersion.yml +++ b/spec/spec_tests/data/client_side_encryption/maxWireVersion.yml @@ -1,5 +1,5 @@ runOn: - - maxServerVersion: "4.0" + - maxServerVersion: "4.0.99" database_name: &database_name "default" collection_name: &collection_name "default" From 4697bb429e37b9a2241a70afb9ca0d38404dca03 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 19 Sep 2022 17:25:33 -0400 Subject: [PATCH 048/198] RUBY-2534/RUBY-3120 Clarify specifying username with X509 certificate authentication (#2630) * RUBY-2534 Clarify specifying username with X509 certificate authentication * RUBY-3120 fix broken link --- docs/reference/authentication.txt | 8 +++++++- docs/reference/user-management.txt | 4 ++-- docs/tutorials/common-errors.txt | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/reference/authentication.txt b/docs/reference/authentication.txt index c19a3472a5..7371ae1003 100644 --- a/docs/reference/authentication.txt +++ b/docs/reference/authentication.txt @@ -193,7 +193,13 @@ Client Certificate (X.509) The driver presents an X.509 certificate during TLS negotiation. The MONGODB-X509 authentication mechanism authenticates a username -derived from the distinguished subject name of this certificate. +retrieved from the distinguished subject name of this certificate. + +.. note:: + + Since the username is retrieved from the certificate, a username does not + need to be specified. If a username is specified, it will be sent to the + server verbatim. If a password is provided, an error will be raised. This authentication method requires the use of TLS connections with certificate validation. diff --git a/docs/reference/user-management.txt b/docs/reference/user-management.txt index 7d0f527cc5..5363b9a2fc 100644 --- a/docs/reference/user-management.txt +++ b/docs/reference/user-management.txt @@ -42,7 +42,7 @@ Users and Databases =================== When a client connects to the server, MongoDB distinguishes the database -that the client will perform operations on from the `auth source `_ +that the client will perform operations on from the :ref:`auth source ` which is the database storing the user that the client is authenticating as. In many cases, the auth source is the same as the database. When they differ, @@ -121,7 +121,7 @@ For more information about roles in MongoDB, see the In addition to the ``:roles`` option, the ``create`` method supports a ``:session`` option, which allows you to specify a ``Mongo::Session`` object -to use for this operation, as well as a ``:write_concern`` option, +to use for this operation, as well as a ``:write_concern`` option, which specifies the write concern of this operation when performed on a replica set. diff --git a/docs/tutorials/common-errors.txt b/docs/tutorials/common-errors.txt index 4cb82b9fb6..6b393cc2df 100644 --- a/docs/tutorials/common-errors.txt +++ b/docs/tutorials/common-errors.txt @@ -109,7 +109,7 @@ Some users reported getting the following error: Mongo::Auth::Unauthorized: User ... is not authorized to access ... After upgrading to Ruby Driver version 2.5+, the driver was changed to use -the scram authorization mechanism by default. This error might be happen if the +the scram authorization mechanism by default. This error might happen if the authorization mechanism your user was created with does not match the authorization mechanism used by the driver. You can specify the preferred authorization mechanism using the ``auth_mech`` option in your ``mongoid.yml`` From c678f8d0e621b398bad848d7053844ae758df54b Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 23 Sep 2022 10:27:19 -0400 Subject: [PATCH 049/198] RUBY-3119 Add `serverless: forbid` to change streams clusterTime test (#2627) --- .../data/change_streams_unified/change-streams-clusterTime.yml | 3 ++- .../change-streams-showExpandedEvents.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml b/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml index 0a547bac7d..997d4d5761 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml @@ -1,5 +1,5 @@ description: "change-streams-clusterTime" -schemaVersion: "1.3" +schemaVersion: "1.4" createEntities: - client: id: &client0 client0 @@ -16,6 +16,7 @@ createEntities: runOnRequirements: - minServerVersion: "4.0.0" topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] + serverless: forbid initialData: - collectionName: *collection0 diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml b/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml index c0443d0e22..7a15ba4b54 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml @@ -3,6 +3,7 @@ schemaVersion: "1.7" runOnRequirements: - minServerVersion: "6.0.0" topologies: [ replicaset, sharded-replicaset, sharded ] + serverless: forbid createEntities: - client: id: &client0 client0 From d80ea7e4502b6ec7ed3ee338f9ef9f2c7e15163c Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 23 Sep 2022 13:33:39 -0400 Subject: [PATCH 050/198] RUBY-2773 Verify query cache behavior with concurrent iterations of the same cursor (#2633) * RUBY-2773 Verify query cache behavior with concurrent iterations of the same cursor * RUBY-2773 clean up test * RUBY-2773 use a sleep --- spec/integration/query_cache_spec.rb | 89 +++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/spec/integration/query_cache_spec.rb b/spec/integration/query_cache_spec.rb index 4b1f41907e..efe3156677 100644 --- a/spec/integration/query_cache_spec.rb +++ b/spec/integration/query_cache_spec.rb @@ -86,7 +86,6 @@ end describe 'query with multiple batches' do - min_server_fcv '3.2' before do 102.times { |i| authorized_collection.insert_one(_id: i) } @@ -1253,4 +1252,92 @@ include_examples 'retrieves full result set on second iteration' end end + + describe 'concurrent queries with multiple batches' do + + before do + 102.times { |i| authorized_collection.insert_one(_id: i) } + end + + # The query cache table is stored in thread local storage, so even though + # we executed the same queries in the first thread (and waited for them to + # finish), that query is going to be executed again (only once) in the + # second thread. + it "uses separate cache tables per thread" do + thread1 = Thread.new do + Mongo::QueryCache.cache do + authorized_collection.find.to_a + authorized_collection.find.to_a + authorized_collection.find.to_a + authorized_collection.find.to_a + end + end + thread1.join + thread2 = Thread.new do + Mongo::QueryCache.cache do + authorized_collection.find.to_a + authorized_collection.find.to_a + authorized_collection.find.to_a + authorized_collection.find.to_a + end + end + thread2.join + + expect(subscriber.command_started_events('find').length).to eq(2) + expect(subscriber.command_started_events('getMore').length).to eq(2) + end + + it "is able to query concurrently" do + wait_for_first_thread = true + wait_for_second_thread = true + threads = [] + first_thread_docs = [] + threads << Thread.new do + Mongo::QueryCache.cache do + # 1. iterate first batch + authorized_collection.find.each_with_index do |doc, i| + # 2. verify that we're getting all of the correct documents + first_thread_docs << doc + expect(doc).to eq({ "_id" => i }) + if i == 50 + # 2. check that there hasn't been a getmore + expect(subscriber.command_started_events('getMore').length).to eq(0) + # 3. mark second thread ready to start + wait_for_first_thread = false + # 4. wait for second thread + sleep 0.1 while wait_for_second_thread + # 5. verify that the other thread sent a getmore + expect(subscriber.command_started_events('getMore').length).to eq(1) + end + # 6. finish iterating the batch + end + # 7. verify that it still caches the query + authorized_collection.find.to_a + end + end + + threads << Thread.new do + Mongo::QueryCache.cache do + # 1. wait for the first thread to finish first batch iteration + sleep 0.1 while wait_for_first_thread + # 2. iterate the entire result set + authorized_collection.find.each_with_index do |doc, i| + # 3. verify documnents + expect(doc).to eq({ "_id" => i }) + end + # 4. verify get more + expect(subscriber.command_started_events('getMore').length).to eq(1) + # 5. mark second thread done + wait_for_second_thread = false + # 6. verify that it still caches the query + authorized_collection.find.to_a + end + end + + threads.map(&:join) + expect(first_thread_docs.length).to eq(102) + expect(subscriber.command_started_events('find').length).to eq(2) + expect(subscriber.command_started_events('getMore').length).to eq(2) + end + end end From 024eac5f52c2c61053bdd457362e60dff356058c Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 23 Sep 2022 14:15:07 -0400 Subject: [PATCH 051/198] RUBY-2854 Test: :session option is passed correctly to count_documents and other readable methods (#2637) * Test: :session option is passed correctly to count_documents and other readable methods * Apply suggestions from code review Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> --- spec/mongo/collection/view/readable_spec.rb | 138 ++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/spec/mongo/collection/view/readable_spec.rb b/spec/mongo/collection/view/readable_spec.rb index cf5b2385b7..d83bed8351 100644 --- a/spec/mongo/collection/view/readable_spec.rb +++ b/spec/mongo/collection/view/readable_spec.rb @@ -217,6 +217,34 @@ end end + context "when a :session is given on the view" do + let(:opt) { :session } + let(:param) { authorized_client.start_session } + let(:aggregate) do + authorized_collection.find({}, session: param).aggregate(pipeline, options) + end + + after do + param.end_session + end + + context "when broken_view_options is false" do + config_override :broken_view_options, false + + it "sets the option correctly" do + expect(aggregate.options[opt]).to eq(param) + end + end + + context "when broken_view_options is true" do + config_override :broken_view_options, true + + it "does not set the option" do + expect(aggregate.options[opt]).to be nil + end + end + end + context "when also including in options" do let(:aggregate) do @@ -418,6 +446,24 @@ expect(map_reduce.options[:limit]).to eq(2) end end + + context "when a :session is given on the view" do + let(:opt) { :session } + let(:param) { authorized_client.start_session } + let(:map_reduce) do + authorized_collection.find({}, session: param).map_reduce(map, reduce, options) + end + + after do + param.end_session + end + + with_config_values :broken_view_options, true, false do + it "sets the option correctly" do + expect(map_reduce.options[opt]).to eq(param) + end + end + end end end @@ -862,6 +908,29 @@ it_behaves_like "a count option" end + context "when a :session is given on the view" do + + let(:opt) { :session } + let(:param) { authorized_client.start_session } + let(:aggregate) do + authorized_collection.find({}, session: param).aggregate(pipeline, options) + end + + after do + param.end_session + end + + with_config_values :broken_view_options, true, false do + it "sets the option correctly" do + expect(Mongo::Operation::Count).to receive(:new).once.and_wrap_original do |m, *args| + expect(args.first[opt]).to eq(param) + m.call(*args) + end + authorized_collection.find({}, session: param).count(options) + end + end + end + context "when also including in options" do with_config_values :broken_view_options, true, false do @@ -1026,6 +1095,25 @@ end end + context "when a :session is given on the view" do + let(:opt) { :session } + let(:param) { authorized_client.start_session } + + after do + param.end_session + end + + with_config_values :broken_view_options, true, false do + it "sets the option correctly" do + expect(Mongo::Operation::Count).to receive(:new).once.and_wrap_original do |m, *args| + expect(args.first[opt]).to eq(param) + m.call(*args) + end + authorized_collection.find({}, session: param).estimated_document_count(options) + end + end + end + context "when also including in options" do with_config_values :broken_view_options, true, false do @@ -1179,6 +1267,37 @@ end end + context "when a :session is given on the view" do + let(:opt) { :session } + let(:param) { authorized_client.start_session } + + after do + param.end_session + end + + context "when broken_view_options is false" do + config_override :broken_view_options, false + it "sets the option correctly" do + expect_any_instance_of(Mongo::Collection::View).to receive(:aggregate).once.and_wrap_original do |m, *args| + expect(args[1][opt]).to eq(param) + m.call(*args) + end + authorized_collection.find({}, session: param).count_documents(options) + end + end + + context "when broken_view_options is true" do + config_override :broken_view_options, true + it "does not set the option" do + expect_any_instance_of(Mongo::Collection::View).to receive(:aggregate).once.and_wrap_original do |m, *args| + expect(args[1][opt]).to be nil + m.call(*args) + end + authorized_collection.find({}, session: param).count_documents(options) + end + end + end + context "when also including in options" do with_config_values :broken_view_options, true, false do @@ -1660,6 +1779,25 @@ end end + context "when a :session is given on the view" do + let(:opt) { :session } + let(:param) { authorized_client.start_session } + + after do + param.end_session + end + + with_config_values :broken_view_options, true, false do + it "sets the option correctly" do + expect(Mongo::Operation::Distinct).to receive(:new).once.and_wrap_original do |m, *args| + expect(args.first[opt]).to eq(param) + m.call(*args) + end + authorized_collection.find({}, session: param).distinct(options) + end + end + end + context "when also including in options" do with_config_values :broken_view_options, true, false do From b0d7f044cde93c214ddfcc7092ab42ee84458a5d Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 27 Sep 2022 11:07:48 +0200 Subject: [PATCH 052/198] RUBY-1253 Detect cursor closure from other thread (#2639) Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> --- lib/mongo/cursor.rb | 17 +++++++++++++++++ spec/mongo/cursor_spec.rb | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index 0d3f3276d7..90f24a7863 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -83,6 +83,8 @@ def initialize(view, result, server, options = {}) @connection_global_id = result.connection_global_id @options = options @session = @options[:session] + @explicitly_closed = false + @lock = Mutex.new unless closed? register ObjectSpace.define_finalizer(self, self.class.finalize(kill_spec(@connection_global_id), @@ -164,6 +166,9 @@ def each # StopIteration raised by try_next ends this loop. loop do document = try_next + if explicitly_closed? + raise Error::InvalidCursorOperation, 'Cursor was explicitly closed' + end yield document if document end self @@ -172,6 +177,9 @@ def each # StopIteration raised by try_next ends this loop. loop do document = try_next + if explicitly_closed? + raise Error::InvalidCursorOperation, 'Cursor was explicitly closed' + end documents << document if document end documents @@ -296,6 +304,9 @@ def close ensure end_session @cursor_id = 0 + @lock.synchronize do + @explicitly_closed = true + end end # Get the parsed collection name. @@ -386,6 +397,12 @@ def fully_iterated? private + def explicitly_closed? + @lock.synchronize do + @explicitly_closed + end + end + def batch_size_for_get_more if batch_size && use_limit? [batch_size, @remaining].min diff --git a/spec/mongo/cursor_spec.rb b/spec/mongo/cursor_spec.rb index cc99640afc..3fa1768a3b 100644 --- a/spec/mongo/cursor_spec.rb +++ b/spec/mongo/cursor_spec.rb @@ -731,6 +731,19 @@ expect(cursor).to be_closed end + context 'when closed from another thread' do + it 'raises an error' do + Thread.new do + cursor.close + end + sleep(1) + expect(cursor).to be_closed + expect do + cursor.to_a + end.to raise_error Mongo::Error::InvalidCursorOperation + end + end + context 'when there is a socket error during close' do clean_slate From db1efb5a6f67cb16bed5c0c59346e29048e88b07 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 27 Sep 2022 11:08:06 +0200 Subject: [PATCH 053/198] RUBY-3112 Run all unified tests on serverless (#2634) --- .evergreen/run-tests-serverless.sh | 10 +++++++++- spec/runners/crud/requirement.rb | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index 4f96a3ea33..221da16c38 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -20,10 +20,18 @@ echo "Running specs" bundle exec rspec \ spec/spec_tests/crud_spec.rb \ - spec/spec_tests/crud_unified_spec.rb \ spec/spec_tests/retryable_reads_spec.rb \ spec/spec_tests/retryable_writes_spec.rb \ spec/spec_tests/transactions_spec.rb \ + spec/spec_tests/change_streams_unified_spec.rb \ + spec/spec_tests/client_side_encryption_unified_spec.rb \ + spec/spec_tests/command_monitoring_unified_spec.rb \ + spec/spec_tests/crud_unified_spec.rb \ + spec/spec_tests/gridfs_unified_spec.rb \ + spec/spec_tests/retryable_reads_unified_spec.rb \ + spec/spec_tests/retryable_writes_unified_spec.rb \ + spec/spec_tests/sdam_unified_spec.rb \ + spec/spec_tests/sessions_unified_spec.rb \ spec/spec_tests/transactions_unified_spec.rb kill_jruby diff --git a/spec/runners/crud/requirement.rb b/spec/runners/crud/requirement.rb index d8e98169b2..88b35d0c95 100644 --- a/spec/runners/crud/requirement.rb +++ b/spec/runners/crud/requirement.rb @@ -109,9 +109,9 @@ def satisfied? end end if @auth == true - ok &&= cc.auth_enabled? + ok &&= SpecConfig.instance.auth? elsif @auth == false - ok &&= !cc.auth_enabled? + ok &&= !SpecConfig.instance.auth? end if @csfle ok &&= !!(ENV['LIBMONGOCRYPT_PATH'] || ENV['FLE']) From c192289e9bdeff876bc82afecf35f60713c49c99 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 28 Sep 2022 10:04:53 -0400 Subject: [PATCH 054/198] RUBY-2707 Document list_collections in tutorial (#2628) --- docs/tutorials/quick-start.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tutorials/quick-start.txt b/docs/tutorials/quick-start.txt index 58eb1cde77..b8fddc6379 100644 --- a/docs/tutorials/quick-start.txt +++ b/docs/tutorials/quick-start.txt @@ -57,6 +57,7 @@ and show its collections: db.collections # returns a list of collection objects db.collection_names # returns a list of collection names + db.list_collections # returns a list of collection metadata hashes To access a collection, refer to it by name. From 6333c6d8e4c6e2a93731e6d3707e0b4f5c59858d Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 28 Sep 2022 11:16:04 -0400 Subject: [PATCH 055/198] RUBY-2592 add tests for load-balancers (#2638) * RUBY-2592 allow load balancers * RUBY-2592 fix all tests on load balancer single * RUBY-2592 fix test * RUBY-2592 require single lb --- lib/mongo/collection/view/change_stream.rb | 2 +- lib/mongo/error.rb | 1 + lib/mongo/error/connection_unavailable.rb | 27 +++++++++++++++++++ lib/mongo/server/connection_pool.rb | 5 +++- spec/integration/transaction_pinning_spec.rb | 4 +-- .../spec_tests/change_streams_unified_spec.rb | 1 + .../change-streams-errors.yml | 8 ++---- .../change-streams-resume-allowlist.yml | 4 +-- .../change-streams-resume-errorLabels.yml | 4 +-- 9 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 lib/mongo/error/connection_unavailable.rb diff --git a/lib/mongo/collection/view/change_stream.rb b/lib/mongo/collection/view/change_stream.rb index 7ea1e31a08..073b811a25 100644 --- a/lib/mongo/collection/view/change_stream.rb +++ b/lib/mongo/collection/view/change_stream.rb @@ -238,7 +238,7 @@ def close unless closed? begin @cursor.close - rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError + rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError, Error::ConnectionUnavailable # ignore end @cursor = nil diff --git a/lib/mongo/error.rb b/lib/mongo/error.rb index c3fa0c89e2..f647aa181e 100644 --- a/lib/mongo/error.rb +++ b/lib/mongo/error.rb @@ -142,6 +142,7 @@ def write_concern_error_labels require 'mongo/error/closed_stream' require 'mongo/error/connection_check_out_timeout' require 'mongo/error/connection_perished' +require 'mongo/error/connection_unavailable' require 'mongo/error/credential_check_error' require 'mongo/error/crypt_error' require 'mongo/error/extra_file_chunk' diff --git a/lib/mongo/error/connection_unavailable.rb b/lib/mongo/error/connection_unavailable.rb new file mode 100644 index 0000000000..7713fed0e7 --- /dev/null +++ b/lib/mongo/error/connection_unavailable.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2022 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Error + + # Exception raised when trying to check out a connection with a specific + # global id, and the connection for that global id no longer exists in the + # pool. + class ConnectionUnavailable < Error + end + end +end diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index 7db3406269..0e82c8af52 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -370,7 +370,10 @@ def check_out(connection_global_id: nil) end if @server.load_balancer? && connection_global_id - # We need a particular connection, and if it is not available + unless @checked_out_connections.detect { |c| c.global_id == connection_global_id } + raise Error::ConnectionUnavailable.new + end + # We need a particular connection, and if it is not available # we can wait for an in-progress operation to return # such a connection to the pool. else diff --git a/spec/integration/transaction_pinning_spec.rb b/spec/integration/transaction_pinning_spec.rb index 1863c3e510..4fb8080c44 100644 --- a/spec/integration/transaction_pinning_spec.rb +++ b/spec/integration/transaction_pinning_spec.rb @@ -71,7 +71,7 @@ client["tx_pin"].create end - it 'raises ConnectionCheckOutTimeout' do + it 'raises ConnectionUnavailable' do session = client.start_session session.start_transaction client["tx_pin"].insert_one({test: 1}, session: session) @@ -84,7 +84,7 @@ lambda do client["tx_pin"].insert_one({test: 2}, session: session) - end.should raise_error(Mongo::Error::ConnectionCheckOutTimeout) + end.should raise_error(Mongo::Error::ConnectionUnavailable) end end diff --git a/spec/spec_tests/change_streams_unified_spec.rb b/spec/spec_tests/change_streams_unified_spec.rb index f9da7dd06c..cb2cb07ce8 100644 --- a/spec/spec_tests/change_streams_unified_spec.rb +++ b/spec/spec_tests/change_streams_unified_spec.rb @@ -9,5 +9,6 @@ CHANGE_STREAM_UNIFIED_TESTS = Dir.glob("#{base}/**/*.yml").sort describe 'Change stream unified spec tests' do + require_no_multi_mongos define_unified_spec_tests(base, CHANGE_STREAM_UNIFIED_TESTS) end diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml b/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml index b2279954bf..47ad1d07ab 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml @@ -74,9 +74,7 @@ tests: - description: Change Stream should error when _id is projected out runOnRequirements: - minServerVersion: "4.1.11" - # Removed load balanced tests as per RUBY-2952 because they were causing - # failures. - topologies: [ replicaset, sharded-replicaset ] #, load-balanced ] + topologies: [ replicaset, sharded-replicaset, load-balanced ] operations: - name: createChangeStream object: *collection0 @@ -95,9 +93,7 @@ tests: - description: change stream errors on ElectionInProgress runOnRequirements: - minServerVersion: "4.2" - # Removed load balanced tests as per RUBY-2952 because they were causing - # failures. - topologies: [ replicaset, sharded-replicaset ] #, load-balanced ] + topologies: [ replicaset, sharded-replicaset, load-balanced ] operations: - name: failPoint object: testRunner diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml b/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml index 6a883443c8..5c69e23010 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml @@ -5,9 +5,7 @@ schemaVersion: "1.7" runOnRequirements: - minServerVersion: "3.6" - # Removed load balanced tests as per RUBY-2952 because they were causing - # failures. - topologies: [ replicaset, sharded-replicaset ] #, load-balanced ] + topologies: [ replicaset, sharded-replicaset, load-balanced ] serverless: forbid createEntities: diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml b/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml index e2758fc780..5879b59c6a 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml @@ -5,9 +5,7 @@ schemaVersion: "1.7" runOnRequirements: - minServerVersion: "4.3.1" - # Removed load balanced tests as per RUBY-2952 because they were causing - # failures. - topologies: [ replicaset, sharded-replicaset ] #, load-balanced ] + topologies: [ replicaset, sharded-replicaset, load-balanced ] serverless: forbid createEntities: From cb84f700762affcaed84e812a7995bacd345d0a3 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 28 Sep 2022 11:16:14 -0400 Subject: [PATCH 056/198] RUBY-2897 Remove use of example.com in Client Side Encryption prose tests (#2636) * RUBY-2897 Remove use of example.com in Client Side Encryption prose tests * change error regex * Update spec/integration/client_side_encryption/custom_endpoint_spec.rb --- .../client_side_encryption/custom_endpoint_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/integration/client_side_encryption/custom_endpoint_spec.rb b/spec/integration/client_side_encryption/custom_endpoint_spec.rb index c6f4668978..6106d5f5e1 100644 --- a/spec/integration/client_side_encryption/custom_endpoint_spec.rb +++ b/spec/integration/client_side_encryption/custom_endpoint_spec.rb @@ -119,11 +119,11 @@ context 'with region, key, and endpoint at incorrect domain' do let(:master_key) do - master_key_template.merge({endpoint: "example.com"}) + master_key_template.merge({endpoint: "doesnotexist.invalid"}) end let(:error_regex) do - /parse error/ + /SocketError: getaddrinfo:/ end it_behaves_like 'raising a KMS error' From be647921b119199649e6d34c3febd6f2abd889ea Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 29 Sep 2022 09:53:43 -0400 Subject: [PATCH 057/198] RUBY-2768 Communicate maintenance & bug fix policy to userbase (#2635) * RUBY-2768 Communicate maintenance & bug fix policy to userbase * Update README.md Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> * address comments * spacing * RUBY-2768 address comments Co-authored-by: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index dee1bf8bc9..01ff3833e4 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,16 @@ affects more than the driver alone (for example, a feature which requires MongoDB server support), please submit your idea through the [MongoDB Feedback Forum](https://feedback.mongodb.com/forums/924286-drivers). +## Maintenance and Bug Fix Policy + +New driver functionality is generally added in a backwards-compatible manner +and results in new minor driver releases (2.x). Bug fixes are generally made on +master first and are backported to the current minor driver release. Exceptions +may be made on a case-by-case basis, for example security fixes may be +backported to older stable branches. Only the most recent minor driver release +is officially supported. Customers should use the most recent driver release in +their applications. + ## Running Tests Please refer to [spec/README.md](spec/README.md) for instructions on how From 80567185eed7bcce26af771fd219495c8720b783 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 29 Sep 2022 09:54:14 -0400 Subject: [PATCH 058/198] RUBY-3118 Fix flaky sdam unified test (#2623) * RUBY-3118 Fix flaky sdam unified test * RUBY-3118 add pool cleared event --- spec/runners/unified/assertions.rb | 6 ++++++ spec/runners/unified/support_operations.rb | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spec/runners/unified/assertions.rb b/spec/runners/unified/assertions.rb index d23a57f9ba..fb372dec94 100644 --- a/spec/runners/unified/assertions.rb +++ b/spec/runners/unified/assertions.rb @@ -216,6 +216,12 @@ def assert_eq(actual, expected, msg) end end + def assert_gte(actual, expected, msg) + unless actual >= expected + raise Error::ResultMismatch, "#{msg}: expected #{expected}, actual #{actual}" + end + end + def assert_matches(actual, expected, msg) if actual.nil? if expected.is_a?(Hash) && expected.keys == ["$$unsetOrMatches"] diff --git a/spec/runners/unified/support_operations.rb b/spec/runners/unified/support_operations.rb index 7446654f72..9e5f7fe8b0 100644 --- a/spec/runners/unified/support_operations.rb +++ b/spec/runners/unified/support_operations.rb @@ -260,7 +260,16 @@ def assert_event_count(op) count = args.use!('count') events = select_events(subscriber, event) - assert_eq(events.length, count, "Expected event #{event} to occur #{count} times but received it #{events.length} times.") + if %w(serverDescriptionChangedEvent poolClearedEvent).include?(event.keys.first) + # We publish SDAM events from both regular and push monitors. + # This means sometimes there are two ServerMarkedUnknownEvent + # events published for the same server transition. + # Allow actual event count to be at least the expected event count + # in case there are multiple transitions in a single test. + assert_gte(events.length, count, "Expected event #{event} to occur #{count} times but received it #{events.length} times.") + else + assert_eq(events.length, count, "Expected event #{event} to occur #{count} times but received it #{events.length} times.") + end end end From 825ac4a391ffab203bc3122d40ef862489b753a2 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 29 Sep 2022 12:28:06 -0400 Subject: [PATCH 059/198] RUBY-3134 attempt to fix flaky speculativeAuthenitcate test (#2643) --- spec/runners/unified/assertions.rb | 2 +- spec/runners/unified/test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/runners/unified/assertions.rb b/spec/runners/unified/assertions.rb index fb372dec94..a0136a9c26 100644 --- a/spec/runners/unified/assertions.rb +++ b/spec/runners/unified/assertions.rb @@ -156,7 +156,7 @@ def assert_events else false end - actual_events = subscriber.wanted_events(@observe_sensitive[client]) + actual_events = subscriber.wanted_events(@observe_sensitive[client_id]) case spec.use('eventType') when nil, 'command' actual_events.select! do |event| diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index 94b195a365..e5d973d29f 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -183,7 +183,7 @@ def generate_entities(es) end create_client(**opts).tap do |client| - @observe_sensitive[client] = spec.use('observeSensitiveCommands') + @observe_sensitive[id] = spec.use('observeSensitiveCommands') @subscribers[client] ||= subscriber end when 'database' From a25b38199e2213131985f431be5e0e8c8f6c9456 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 30 Sep 2022 10:34:15 -0400 Subject: [PATCH 060/198] RUBY-2803 Add a load balancer test case for SRV resolving to 0 hosts and sync lb tests (#2642) --- .../load-balanced/loadBalanced-directConnection.yml | 11 ++++++----- .../load-balanced/loadBalanced-replicaSet-errors.yml | 4 ++-- .../load-balanced/loadBalanced-true-txt.yml | 9 +++++---- ...vMaxHosts-conflicts_with_loadBalanced-true-txt.yml | 2 +- .../load-balanced/srvMaxHosts-zero-txt.yml | 7 ++++--- .../load-balanced/srvMaxHosts-zero.yml | 7 ++++--- 6 files changed, 22 insertions(+), 18 deletions(-) diff --git a/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-directConnection.yml b/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-directConnection.yml index 0c96f352c2..7a77944a30 100644 --- a/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-directConnection.yml +++ b/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-directConnection.yml @@ -1,13 +1,14 @@ -# The TXT record for test20.test.build.10gen.cc contains loadBalanced=true. +# The TXT record for test24.test.build.10gen.cc contains loadBalanced=true. # DRIVERS-1721 introduced this test as passing. -uri: "mongodb+srv://test20.test.build.10gen.cc/?directConnection=false" +uri: "mongodb+srv://test24.test.build.10gen.cc/?directConnection=false" seeds: - - localhost.test.build.10gen.cc:27017 + - localhost.test.build.10gen.cc:8000 hosts: # In LB mode, the driver does not do server discovery, so the hostname does - # not get resolved to localhost:27017. - - localhost.test.build.10gen.cc:27017 + # not get resolved to localhost:8000. + - localhost.test.build.10gen.cc:8000 options: loadBalanced: true ssl: true directConnection: false +ping: true diff --git a/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-replicaSet-errors.yml b/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-replicaSet-errors.yml index 208c6a9739..896f7de7b5 100644 --- a/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-replicaSet-errors.yml +++ b/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-replicaSet-errors.yml @@ -1,5 +1,5 @@ -# The TXT record for test20.test.build.10gen.cc contains loadBalanced=true. -uri: "mongodb+srv://test20.test.build.10gen.cc/?replicaSet=replset" +# The TXT record for test24.test.build.10gen.cc contains loadBalanced=true. +uri: "mongodb+srv://test24.test.build.10gen.cc/?replicaSet=replset" seeds: [] hosts: [] error: true diff --git a/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-true-txt.yml b/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-true-txt.yml index bbf9e8c599..c373192f01 100644 --- a/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-true-txt.yml +++ b/spec/spec_tests/data/seed_list_discovery/load-balanced/loadBalanced-true-txt.yml @@ -1,10 +1,11 @@ -uri: "mongodb+srv://test20.test.build.10gen.cc/" +uri: "mongodb+srv://test24.test.build.10gen.cc/" seeds: - - localhost.test.build.10gen.cc:27017 + - localhost.test.build.10gen.cc:8000 hosts: # In LB mode, the driver does not do server discovery, so the hostname does - # not get resolved to localhost:27017. - - localhost.test.build.10gen.cc:27017 + # not get resolved to localhost:8000. + - localhost.test.build.10gen.cc:8000 options: loadBalanced: true ssl: true +ping: true diff --git a/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true-txt.yml b/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true-txt.yml index bf0636039c..01379cd205 100644 --- a/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true-txt.yml +++ b/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-conflicts_with_loadBalanced-true-txt.yml @@ -1,4 +1,4 @@ -uri: "mongodb+srv://test20.test.build.10gen.cc/?srvMaxHosts=1" +uri: "mongodb+srv://test24.test.build.10gen.cc/?srvMaxHosts=1" seeds: [] hosts: [] error: true diff --git a/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-zero-txt.yml b/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-zero-txt.yml index 3ec00b574d..f223a8d558 100644 --- a/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-zero-txt.yml +++ b/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-zero-txt.yml @@ -1,10 +1,11 @@ # loadBalanced=true (TXT) is permitted because srvMaxHosts is non-positive -uri: "mongodb+srv://test20.test.build.10gen.cc/?srvMaxHosts=0" +uri: "mongodb+srv://test24.test.build.10gen.cc/?srvMaxHosts=0" seeds: - - localhost.test.build.10gen.cc:27017 + - localhost.test.build.10gen.cc:8000 hosts: - - localhost.test.build.10gen.cc:27017 + - localhost.test.build.10gen.cc:8000 options: loadBalanced: true srvMaxHosts: 0 ssl: true +ping: true diff --git a/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-zero.yml b/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-zero.yml index a8d3a19f36..f8343e0e3e 100644 --- a/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-zero.yml +++ b/spec/spec_tests/data/seed_list_discovery/load-balanced/srvMaxHosts-zero.yml @@ -1,10 +1,11 @@ # loadBalanced=true is permitted because srvMaxHosts is non-positive -uri: "mongodb+srv://test3.test.build.10gen.cc/?loadBalanced=true&srvMaxHosts=0" +uri: "mongodb+srv://test23.test.build.10gen.cc/?loadBalanced=true&srvMaxHosts=0" seeds: - - localhost.test.build.10gen.cc:27017 + - localhost.test.build.10gen.cc:8000 hosts: - - localhost.test.build.10gen.cc:27017 + - localhost.test.build.10gen.cc:8000 options: loadBalanced: true srvMaxHosts: 0 ssl: true +ping: true From f284c06db2016b5d54ebda521d265b6dd4281209 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Mon, 3 Oct 2022 13:24:12 -0400 Subject: [PATCH 061/198] RUBY-2942: Update documentation links (#2644) --- README.md | 4 +-- docs/reference/authentication.txt | 2 +- docs/reference/geospatial-search.txt | 2 +- lib/mongo/auth/roles.rb | 32 +++++++++---------- lib/mongo/uri.rb | 4 +-- .../versioned_api_examples_spec.rb | 2 +- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 01ff3833e4..6d59fe9802 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The Ruby driver supports Ruby 2.5-3.0 and JRuby 9.2. ## Documentation High level documentation and usage examples are located -[here](http://docs.mongodb.org/ecosystem/drivers/ruby/). +[here](https://www.mongodb.com/docs/ecosystem/drivers/ruby/). API documentation for the most recent release can be found [here](https://mongodb.com/docs/ruby-driver/current/api/). @@ -58,7 +58,7 @@ is publicly visible. If you have identified a potential security-related issue in the Ruby driver (or any other MongoDB product), please report it by following the -[instructions here](http://docs.mongodb.org/manual/tutorial/create-a-vulnerability-report). +[instructions here](https://www.mongodb.com/docs/manual/tutorial/create-a-vulnerability-report). ## Product Feature Requests diff --git a/docs/reference/authentication.txt b/docs/reference/authentication.txt index 7371ae1003..acf9ab7f72 100644 --- a/docs/reference/authentication.txt +++ b/docs/reference/authentication.txt @@ -466,7 +466,7 @@ via client configuration, or the path to a keytab file may be provided via configuration stored in the ``java.security.auth.login.config`` system property. Additionally, the Java runtime environment must be configured for Kerberos; please refer to the `MongoDB Java Driver Kerberos documentation -`_ +`_ for more information. .. note:: diff --git a/docs/reference/geospatial-search.txt b/docs/reference/geospatial-search.txt index b1626d6c01..8550d64b31 100644 --- a/docs/reference/geospatial-search.txt +++ b/docs/reference/geospatial-search.txt @@ -18,7 +18,7 @@ with the Ruby driver. The examples on this page use a sample collection called ``restaurants`` in the ``test`` database. -A `sample dataset `_ +A `sample dataset `_ is available for download. The following is a sample document in the ``restaurants`` diff --git a/lib/mongo/auth/roles.rb b/lib/mongo/auth/roles.rb index 8f9e00a955..1713cd5aa0 100644 --- a/lib/mongo/auth/roles.rb +++ b/lib/mongo/auth/roles.rb @@ -23,82 +23,82 @@ module Auth # @since 2.0.0 module Roles - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#backup + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#backup # # @since 2.0.0 BACKUP = 'backup'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#clusterAdmin + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#clusterAdmin # # @since 2.0.0 CLUSTER_ADMIN = 'clusterAdmin'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#clusterManager + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#clusterManager # # @since 2.0.0 CLUSTER_MANAGER = 'clusterManager'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#clusterMonitor + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#clusterMonitor # # @since 2.0.0 CLUSTER_MONITOR = 'clusterMonitor'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#dbAdmin + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#dbAdmin # # @since 2.0.0 DATABASE_ADMIN = 'dbAdmin'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#dbAdminAnyDatabase + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#dbAdminAnyDatabase # # @since 2.0.0 DATABASE_ADMIN_ANY_DATABASE = 'dbAdminAnyDatabase'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#dbOwner + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#dbOwner # # @since 2.0.0 DATABASE_OWNER = 'dbOwner'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#hostManager + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#hostManager # # @since 2.0.0 HOST_MANAGER = 'hostManager'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#read + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#read # # @since 2.0.0 READ = 'read'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#readAnyDatabase + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#readAnyDatabase # # @since 2.0.0 READ_ANY_DATABASE = 'readAnyDatabase'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#readWriteAnyDatabase + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#readWriteAnyDatabase # # @since 2.0.0 READ_WRITE_ANY_DATABASE = 'readWriteAnyDatabase'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#readWrite + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#readWrite # # @since 2.0.0 READ_WRITE = 'readWrite'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#restore + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#restore # # @since 2.0.0 RESTORE = 'restore'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#root + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#root # # @since 2.0.0 ROOT = 'root'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#userAdmin + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#userAdmin # # @since 2.0.0 USER_ADMIN = 'userAdmin'.freeze - # @see http://docs.mongodb.org/manual/reference/built-in-roles/#userAdminAnyDatabase + # @see https://www.mongodb.com/docs/manual/reference/built-in-roles/#userAdminAnyDatabase # # @since 2.0.0 USER_ADMIN_ANY_DATABASE = 'userAdminAnyDatabase'.freeze diff --git a/lib/mongo/uri.rb b/lib/mongo/uri.rb index f0def070ba..4ab76b8e0a 100644 --- a/lib/mongo/uri.rb +++ b/lib/mongo/uri.rb @@ -20,7 +20,7 @@ module Mongo # The URI class provides a way for users to parse the MongoDB uri as # defined in the connection string format spec. # - # http://docs.mongodb.org/manual/reference/connection-string/ + # https://www.mongodb.com/docs/manual/reference/connection-string/ # # @example Use the uri string to make a client connection. # uri = Mongo::URI.new('mongodb://localhost:27017') @@ -80,7 +80,7 @@ class URI # MongoDB URI (connection string) documentation url # # @since 2.0.0 - HELP = 'http://docs.mongodb.org/manual/reference/connection-string/'.freeze + HELP = 'https://www.mongodb.com/docs/manual/reference/connection-string/'.freeze # Unsafe characters that must be urlencoded. # diff --git a/spec/integration/versioned_api_examples_spec.rb b/spec/integration/versioned_api_examples_spec.rb index 8071926dc6..ffcc90d6cb 100644 --- a/spec/integration/versioned_api_examples_spec.rb +++ b/spec/integration/versioned_api_examples_spec.rb @@ -98,7 +98,7 @@ # Start Versioned API Example 6 # Mongo::Error::OperationFailure: - # [323:APIStrictError]: Provided apiStrict:true, but the command count is not in API Version 1. Information on supported commands and migrations in API Version 1 can be found at https://dochub.mongodb.org/core/manual-versioned-api + # [323:APIStrictError]: Provided apiStrict:true, but the command count is not in API Version 1. Information on supported commands and migrations in API Version 1 can be found at https://www.mongodb.com/docs/manual/reference/stable-api # End Versioned API Example 6 From b70744de76b2fc6bc4232d353596e05650811847 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Mon, 3 Oct 2022 13:30:24 -0400 Subject: [PATCH 062/198] RUBY-3139: Update Ruby Team details (#2645) --- mongo.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mongo.gemspec b/mongo.gemspec index 30b9a3887b..0b22890c09 100644 --- a/mongo.gemspec +++ b/mongo.gemspec @@ -6,8 +6,8 @@ Gem::Specification.new do |s| s.name = 'mongo' s.version = Mongo::VERSION s.platform = Gem::Platform::RUBY - - s.authors = ['Tyler Brock', 'Emily Stolfo', 'Durran Jordan'] + s.authors = ["The MongoDB Ruby Team"] + s.email = "dbx-ruby@mongodb.com" s.homepage = 'https://mongodb.com/docs/ruby-driver/' s.summary = 'Ruby driver for MongoDB' s.description = 'A Ruby driver for MongoDB' From 15a77ccf0dc5c6d1fbd49754358a95b019664190 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 7 Oct 2022 12:08:24 -0400 Subject: [PATCH 063/198] RUBY-2808 add other cmap tests that work (#2641) * RUBY-2808 Add a CMAP test that verifies the background thread hands over connections to threads doing checkout * RUBY-2808 add ready op * RUBY-2808 add cmap tests that work * RUBY-2808 remove maxConnecting test --- spec/runners/cmap.rb | 6 ++++++ spec/runners/cmap/verifier.rb | 4 +++- spec/spec_tests/data/cmap/connection-must-have-id.yml | 2 ++ spec/spec_tests/data/cmap/connection-must-order-ids.yml | 2 ++ spec/spec_tests/data/cmap/pool-checkin-destroy-closed.yml | 2 ++ spec/spec_tests/data/cmap/pool-checkin-destroy-stale.yml | 2 ++ spec/spec_tests/data/cmap/pool-checkin-make-available.yml | 4 +++- spec/spec_tests/data/cmap/pool-checkin.yml | 2 ++ spec/spec_tests/data/cmap/pool-checkout-connection.yml | 2 ++ spec/spec_tests/data/cmap/pool-checkout-error-closed.yml | 2 ++ spec/spec_tests/data/cmap/pool-checkout-multiple.yml | 2 ++ spec/spec_tests/data/cmap/pool-close-destroy-conns.yml | 2 ++ spec/spec_tests/data/cmap/pool-create-max-size.yml | 2 ++ spec/spec_tests/data/cmap/wait-queue-fairness.yml | 2 ++ spec/spec_tests/data/cmap/wait-queue-timeout.yml | 4 +++- 15 files changed, 37 insertions(+), 3 deletions(-) diff --git a/spec/runners/cmap.rb b/spec/runners/cmap.rb index 85c0715877..8213d9ec97 100644 --- a/spec/runners/cmap.rb +++ b/spec/runners/cmap.rb @@ -265,6 +265,8 @@ def run(pool, state, main_thread = true) case name when 'start' run_start_op(state) + when 'ready' + run_ready_op(state) when 'wait' run_wait_op(state) when 'waitForThread' @@ -318,6 +320,10 @@ def run_start_op(state) end end + # Ruby driver does not have the concept of pausing and readying the pool. + def run_ready_op(_state) + end + def run_wait_op(_state) sleep(ms / 1000.0) end diff --git a/spec/runners/cmap/verifier.rb b/spec/runners/cmap/verifier.rb index 491308b3d8..b17e820b14 100644 --- a/spec/runners/cmap/verifier.rb +++ b/spec/runners/cmap/verifier.rb @@ -41,7 +41,9 @@ def verify_hashes(actual, expected) end end - expect(actual_modified).to eq(expected) + expected.each do |k, v| + expect(actual_modified[k]).to eq(v) + end end end end diff --git a/spec/spec_tests/data/cmap/connection-must-have-id.yml b/spec/spec_tests/data/cmap/connection-must-have-id.yml index 5b7b660e54..b4a2cd3440 100644 --- a/spec/spec_tests/data/cmap/connection-must-have-id.yml +++ b/spec/spec_tests/data/cmap/connection-must-have-id.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must have an ID number associated with it operations: + - name: ready - name: checkOut - name: checkOut events: @@ -23,5 +24,6 @@ events: address: 42 ignore: - ConnectionPoolCreated + - ConnectionPoolReady - ConnectionPoolClosed - ConnectionReady diff --git a/spec/spec_tests/data/cmap/connection-must-order-ids.yml b/spec/spec_tests/data/cmap/connection-must-order-ids.yml index 162acfa797..3e352e7160 100644 --- a/spec/spec_tests/data/cmap/connection-must-order-ids.yml +++ b/spec/spec_tests/data/cmap/connection-must-order-ids.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must have IDs assigned in order of creation operations: + - name: ready - name: checkOut - name: checkOut events: @@ -23,5 +24,6 @@ events: address: 42 ignore: - ConnectionPoolCreated + - ConnectionPoolReady - ConnectionPoolClosed - ConnectionReady diff --git a/spec/spec_tests/data/cmap/pool-checkin-destroy-closed.yml b/spec/spec_tests/data/cmap/pool-checkin-destroy-closed.yml index cf9bdfc1d7..cd6f6bb27c 100644 --- a/spec/spec_tests/data/cmap/pool-checkin-destroy-closed.yml +++ b/spec/spec_tests/data/cmap/pool-checkin-destroy-closed.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must destroy checked in connection if pool has been closed operations: + - name: ready - name: checkOut label: conn - name: close @@ -22,6 +23,7 @@ events: address: 42 ignore: - ConnectionPoolCreated + - ConnectionPoolReady - ConnectionCreated - ConnectionReady - ConnectionCheckOutStarted diff --git a/spec/spec_tests/data/cmap/pool-checkin-destroy-stale.yml b/spec/spec_tests/data/cmap/pool-checkin-destroy-stale.yml index 2c95d5c03b..f54ae7675d 100644 --- a/spec/spec_tests/data/cmap/pool-checkin-destroy-stale.yml +++ b/spec/spec_tests/data/cmap/pool-checkin-destroy-stale.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must destroy checked in connection if it is stale operations: + - name: ready - name: checkOut label: conn - name: clear @@ -22,6 +23,7 @@ events: address: 42 ignore: - ConnectionPoolCreated + - ConnectionPoolReady - ConnectionCreated - ConnectionReady - ConnectionCheckOutStarted diff --git a/spec/spec_tests/data/cmap/pool-checkin-make-available.yml b/spec/spec_tests/data/cmap/pool-checkin-make-available.yml index bebc035f70..5179432788 100644 --- a/spec/spec_tests/data/cmap/pool-checkin-make-available.yml +++ b/spec/spec_tests/data/cmap/pool-checkin-make-available.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must make valid checked in connection available operations: + - name: ready - name: checkOut label: conn - name: checkIn @@ -19,6 +20,7 @@ events: address: 42 ignore: - ConnectionPoolCreated + - ConnectionPoolReady - ConnectionCreated - ConnectionReady - - ConnectionCheckOutStarted \ No newline at end of file + - ConnectionCheckOutStarted diff --git a/spec/spec_tests/data/cmap/pool-checkin.yml b/spec/spec_tests/data/cmap/pool-checkin.yml index c2560a5cd3..b6a4320376 100644 --- a/spec/spec_tests/data/cmap/pool-checkin.yml +++ b/spec/spec_tests/data/cmap/pool-checkin.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must have a method of allowing the driver to check in a connection operations: + - name: ready - name: checkOut label: conn - name: checkIn @@ -12,6 +13,7 @@ events: address: 42 ignore: - ConnectionPoolCreated + - ConnectionPoolReady - ConnectionCreated - ConnectionReady - ConnectionClosed diff --git a/spec/spec_tests/data/cmap/pool-checkout-connection.yml b/spec/spec_tests/data/cmap/pool-checkout-connection.yml index d17a030291..bbbd03ff5c 100644 --- a/spec/spec_tests/data/cmap/pool-checkout-connection.yml +++ b/spec/spec_tests/data/cmap/pool-checkout-connection.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must be able to check out a connection operations: + - name: ready - name: checkOut events: - type: ConnectionCheckOutStarted @@ -16,4 +17,5 @@ events: connectionId: 1 address: 42 ignore: + - ConnectionPoolReady - ConnectionPoolCreated diff --git a/spec/spec_tests/data/cmap/pool-checkout-error-closed.yml b/spec/spec_tests/data/cmap/pool-checkout-error-closed.yml index 6621685545..4d1b0f3b29 100644 --- a/spec/spec_tests/data/cmap/pool-checkout-error-closed.yml +++ b/spec/spec_tests/data/cmap/pool-checkout-error-closed.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must throw error if checkOut is called on a closed pool operations: + - name: ready - name: checkOut label: conn1 - name: checkIn @@ -31,6 +32,7 @@ events: address: 42 reason: poolClosed ignore: + - ConnectionPoolReady - ConnectionCreated - ConnectionReady - ConnectionClosed diff --git a/spec/spec_tests/data/cmap/pool-checkout-multiple.yml b/spec/spec_tests/data/cmap/pool-checkout-multiple.yml index 714506ef7f..03a355df06 100644 --- a/spec/spec_tests/data/cmap/pool-checkout-multiple.yml +++ b/spec/spec_tests/data/cmap/pool-checkout-multiple.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: must be able to check out multiple connections at the same time operations: + - name: ready - name: start target: thread1 - name: start @@ -32,6 +33,7 @@ events: address: 42 ignore: - ConnectionCreated + - ConnectionPoolReady - ConnectionReady - ConnectionPoolCreated - ConnectionCheckOutStarted diff --git a/spec/spec_tests/data/cmap/pool-close-destroy-conns.yml b/spec/spec_tests/data/cmap/pool-close-destroy-conns.yml index 65b13a6d51..0140cdd198 100644 --- a/spec/spec_tests/data/cmap/pool-close-destroy-conns.yml +++ b/spec/spec_tests/data/cmap/pool-close-destroy-conns.yml @@ -2,6 +2,7 @@ version: 1 style: unit description: When a pool is closed, it MUST first destroy all available connections in that pool operations: + - name: ready - name: checkOut - name: checkOut label: conn @@ -21,6 +22,7 @@ events: address: 42 ignore: - ConnectionCreated + - ConnectionPoolReady - ConnectionReady - ConnectionPoolCreated - ConnectionCheckOutStarted diff --git a/spec/spec_tests/data/cmap/pool-create-max-size.yml b/spec/spec_tests/data/cmap/pool-create-max-size.yml index 64e521c7ec..fa85d9eb48 100644 --- a/spec/spec_tests/data/cmap/pool-create-max-size.yml +++ b/spec/spec_tests/data/cmap/pool-create-max-size.yml @@ -4,6 +4,7 @@ description: must never exceed maxPoolSize total connections poolOptions: maxPoolSize: 3 operations: + - name: ready - name: checkOut label: conn1 - name: checkOut @@ -69,3 +70,4 @@ events: address: 42 ignore: - ConnectionReady + - ConnectionPoolReady diff --git a/spec/spec_tests/data/cmap/wait-queue-fairness.yml b/spec/spec_tests/data/cmap/wait-queue-fairness.yml index 024ec69316..e7027c3304 100644 --- a/spec/spec_tests/data/cmap/wait-queue-fairness.yml +++ b/spec/spec_tests/data/cmap/wait-queue-fairness.yml @@ -5,6 +5,7 @@ poolOptions: maxPoolSize: 1 waitQueueTimeoutMS: 5000 operations: + - name: ready # Check out sole connection in pool - name: checkOut label: conn0 @@ -121,4 +122,5 @@ ignore: - ConnectionCreated - ConnectionReady - ConnectionClosed + - ConnectionPoolReady - ConnectionPoolCreated diff --git a/spec/spec_tests/data/cmap/wait-queue-timeout.yml b/spec/spec_tests/data/cmap/wait-queue-timeout.yml index eba4ab638d..5433c14898 100644 --- a/spec/spec_tests/data/cmap/wait-queue-timeout.yml +++ b/spec/spec_tests/data/cmap/wait-queue-timeout.yml @@ -3,8 +3,9 @@ style: unit description: must aggressively timeout threads enqueued longer than waitQueueTimeoutMS poolOptions: maxPoolSize: 1 - waitQueueTimeoutMS: 20 + waitQueueTimeoutMS: 50 operations: + - name: ready # Check out only possible connection - name: checkOut label: conn0 @@ -44,3 +45,4 @@ ignore: - ConnectionReady - ConnectionClosed - ConnectionPoolCreated + - ConnectionPoolReady From 829b7fb9c595b92f5e4943e583fc15eadb2b5724 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 12 Oct 2022 10:29:45 -0400 Subject: [PATCH 064/198] RUBY-1768 change to_s to return original URI (#2596) * RUBY-1768 change to_s to return original URI * RUBY-1768 reconstruct the uri from its parts and test * RUBY-1768 revert to identity * RUBY-1768 make stringify methods * RUBY-1768 add stringify tests * RUBY-1768 fix all to_s tests * RUBY-1768 docs and tests * RUBY-1768 fix kerberos tests * Update lib/mongo/uri.rb --- lib/mongo/uri.rb | 46 ++- lib/mongo/uri/options_mapper.rb | 172 +++++++++- spec/mongo/uri/options_mapper_spec.rb | 452 +++++++++++++++++++++++++- spec/mongo/uri/srv_protocol_spec.rb | 150 ++++++++- spec/mongo/uri_spec.rb | 143 +++++++- 5 files changed, 931 insertions(+), 32 deletions(-) diff --git a/lib/mongo/uri.rb b/lib/mongo/uri.rb index 4ab76b8e0a..c386b26bfa 100644 --- a/lib/mongo/uri.rb +++ b/lib/mongo/uri.rb @@ -328,8 +328,45 @@ def database @database ? @database : Database::ADMIN end + # Get the uri as a string. + # + # @example Get the uri as a string. + # uri.to_s + # + # @return [ String ] The uri string. + def to_s + reconstruct_uri + end + private + # Reconstruct the URI from its parts. Invalid options are dropped and options + # are converted to camelCase. + # + # @return [ String ] the uri. + def reconstruct_uri + servers = @servers.join(',') + options = options_mapper.ruby_to_string(@uri_options).map do |k, vs| + unless vs.nil? + if vs.is_a?(Array) + vs.map { |v| "#{k}=#{v}" }.join('&') + else + "#{k}=#{vs}" + end + end + end.compact.join('&') + + uri = "#{scheme}#{SCHEME_DELIM}" + uri += @user.to_s if @user + uri += "#{AUTH_USER_PWD_DELIM}#{@password}" if @password + uri += "@" if @user || @password + uri += @query_hostname || servers + uri += "/" if @database || !options.empty? + uri += @database.to_s if @database + uri += "?#{options}" unless options.empty? + uri + end + def scheme MONGODB_SCHEME end @@ -382,15 +419,6 @@ def parse!(remaining) raise_invalid_error!(e.message) end - def extract_db_opts!(string) - db_opts, _, creds_hosts = string.reverse.partition(DATABASE_DELIM) - db_opts, creds_hosts = creds_hosts, db_opts if creds_hosts.empty? - if db_opts.empty? && creds_hosts.include?(URI_OPTS_DELIM) - raise_invalid_error!(INVALID_OPTS_DELIM) - end - [ creds_hosts, db_opts ].map { |s| s.reverse } - end - def options_mapper @options_mapper ||= OptionsMapper.new( logger: @options[:logger], diff --git a/lib/mongo/uri/options_mapper.rb b/lib/mongo/uri/options_mapper.rb index 5243e0c331..1f63ec49f2 100644 --- a/lib/mongo/uri/options_mapper.rb +++ b/lib/mongo/uri/options_mapper.rb @@ -115,7 +115,6 @@ def smc_to_ruby(opts) end end - #p uri_options uri_options end @@ -134,8 +133,51 @@ def ruby_to_smc(opts) v = opts[spec[:name]] end unless v.nil? - if spec[:type] - v = send("revert_#{spec[:type]}", v) + if type = spec[:type] + v = send("revert_#{type}", v) + end + canonical_key = URI_OPTION_CANONICAL_NAMES[uri_key] + unless canonical_key + raise ArgumentError, "Option #{uri_key} is not known" + end + rv[canonical_key] = v + end + end + # For options that default to true, remove the value if it is true. + %w(retryReads retryWrites).each do |k| + if rv[k] + rv.delete(k) + end + end + # Remove auth source when it is $external for mechanisms that default + # (or require) that auth source. + if %w(MONGODB-AWS).include?(rv['authMechanism']) && rv['authSource'] == '$external' + rv.delete('authSource') + end + # ssl and tls are aliases, remove ssl ones + rv.delete('ssl') + # TODO remove authSource if it is the same as the database, + # requires this method to know the database specified in the client. + rv + end + + # Converts Ruby options provided to their representation in a URI string. + # + # @param [ Hash ] opts Ruby options to convert. + # + # @return [ Hash ] URI string hash. + def ruby_to_string(opts) + rv = {} + URI_OPTION_MAP.each do |uri_key, spec| + if spec[:group] + v = opts[spec[:group]] + v = v && v[spec[:name]] + else + v = opts[spec[:name]] + end + unless v.nil? + if type = spec[:type] + v = send("stringify_#{type}", v) end canonical_key = URI_OPTION_CANONICAL_NAMES[uri_key] unless canonical_key @@ -310,6 +352,15 @@ def revert_bool(value) value end + # Stringifies a boolean type. + # + # @param [ true | false | nil ] value The boolean. + # + # @return [ String | nil ] The string. + def stringify_bool(value) + revert_bool(value)&.to_s + end + # Converts the value into a boolean and returns it wrapped in an array. # # @param [ String ] name Name of the URI option being processed. @@ -323,13 +374,27 @@ def convert_repeated_bool(name, value) # Reverts a repeated boolean type. # - # @param [ Array | nil ] value The repeated boolean to revert. + # @param [ Array | true | false | nil ] value The repeated boolean to revert. # - # @return [ Array | nil ] The passed value. + # @return [ Array | true | false | nil ] The passed value. def revert_repeated_bool(value) value end + # Stringifies a repeated boolean type. + # + # @param [ Array | nil ] value The repeated boolean. + # + # @return [ Array | nil ] The string. + def stringify_repeated_bool(value) + rep = revert_repeated_bool(value) + if rep&.is_a?(Array) + rep.join(",") + else + rep + end + end + # Parses a boolean value and returns its inverse. # # @param [ String ] name Name of the URI option being processed. @@ -356,6 +421,15 @@ def revert_inverse_bool(value) value.nil? ? nil : !value end + # Inverts and stringifies a boolean. + # + # @param [ true | false | nil ] value The boolean. + # + # @return [ String | nil ] The string. + def stringify_inverse_bool(value) + revert_inverse_bool(value)&.to_s + end + # Converts +value+ into an integer. Only converts positive integers. # # If the value is not a valid integer, warns and returns nil. @@ -382,6 +456,15 @@ def revert_integer(value) value end + # Stringifies an integer. + # + # @param [ Integer | nil ] value The integer. + # + # @return [ String | nil ] The string. + def stringify_integer(value) + revert_integer(value)&.to_s + end + # Ruby's convention is to provide timeouts in seconds, not milliseconds and # to use fractions where more precision is necessary. The connection string # options are always in MS so we provide an easy conversion type. @@ -424,6 +507,15 @@ def revert_ms(value) (value * 1000).round end + # Stringifies an ms. + # + # @param [ Float ] value The float. + # + # @return [ String ] The string. + def stringify_ms(value) + revert_ms(value).to_s + end + # Converts +value+ into a symbol. # # @param [ String ] name Name of the URI option being processed. @@ -442,6 +534,7 @@ def convert_symbol(name, value) def revert_symbol(value) value.to_s end + alias :stringify_symbol :revert_symbol # Extract values from the string and put them into an array. # @@ -462,6 +555,15 @@ def revert_array(value) value end + # Stringifies an array. + # + # @param [ Array ] value An array of strings. + # + # @return [ String ] The array joined by commas. + def stringify_array(value) + value.join(',') + end + # Authentication mechanism transformation. # # @param [ String ] name Name of the URI option being processed. @@ -480,6 +582,8 @@ def convert_auth_mech(name, value) # @param [ Symbol ] value The auth mechanism. # # @return [ String ] The auth mechanism as a string. + # + # @raise [ ArgumentError ] if its an invalid auth mechanism. def revert_auth_mech(value) found = AUTH_MECH_MAP.detect do |k, v| v == value @@ -491,6 +595,15 @@ def revert_auth_mech(value) end end + # Stringifies auth mechanism. + # + # @param [ Symbol ] value The auth mechanism. + # + # @return [ String | nil ] The auth mechanism as a string. + def stringify_auth_mech(value) + revert_auth_mech(value) rescue nil + end + # Auth mechanism properties extractor. # # @param [ String ] name Name of the URI option being processed. @@ -518,6 +631,16 @@ def revert_auth_mech_props(value) value end + # Stringifies auth mechanism properties. + # + # @param [ Hash | nil ] value The auth mech properties. + # + # @return [ String | nil ] The string. + def stringify_auth_mech_props(value) + return if value.nil? + value.map { |k, v| "#{k}:#{v}" }.join(',') + end + # Parses the max staleness value, which must be either "0" or an integer # greater or equal to 90. # @@ -559,6 +682,15 @@ def revert_max_staleness(value) value end + # Stringifies max staleness. + # + # @param [ Integer | nil ] value The max staleness. + # + # @return [ String | nil ] The string. + def stringify_max_staleness(value) + revert_max_staleness(value)&.to_s + end + # Read preference mode transformation. # # @param [ String ] name Name of the URI option being processed. @@ -577,6 +709,7 @@ def convert_read_mode(name, value) def revert_read_mode(value) value.to_s.gsub(/_(\w)/) { $1.upcase } end + alias :stringify_read_mode :revert_read_mode # Read preference tags transformation. # @@ -602,6 +735,15 @@ def revert_read_tags(value) value end + # Stringifies read tags. + # + # @param [ Array | nil ] value The read tags. + # + # @return [ String | nil ] The joined string of read tags. + def stringify_read_tags(value) + value&.map { |ar| ar.map { |k, v| "#{k}:#{v}" }.join(',') } + end + # Read preference tag set extractor. # # @param [ String ] name Name of the URI option being processed. @@ -637,7 +779,7 @@ def convert_w(name, value) # # @param [ Integer | Symbol | String ] value The write concern. # - # @return [ String ] The write concern as a string. + # @return [ Integer | String ] The write concern as a string. def revert_w(value) case value when Symbol @@ -647,6 +789,15 @@ def revert_w(value) end end + # Stringifies write concern. + # + # @param [ Integer | Symbol | String ] value The write concern. + # + # @return [ String ] The write concern as a string. + def stringify_w(value) + revert_w(value)&.to_s + end + # Parses the zlib compression level. # # @param [ String ] name Name of the URI option being processed. @@ -678,6 +829,15 @@ def revert_zlib_compression_level(value) value end + # Stringifies zlib compression level + # + # @param [ Integer | nil ] value The write concern. + # + # @return [ String | nil ] The string. + def stringify_zlib_compression_level(value) + revert_zlib_compression_level(value)&.to_s + end + # Extract values from the string and put them into a nested hash. # # @param [ String ] name Name of the URI option being processed. diff --git a/spec/mongo/uri/options_mapper_spec.rb b/spec/mongo/uri/options_mapper_spec.rb index 67e421cbfb..ee63e616cc 100644 --- a/spec/mongo/uri/options_mapper_spec.rb +++ b/spec/mongo/uri/options_mapper_spec.rb @@ -100,6 +100,35 @@ end end + describe "#stringify_bool" do + + let(:method) { :stringify_bool } + + context "when passing a boolean" do + let(:value) { true } + + it "returns a string" do + expect(reverted).to eq("true") + end + end + + context "when passing false" do + let(:value) { false } + + it "returns a string" do + expect(reverted).to eq("false") + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + describe "#convert_repeated_bool" do let(:method) { :convert_repeated_bool } @@ -131,6 +160,35 @@ end end + describe "#stringify_repeated_bool" do + + let(:method) { :stringify_repeated_bool } + + context "when passing a boolean list" do + let(:value) { [ true ] } + + it "returns a string" do + expect(reverted).to eq("true") + end + end + + context "when passing a multi boolean list" do + let(:value) { [ true, false ] } + + it "returns a string" do + expect(reverted).to eq("true,false") + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + describe "#convert_inverse_bool" do let(:method) { :convert_inverse_bool } @@ -229,6 +287,35 @@ end end + describe "#stringify_inverse_bool" do + + let(:method) { :stringify_inverse_bool } + + context "when passing true" do + let(:value) { true } + + it "returns false string" do + expect(reverted).to eq("false") + end + end + + context "when passing false" do + let(:value) { false } + + it "returns true string" do + expect(reverted).to eq("true") + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + describe "#convert_integer" do let(:method) { :convert_integer } @@ -279,6 +366,27 @@ end end + describe "#stringify_integer" do + + let(:method) { :stringify_integer } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns the passed value as a string" do + expect(reverted).to eq("1") + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + describe "#convert_ms" do let(:method) { :convert_ms } @@ -361,6 +469,19 @@ end end + describe "#stringify_ms" do + + let(:method) { :stringify_ms } + + context "when passing a float" do + let(:value) { 1.000005 } + + it "returns a string" do + expect(reverted).to eq("1000") + end + end + end + describe "#convert_symbol" do let(:method) { :convert_symbol } @@ -395,6 +516,19 @@ end end + describe "#stringify_symbol" do + + let(:method) { :stringify_symbol } + + context "when passing a symbol" do + let(:value) { :hello } + + it "returns it as a string" do + expect(reverted).to eq("hello") + end + end + end + describe "#convert_array" do let(:method) { :convert_array } @@ -437,6 +571,27 @@ end end + describe "#stringify_array" do + + let(:method) { :stringify_array } + + context "when passing one value" do + let(:value) { [ "hello" ] } + + it "returns a string" do + expect(reverted).to eq("hello") + end + end + + context "when passing multiple value" do + let(:value) { [ "1", "2", "3" ] } + + it "returns the joined string" do + expect(reverted).to eq("1,2,3") + end + end + end + describe "#convert_auth_mech" do let(:method) { :convert_auth_mech } @@ -518,7 +673,7 @@ context "when passing GSSAPI" do let(:value) { :gssapi } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("GSSAPI") end end @@ -526,7 +681,7 @@ context "when passing MONGODB-AWS" do let(:value) { :aws } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("MONGODB-AWS") end end @@ -534,7 +689,7 @@ context "when passing MONGODB-CR" do let(:value) { :mongodb_cr } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("MONGODB-CR") end end @@ -542,7 +697,7 @@ context "when passing MONGODB-X509" do let(:value) { :mongodb_x509 } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("MONGODB-X509") end end @@ -550,7 +705,7 @@ context "when passing PLAIN" do let(:value) { :plain } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("PLAIN") end end @@ -558,7 +713,7 @@ context "when passing SCRAM-SHA-1" do let(:value) { :scram } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("SCRAM-SHA-1") end end @@ -566,7 +721,7 @@ context "when passing SCRAM-SHA-256" do let(:value) { :scram256 } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("SCRAM-SHA-256") end end @@ -592,6 +747,83 @@ end end + describe "#stringify_auth_mech" do + + let(:method) { :stringify_auth_mech } + + context "when passing GSSAPI" do + let(:value) { :gssapi } + + it "returns it as a string" do + expect(reverted).to eq("GSSAPI") + end + end + + context "when passing MONGODB-AWS" do + let(:value) { :aws } + + it "returns it as a string" do + expect(reverted).to eq("MONGODB-AWS") + end + end + + context "when passing MONGODB-CR" do + let(:value) { :mongodb_cr } + + it "returns it as a string" do + expect(reverted).to eq("MONGODB-CR") + end + end + + context "when passing MONGODB-X509" do + let(:value) { :mongodb_x509 } + + it "returns it as a string" do + expect(reverted).to eq("MONGODB-X509") + end + end + + context "when passing PLAIN" do + let(:value) { :plain } + + it "returns it as a string" do + expect(reverted).to eq("PLAIN") + end + end + + context "when passing SCRAM-SHA-1" do + let(:value) { :scram } + + it "returns it as a string" do + expect(reverted).to eq("SCRAM-SHA-1") + end + end + + context "when passing SCRAM-SHA-256" do + let(:value) { :scram256 } + + it "returns it as a string" do + expect(reverted).to eq("SCRAM-SHA-256") + end + end + + context "when passing a bogus value" do + let(:value) { "hello" } + + it "returns nil" do + expect(reverted).to be nil + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + describe "#convert_auth_mech_props" do let(:method) { :convert_auth_mech_props } @@ -706,6 +938,35 @@ end end + describe "#stringify_auth_mech_props" do + + let(:method) { :stringify_auth_mech_props } + + context "when including one item" do + let(:value) { { key: "value" } } + + it "returns a string" do + expect(reverted).to eq("key:value") + end + end + + context "when including multiple items" do + let(:value) { { k1: "v1", k2: "v2" } } + + it "returns a string" do + expect(reverted).to eq("k1:v1,k2:v2") + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + describe "#convert_max_staleness" do let(:method) { :convert_max_staleness } @@ -830,6 +1091,27 @@ end end + describe "#stringify_max_staleness" do + + let(:method) { :stringify_max_staleness } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns the integer string" do + expect(reverted).to eq('1') + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + describe "#convert_read_mode" do let(:method) { :convert_read_mode } @@ -898,7 +1180,7 @@ context "when passing primary" do let(:value) { :primary } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("primary") end end @@ -906,7 +1188,7 @@ context "when passing primarypreferred" do let(:value) { :primary_preferred } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("primaryPreferred") end end @@ -914,7 +1196,7 @@ context "when passing secondary" do let(:value) { :secondary } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("secondary") end end @@ -922,7 +1204,7 @@ context "when passing secondarypreferred" do let(:value) { :secondary_preferred } - it "returns it as a symbol" do + it "returns it as a string" do expect(reverted).to eq("secondaryPreferred") end end @@ -930,7 +1212,60 @@ context "when passing nearest" do let(:value) { :nearest } - it "returns it as a symbol" do + it "returns it as a string" do + expect(reverted).to eq("nearest") + end + end + + context "when passing a bogus string" do + let(:value) { "hello" } + + it "returns the string" do + expect(reverted).to eq("hello") + end + end + end + + describe "#stringify_read_mode" do + + let(:method) { :stringify_read_mode } + + context "when passing primary" do + let(:value) { :primary } + + it "returns it as a string" do + expect(reverted).to eq("primary") + end + end + + context "when passing primarypreferred" do + let(:value) { :primary_preferred } + + it "returns it as a string" do + expect(reverted).to eq("primaryPreferred") + end + end + + context "when passing secondary" do + let(:value) { :secondary } + + it "returns it as a string" do + expect(reverted).to eq("secondary") + end + end + + context "when passing secondarypreferred" do + let(:value) { :secondary_preferred } + + it "returns it as a string" do + expect(reverted).to eq("secondaryPreferred") + end + end + + context "when passing nearest" do + let(:value) { :nearest } + + it "returns it as a string" do expect(reverted).to eq("nearest") end end @@ -1001,7 +1336,7 @@ context "when including one item" do let(:value) { [ { key: "value" } ] } - it "returns a one element hash" do + it "returns the passed value" do expect(reverted).to eq(value) end end @@ -1009,7 +1344,7 @@ context "when including multiple items" do let(:value) { [ { k1: "v1", k2: "v2" } ] } - it "returns a multiple element hash" do + it "returns the passed value" do expect(reverted).to eq(value) end end @@ -1017,7 +1352,7 @@ context "when including multiple hashes" do let(:value) { [ { k1: "v1", k2: "v2" }, { k3: "v3", k4: "v4" } ] } - it "returns a multiple element hash" do + it "returns the passed value" do expect(reverted).to eq(value) end end @@ -1031,6 +1366,43 @@ end end + describe "#stringify_read_tags" do + + let(:method) { :stringify_read_tags } + + context "when including one item" do + let(:value) { [ { key: "value" } ] } + + it "returns a one element string list" do + expect(reverted).to eq([ "key:value" ]) + end + end + + context "when including multiple items" do + let(:value) { [ { k1: "v1", k2: "v2" } ] } + + it "returns a one element string list" do + expect(reverted).to eq([ "k1:v1,k2:v2" ]) + end + end + + context "when including multiple hashes" do + let(:value) { [ { k1: "v1", k2: "v2" }, { k3: "v3", k4: "v4" } ] } + + it "returns a multiple element string list" do + expect(reverted).to eq([ "k1:v1,k2:v2", "k3:v3,k4:v4" ]) + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end + describe "#convert_w" do let(:method) { :convert_w } @@ -1089,6 +1461,35 @@ end end + describe "#stringify_w" do + + let(:method) { :stringify_w } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns a string" do + expect(reverted).to eq('1') + end + end + + context "when passing a symbol" do + let(:value) { :majority } + + it "returns a string" do + expect(reverted).to eq("majority") + end + end + + context "when passing a string" do + let(:value) { "hello" } + + it "returns a string" do + expect(reverted).to eq(value) + end + end + end + describe "#convert_zlib_compression_level" do let(:method) { :convert_zlib_compression_level } @@ -1180,4 +1581,25 @@ end end end + + describe "#stringify_zlib_compression_level" do + + let(:method) { :stringify_zlib_compression_level } + + context "when passing an integer" do + let(:value) { 1 } + + it "returns a string" do + expect(reverted).to eq('1') + end + end + + context "when passing nil" do + let(:value) { nil } + + it "returns nil" do + expect(reverted).to be nil + end + end + end end diff --git a/spec/mongo/uri/srv_protocol_spec.rb b/spec/mongo/uri/srv_protocol_spec.rb index c907435cc3..8d82e7797a 100644 --- a/spec/mongo/uri/srv_protocol_spec.rb +++ b/spec/mongo/uri/srv_protocol_spec.rb @@ -15,6 +15,12 @@ new_local_client_nmio(string) end + shared_examples "roundtrips string" do + it "returns the correct string for the uri" do + expect(uri.to_s).to eq(URI::DEFAULT_PARSER.unescape(string)) + end + end + describe 'invalid uris' do context 'when there is more than one hostname' do @@ -273,6 +279,8 @@ it 'returns an array with the parsed server' do expect(uri.servers).to eq(['localhost.test.build.10gen.cc:27017']) end + + include_examples "roundtrips string" end context 'multiple servers' do @@ -283,6 +291,8 @@ expect(uri.servers.length).to eq 2 uri.servers.should =~ hosts end + + include_examples "roundtrips string" end context 'with srvMaxHosts' do @@ -291,6 +301,8 @@ expect(uri.servers.length).to eq 1 expect(hosts.include?(uri.servers.first)).to be true end + + include_examples "roundtrips string" end context 'with srvMaxHosts > total hosts' do @@ -299,6 +311,8 @@ expect(uri.servers.length).to eq 2 uri.servers.should =~ hosts end + + include_examples "roundtrips string" end context 'with srvMaxHosts == total hosts' do @@ -307,6 +321,8 @@ expect(uri.servers.length).to eq 2 uri.servers.should =~ hosts end + + include_examples "roundtrips string" end context 'with srvMaxHosts=0' do @@ -315,6 +331,8 @@ expect(uri.servers.length).to eq 2 uri.servers.should =~ hosts end + + include_examples "roundtrips string" end context 'when setting the srvServiceName' do @@ -324,6 +342,8 @@ it 'returns an array with the parsed server' do uri.servers.should =~ hosts end + + include_examples "roundtrips string" end end end @@ -356,6 +376,8 @@ it 'sets ssl to true' do expect(options[:ssl]).to eq(true) end + + include_examples "roundtrips string" end describe '#credentials' do @@ -369,6 +391,10 @@ it 'returns the username' do expect(uri.credentials[:user]).to eq(user) end + + it "drops the colon in to_s" do + expect(uri.to_s).to eq("mongodb+srv://tyler@test5.test.build.10gen.cc") + end end context 'username and password provided' do @@ -382,6 +408,8 @@ it 'returns the password' do expect(uri.credentials[:password]).to eq(password) end + + include_examples "roundtrips string" end end @@ -394,6 +422,8 @@ it 'returns the database name' do expect(uri.database).to eq(db) end + + include_examples "roundtrips string" end end @@ -407,6 +437,8 @@ it 'returns an empty hash' do expect(uri.uri_options).to be_empty end + + include_examples "roundtrips string" end context 'write concern options provided' do @@ -422,6 +454,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:write_concern]).to eq(concern) end + + include_examples "roundtrips string" end context 'w=majority' do @@ -435,6 +469,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:write_concern]).to eq(concern) end + + include_examples "roundtrips string" end context 'journal' do @@ -448,6 +484,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:write_concern]).to eq(concern) end + + include_examples "roundtrips string" end context 'fsync' do @@ -461,6 +499,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:write_concern]).to eq(concern) end + + include_examples "roundtrips string" end context 'wtimeoutMS' do @@ -475,6 +515,10 @@ it 'sets the options on a client created with the uri' do expect(client.options[:write_concern]).to eq(concern) end + + it "roundtrips the string with camelCase" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc/?w=2&wTimeoutMS=1234") + end end end @@ -492,6 +536,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'primaryPreferred' do @@ -505,6 +551,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'secondary' do @@ -518,6 +566,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'secondaryPreferred' do @@ -531,6 +581,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'nearest' do @@ -544,6 +596,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end end @@ -565,6 +619,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'multiple read preference tag sets' do @@ -583,6 +639,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end end @@ -604,6 +662,10 @@ expect(client.options[:read]).to eq(read) end + it "rountrips the string with lowercase values" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc/?readPreference=secondary&maxStalenessSeconds=120") + end + context 'when the read preference and max staleness combination is invalid' do context 'when max staleness is combined with read preference mode primary' do @@ -628,6 +690,10 @@ it 'does not raise an exception and is omitted' do expect(client.read_preference).to eq(BSON::Document.new(mode: :secondary)) end + + it "drops maxStalenessSeconds in to_s" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc/?readPreference=secondary") + end end end end @@ -643,6 +709,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:replica_set]).to eq(rs_name) end + + include_examples "roundtrips string" end context 'auth mechanism provided' do @@ -668,6 +736,8 @@ client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end + + include_examples "roundtrips string" end context 'mongodb-cr' do @@ -686,6 +756,8 @@ client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end + + include_examples "roundtrips string" end context 'gssapi' do @@ -707,6 +779,10 @@ client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end + + it "roundtrips the string" do + expect(uri.to_s).to eq("mongodb+srv://tyler:s3kr4t@test5.test.build.10gen.cc/?authSource=$external&authMechanism=GSSAPI") + end end context 'scram-sha-1' do @@ -725,6 +801,8 @@ client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end + + include_examples "roundtrips string" end context 'mongodb-x509' do @@ -746,6 +824,10 @@ expect(client.options[:auth_mech]).to eq(expected) end + it "roundtrips the string" do + expect(uri.to_s).to eq("mongodb+srv://tyler@test5.test.build.10gen.cc/?authSource=$external&authMechanism=MONGODB-X509") + end + context 'when a username is not provided' do let(:string) { "#{scheme}#{servers}/?#{options}" } it 'recognizes the mechanism with no username' do @@ -753,6 +835,10 @@ expect(client.options[:auth_mech]).to eq(expected) expect(client.options[:user]).to be_nil end + + it "roundtrips the string" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc/?authSource=$external&authMechanism=MONGODB-X509") + end end end end @@ -770,6 +856,8 @@ it 'sets the options on a client created with the uri' do expect(client.options[:auth_source]).to eq(source) end + + include_examples "roundtrips string" end end @@ -807,6 +895,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'canonicalize_host_name' do @@ -829,6 +918,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'service_realm' do @@ -851,6 +941,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'multiple properties' do @@ -881,6 +972,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end end @@ -890,6 +982,8 @@ it 'sets the the connect timeout' do expect(uri.uri_options[:connect_timeout]).to eq(4.567) end + + include_examples "roundtrips string" end context 'socketTimeoutMS' do @@ -898,6 +992,8 @@ it 'sets the socket timeout' do expect(uri.uri_options[:socket_timeout]).to eq(8.910) end + + include_examples "roundtrips string" end context 'when providing serverSelectionTimeoutMS' do @@ -907,6 +1003,8 @@ it 'sets the the connect timeout' do expect(uri.uri_options[:server_selection_timeout]).to eq(3.561) end + + include_examples "roundtrips string" end context 'when providing localThresholdMS' do @@ -916,6 +1014,8 @@ it 'sets the the connect timeout' do expect(uri.uri_options[:local_threshold]).to eq(3.561) end + + include_examples "roundtrips string" end context 'when providing maxPoolSize' do @@ -926,6 +1026,8 @@ it 'sets the max pool size option' do expect(uri.uri_options[:max_pool_size]).to eq(max_pool_size) end + + include_examples "roundtrips string" end context 'when providing minPoolSize' do @@ -936,6 +1038,8 @@ it 'sets the min pool size option' do expect(uri.uri_options[:min_pool_size]).to eq(min_pool_size) end + + include_examples "roundtrips string" end context 'when providing waitQueueTimeoutMS' do @@ -946,6 +1050,8 @@ it 'sets the wait queue timeout option' do expect(uri.uri_options[:wait_queue_timeout]).to eq(0.5) end + + include_examples "roundtrips string" end context 'when providing srvMaxHosts' do @@ -955,6 +1061,8 @@ it 'sets the srv max hosts option' do expect(uri.uri_options[:srv_max_hosts]).to eq(srv_max_hosts) end + + include_examples "roundtrips string" end context 'when providing srvMaxHosts as 0' do @@ -964,6 +1072,8 @@ it 'doesn\'t set the srv max hosts option' do expect(uri.uri_options[:srv_max_hosts]).to eq(srv_max_hosts) end + + include_examples "roundtrips string" end context 'when providing invalid integer to srvMaxHosts' do @@ -973,6 +1083,10 @@ it 'does not set the srv max hosts option' do expect(uri.uri_options).to_not have_key(:srv_max_hosts) end + + it "drops srvMaxHosts in to_s" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc") + end end context 'when providing invalid type to srvMaxHosts' do @@ -982,6 +1096,10 @@ it 'does not set the srv max hosts option' do expect(uri.uri_options).to_not have_key(:srv_max_hosts) end + + it "drops srvMaxHosts in to_s" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc") + end end context 'when providing srvServiceName' do @@ -991,6 +1109,8 @@ it 'sets the srv service name option' do expect(uri.uri_options[:srv_service_name]).to eq(srv_service_name) end + + include_examples "roundtrips string" end context 'ssl' do @@ -1002,6 +1122,10 @@ it 'sets the ssl option to true' do expect(uri.uri_options[:ssl]).to be true end + + it "uses tls in to_s" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc/?tls=true") + end end context 'false' do @@ -1010,6 +1134,10 @@ it 'sets the ssl option to false' do expect(uri.uri_options[:ssl]).to be false end + + it "uses tls in to_s" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc/?tls=false") + end end end @@ -1019,6 +1147,10 @@ it 'do not overshadow top level options' do expect(uri.uri_options).not_to be_empty end + + it "uses tls in to_s" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc/?w=1&tls=true") + end end context 'when an invalid option is provided' do @@ -1033,6 +1165,10 @@ expect(uri_options).to be_empty end + it "drops the invalid option in to_s" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc") + end + context 'when an invalid option is combined with valid options' do let(:options) { 'invalidOption=10&waitQueueTimeoutMS=500&ssl=true' } @@ -1045,15 +1181,21 @@ expect(uri_options[:wait_queue_timeout]).to eq(0.5) expect(uri_options[:ssl]).to be true end + + it "drops the invalid option in to_s" do + expect(uri.to_s).to eq("mongodb+srv://test5.test.build.10gen.cc/?waitQueueTimeoutMS=500&tls=true") + end end end context 'when an app name option is provided' do - let(:options) { "appname=srv_test" } + let(:options) { "appName=srv_test" } it 'sets the app name on the client' do expect(client.options[:app_name]).to eq('srv_test') end + + include_examples "roundtrips string" end context 'when a supported compressors option is provided' do @@ -1062,6 +1204,8 @@ it 'sets the compressors as an array on the client' do expect(client.options[:compressors]).to eq(['zlib']) end + + include_examples "roundtrips string" end context 'when a non-supported compressors option is provided' do @@ -1071,6 +1215,8 @@ expect(Mongo::Logger.logger).to receive(:warn) expect(client.options[:compressors]).to be_nil end + + include_examples "roundtrips string" end context 'when a zlibCompressionLevel option is provided' do @@ -1079,6 +1225,8 @@ it 'sets the zlib compression level on the client' do expect(client.options[:zlib_compression_level]).to eq(6) end + + include_examples "roundtrips string" end end end diff --git a/spec/mongo/uri_spec.rb b/spec/mongo/uri_spec.rb index 36b79d10ee..da42f4e1f2 100644 --- a/spec/mongo/uri_spec.rb +++ b/spec/mongo/uri_spec.rb @@ -5,6 +5,12 @@ describe Mongo::URI do + shared_examples "roundtrips string" do + it "returns the correct string for the uri" do + expect(uri.to_s).to eq(URI::DEFAULT_PARSER.unescape(string)) + end + end + describe '.get' do let(:uri) { described_class.get(string) } @@ -64,6 +70,8 @@ it 'returns a Mongo::URI::SRVProtocol object' do expect(uri).to be_a(Mongo::URI::SRVProtocol) end + + include_examples "roundtrips string" end context 'when the scheme is invalid' do @@ -363,6 +371,15 @@ end end + describe "#to_s" do + context "string is a uri" do + let(:string) { 'mongodb://localhost:27017' } + it "returns the original string" do + expect(uri.to_s).to eq(string) + end + end + end + describe '#servers' do let(:string) { "#{scheme}#{servers}" } @@ -372,6 +389,8 @@ it 'returns an array with the parsed server' do expect(uri.servers).to eq([servers]) end + + include_examples "roundtrips string" end context 'single server with port' do @@ -380,6 +399,8 @@ it 'returns an array with the parsed server' do expect(uri.servers).to eq([servers]) end + + include_examples "roundtrips string" end context 'numerical ipv4 server' do @@ -388,6 +409,8 @@ it 'returns an array with the parsed server' do expect(uri.servers).to eq([servers]) end + + include_examples "roundtrips string" end context 'numerical ipv6 server' do @@ -396,6 +419,8 @@ it 'returns an array with the parsed server' do expect(uri.servers).to eq([servers]) end + + include_examples "roundtrips string" end context 'unix socket server' do @@ -404,6 +429,8 @@ it 'returns an array with the parsed server' do expect(uri.servers).to eq([URI::DEFAULT_PARSER.unescape(servers)]) end + + include_examples "roundtrips string" end context 'multiple servers' do @@ -412,6 +439,8 @@ it 'returns an array with the parsed servers' do expect(uri.servers).to eq(servers.split(',')) end + + include_examples "roundtrips string" end context 'multiple servers with ports' do @@ -420,6 +449,8 @@ it 'returns an array with the parsed servers' do expect(uri.servers).to eq(servers.split(',')) end + + include_examples "roundtrips string" end end @@ -447,6 +478,8 @@ it 'includes the password in the options' do expect(options[:password]).to eq(password) end + + include_examples "roundtrips string" end describe '#credentials' do @@ -460,6 +493,10 @@ it 'returns the username' do expect(uri.credentials[:user]).to eq(user) end + + it "roundtrips string without the colon" do + expect(uri.to_s).to eq("mongodb://tyler@localhost") + end end context 'username and password provided' do @@ -473,6 +510,8 @@ it 'returns the password' do expect(uri.credentials[:password]).to eq(password) end + + include_examples "roundtrips string" end end @@ -485,6 +524,8 @@ it 'returns the database name' do expect(uri.database).to eq(db) end + + include_examples "roundtrips string" end end @@ -498,6 +539,8 @@ it 'returns an empty hash' do expect(uri.uri_options).to be_empty end + + include_examples "roundtrips string" end context 'write concern options provided' do @@ -514,6 +557,8 @@ client = new_local_client_nmio(string) expect(client.options[:write_concern]).to eq(concern) end + + include_examples "roundtrips string" end context 'w=majority' do @@ -528,6 +573,8 @@ client = new_local_client_nmio(string) expect(client.options[:write_concern]).to eq(concern) end + + include_examples "roundtrips string" end context 'journal' do @@ -542,6 +589,8 @@ client = new_local_client_nmio(string) expect(client.options[:write_concern]).to eq(concern) end + + include_examples "roundtrips string" end context 'fsync' do @@ -556,6 +605,8 @@ client = new_local_client_nmio(string) expect(client.options[:write_concern]).to eq(concern) end + + include_examples "roundtrips string" end context 'wtimeoutMS' do @@ -571,6 +622,10 @@ client = new_local_client_nmio(string) expect(client.options[:write_concern]).to eq(concern) end + + it "roundtrips the string with camelCase" do + expect(uri.to_s).to eq("mongodb://localhost/?w=2&wTimeoutMS=1234") + end end end @@ -589,6 +644,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'primaryPreferred' do @@ -603,6 +660,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'secondary' do @@ -617,6 +676,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'secondaryPreferred' do @@ -631,6 +692,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'nearest' do @@ -645,6 +708,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end end @@ -667,6 +732,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end context 'multiple read preference tag sets' do @@ -686,6 +753,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end end @@ -734,6 +803,10 @@ client = new_local_client_nmio(string) expect(client.read_preference).to eq(BSON::Document.new(mode: :secondary)) end + + it "returns the string without the dropped option" do + expect(uri.to_s).to eq("mongodb://localhost/?readPreference=secondary") + end end end end @@ -750,6 +823,8 @@ client = new_local_client_nmio(string) expect(client.options[:replica_set]).to eq(rs_name) end + + include_examples "roundtrips string" end context 'auth mechanism provided' do @@ -777,6 +852,8 @@ expect(client.options[:auth_mech]).to eq(expected) end + include_examples "roundtrips string" + context 'when mechanism_properties are provided' do let(:options) { "authMechanism=#{mechanism}&authMechanismProperties=CANONICALIZE_HOST_NAME:true" } @@ -806,6 +883,8 @@ expect(client.options[:auth_mech]).to eq(expected) end + include_examples "roundtrips string" + context 'when mechanism_properties are provided' do let(:options) { "authMechanism=#{mechanism}&authMechanismProperties=CANONICALIZE_HOST_NAME:true" } @@ -837,6 +916,8 @@ expect(client.options[:auth_mech]).to eq(expected) end + include_examples "roundtrips string" + context 'when auth source is invalid' do let(:options) { "authMechanism=#{mechanism}&authSource=foo" } @@ -854,12 +935,18 @@ expect(client.options[:auth_mech_properties]).to eq({ 'canonicalize_host_name' => true, 'service_name' => 'other' }) end + include_examples "roundtrips string" + context 'when a mapping value is missing' do let(:options) { "authMechanism=#{mechanism}&authMechanismProperties=SERVICE_NAME:,CANONICALIZE_HOST_NAME:" } it 'sets the options to defaults' do expect(client.options[:auth_mech_properties]).to eq({ 'service_name' => 'mongodb' }) end + + it "roundtrips the string" do + expect(uri.to_s).to eq("mongodb://tyler:s3kr4t@localhost/?authMechanism=GSSAPI") + end end context 'when a mapping value is missing but another is present' do @@ -868,6 +955,10 @@ it 'only sets the present value' do expect(client.options[:auth_mech_properties]).to eq({ 'service_name' => 'foo' }) end + + it "roundtrips the string" do + expect(uri.to_s).to eq("mongodb://tyler:s3kr4t@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:foo") + end end end end @@ -890,6 +981,8 @@ expect(client.options[:auth_mech]).to eq(expected) end + include_examples "roundtrips string" + context 'when mechanism_properties are provided' do let(:options) { "authMechanism=#{mechanism}&authMechanismProperties=CANONICALIZE_HOST_NAME:true" } @@ -920,6 +1013,8 @@ expect(client.options[:auth_mech]).to eq(expected) end + include_examples "roundtrips string" + context 'when auth source is invalid' do let(:options) { "authMechanism=#{mechanism}&authSource=foo" } @@ -938,6 +1033,8 @@ expect(client.options[:auth_mech]).to eq(expected) expect(client.options[:user]).to be_nil end + + include_examples "roundtrips string" end context 'when a password is provided' do @@ -967,7 +1064,7 @@ let(:string) { "#{scheme}#{credentials}@#{servers}/" } context 'with no credentials' do - let(:string) { "#{scheme}#{servers}/" } + let(:string) { "#{scheme}#{servers}" } it 'sets user and password as nil' do expect(uri.credentials[:user]).to be_nil @@ -979,6 +1076,8 @@ expect(client.options[:user]).to be_nil expect(client.options[:password]).to be_nil end + + include_examples "roundtrips string" end context 'with empty credentials' do @@ -1011,6 +1110,8 @@ client = new_local_client_nmio(string) expect(client.options[:auth_source]).to eq(source) end + + include_examples "roundtrips string" end end @@ -1047,6 +1148,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'canonicalize_host_name' do @@ -1069,6 +1171,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'service_realm' do @@ -1091,6 +1194,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'multiple properties' do @@ -1121,6 +1225,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end end @@ -1130,6 +1235,8 @@ it 'sets the the connect timeout' do expect(uri.uri_options[:connect_timeout]).to eq(4.567) end + + include_examples "roundtrips string" end context 'socketTimeoutMS' do @@ -1138,6 +1245,8 @@ it 'sets the socket timeout' do expect(uri.uri_options[:socket_timeout]).to eq(8.910) end + + include_examples "roundtrips string" end context 'when providing serverSelectionTimeoutMS' do @@ -1147,6 +1256,8 @@ it 'sets the the connect timeout' do expect(uri.uri_options[:server_selection_timeout]).to eq(3.561) end + + include_examples "roundtrips string" end context 'when providing localThresholdMS' do @@ -1156,6 +1267,8 @@ it 'sets the the connect timeout' do expect(uri.uri_options[:local_threshold]).to eq(3.561) end + + include_examples "roundtrips string" end context 'when providing maxPoolSize' do @@ -1166,6 +1279,8 @@ it 'sets the max pool size option' do expect(uri.uri_options[:max_pool_size]).to eq(max_pool_size) end + + include_examples "roundtrips string" end context 'when providing minPoolSize' do @@ -1176,6 +1291,8 @@ it 'sets the min pool size option' do expect(uri.uri_options[:min_pool_size]).to eq(min_pool_size) end + + include_examples "roundtrips string" end context 'when providing srvMaxHosts with non-SRV URI' do @@ -1211,6 +1328,8 @@ it 'sets the wait queue timeout option' do expect(uri.uri_options[:wait_queue_timeout]).to eq(0.5) end + + include_examples "roundtrips string" end context 'ssl' do @@ -1222,6 +1341,10 @@ it 'sets the ssl option to true' do expect(uri.uri_options[:ssl]).to be true end + + it "returns the ssl as tls from to_s" do + expect(uri.to_s).to eq("mongodb://localhost/?tls=true") + end end context 'false' do @@ -1230,6 +1353,10 @@ it 'sets the ssl option to false' do expect(uri.uri_options[:ssl]).to be false end + + it "returns the ssl as tls from to_s" do + expect(uri.to_s).to eq("mongodb://localhost/?tls=false") + end end end @@ -1239,6 +1366,10 @@ it 'do not overshadow top level options' do expect(uri.uri_options).not_to be_empty end + + it "returns the ssl as tls from to_s" do + expect(uri.to_s).to eq("mongodb://localhost/?w=1&tls=true") + end end context 'when an invalid option is provided' do @@ -1275,6 +1406,10 @@ client = new_local_client_nmio(string) expect(client.options[:app_name]).to eq('uri_test') end + + it "roundtrips the string with camelCase" do + expect(uri.to_s).to eq("mongodb://localhost/?appName=uri_test") + end end context 'when a supported compressors option is provided' do @@ -1284,6 +1419,8 @@ client = new_local_client_nmio(string) expect(client.options[:compressors]).to eq(['zlib']) end + + include_examples "roundtrips string" end context 'when a non-supported compressors option is provided' do @@ -1297,6 +1434,8 @@ expect(Mongo::Logger.logger).to receive(:warn) expect(client.options[:compressors]).to be_nil end + + include_examples "roundtrips string" end context 'when a zlibCompressionLevel option is provided' do @@ -1306,6 +1445,8 @@ client = new_local_client_nmio(string) expect(client.options[:zlib_compression_level]).to eq(6) end + + include_examples "roundtrips string" end end end From 35cd0fa87768e5534823734fba91a17d18aeea4d Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 20 Oct 2022 12:12:52 -0400 Subject: [PATCH 065/198] RUBY-3097 Use monotonic time for deadlines (#2646) * RUBY-3097 Use monotonic time for deadlines * RUBY-3097 fix rakefile * RUBY-3097 prefix Mongo:: * get rid of Mongo::Mongo:: * Empty-Commit * RUBY-3097 fix other occurences of Mongo::Mongo:: * RUBY-3097 fix more tests * RUBY-3097 more fixes * RUBY-3097 fix matchers * RUBY-3097 fix another test * RUBY-3097 fix spec/support/utils * fix tests * RUBY-3097 use Process in utils * RUBY-3097 more test fixes --- Rakefile | 4 ++-- spec/integration/sdam_prose_spec.rb | 4 ++-- spec/integration/server_selector_spec.rb | 4 ++-- spec/integration/srv_spec.rb | 4 ++-- spec/mongo/client_construction_spec.rb | 2 +- spec/mongo/collection_spec.rb | 4 ++-- spec/mongo/distinguishing_semaphore_spec.rb | 12 ++++++------ spec/mongo/semaphore_spec.rb | 12 ++++++------ spec/mongo/server/connection_spec.rb | 4 ++-- spec/mongo/server/monitor/connection_spec.rb | 4 ++-- spec/mongo/server/monitor_spec.rb | 4 ++-- spec/runners/cmap.rb | 4 ++-- spec/runners/transactions/operation.rb | 8 ++++---- spec/stress/connection_pool_timing_spec.rb | 20 ++++++++++---------- spec/stress/fork_reconnect_stress_spec.rb | 12 ++++++------ spec/support/aws_utils/orchestrator.rb | 13 ++++++------- spec/support/cluster_tools.rb | 12 ++++++------ spec/support/matchers.rb | 8 ++++---- spec/support/utils.rb | 8 ++++---- 19 files changed, 71 insertions(+), 72 deletions(-) diff --git a/Rakefile b/Rakefile index 88125f1be9..1ac56caeda 100644 --- a/Rakefile +++ b/Rakefile @@ -61,13 +61,13 @@ namespace :spec do client = ClientRegistry.instance.global_client('authorized') client.database.command(ping: 1) - deadline = Time.now + 300 + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 300 loop do begin client.cluster.validate_session_support! break rescue Mongo::Error::SessionsNotSupported - if Time.now >= deadline + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise "Sessions did not become supported in 300 seconds" end client.cluster.scan! diff --git a/spec/integration/sdam_prose_spec.rb b/spec/integration/sdam_prose_spec.rb index 8f85fd869c..70ef60d311 100644 --- a/spec/integration/sdam_prose_spec.rb +++ b/spec/integration/sdam_prose_spec.rb @@ -47,12 +47,12 @@ }, ) - deadline = Time.now + 10 + deadline = Mongo::Utils.monotonic_time + 10 loop do if server.average_round_trip_time > 0.25 break end - if Time.now >= deadline + if Mongo::Utils.monotonic_time >= deadline raise "Failed to witness RTT growing to >= 250 ms in 10 seconds" end sleep 0.2 diff --git a/spec/integration/server_selector_spec.rb b/spec/integration/server_selector_spec.rb index 80e68dc4a7..5c846bdc36 100644 --- a/spec/integration/server_selector_spec.rb +++ b/spec/integration/server_selector_spec.rb @@ -29,11 +29,11 @@ end it 'does not wait for server selection timeout' do - start_time = Time.now + start_time = Mongo::Utils.monotonic_time expect do result end.to raise_error(Mongo::Error::NoServerAvailable) - time_passed = Time.now - start_time + time_passed = Mongo::Utils.monotonic_time - start_time expect(time_passed).to be < 1 end end diff --git a/spec/integration/srv_spec.rb b/spec/integration/srv_spec.rb index af4edcec20..c5b323f126 100644 --- a/spec/integration/srv_spec.rb +++ b/spec/integration/srv_spec.rb @@ -42,13 +42,13 @@ end it 'times out in connect_timeout' do - start_time = Time.now + start_time = Mongo::Utils.monotonic_time lambda do client end.should raise_error(Mongo::Error::NoSRVRecords) - elapsed_time = Time.now - start_time + elapsed_time = Mongo::Utils.monotonic_time - start_time elapsed_time.should > 4 # The number of queries performed depends on local DNS search suffixes, # therefore we cannot reliably assert how long it would take for this diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index 3945de77ed..6a59b1131c 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -13,7 +13,7 @@ it 'does not perform i/o' do allow_any_instance_of(Mongo::Server::Monitor).to receive(:run!) expect_any_instance_of(Mongo::Server::Monitor).not_to receive(:scan!) - start_time = Time.now + # return should be instant c = Timeout.timeout(1) do ClientRegistry.instance.new_local_client(['1.1.1.1'], scan: false) diff --git a/spec/mongo/collection_spec.rb b/spec/mongo/collection_spec.rb index 184347b245..8291b6522c 100644 --- a/spec/mongo/collection_spec.rb +++ b/spec/mongo/collection_spec.rb @@ -834,9 +834,9 @@ end it "waits the appropriate amount of time" do - start_time = Time.now + start_time = Mongo::Utils.monotonic_time enum.try_next - end_time = Time.now + end_time = Mongo::Utils.monotonic_time expect(end_time-start_time).to be >= 3 end diff --git a/spec/mongo/distinguishing_semaphore_spec.rb b/spec/mongo/distinguishing_semaphore_spec.rb index 41c4dbca90..9fc4b31984 100644 --- a/spec/mongo/distinguishing_semaphore_spec.rb +++ b/spec/mongo/distinguishing_semaphore_spec.rb @@ -18,11 +18,11 @@ # Context switch to start the thread sleep 0.1 - start_time = Time.now + start_time = Mongo::Utils.monotonic_time semaphore.signal consumer.join - (Time.now - start_time).should < 1 + (Mongo::Utils.monotonic_time - start_time).should < 1 result.should be true end @@ -37,11 +37,11 @@ # Context switch to start the thread sleep 0.1 - start_time = Time.now + start_time = Mongo::Utils.monotonic_time semaphore.broadcast consumer.join - (Time.now - start_time).should < 1 + (Mongo::Utils.monotonic_time - start_time).should < 1 result.should be true end @@ -56,10 +56,10 @@ # Context switch to start the thread sleep 0.1 - start_time = Time.now + start_time = Mongo::Utils.monotonic_time consumer.join - (Time.now - start_time).should > 1 + (Mongo::Utils.monotonic_time - start_time).should > 1 result.should be false end diff --git a/spec/mongo/semaphore_spec.rb b/spec/mongo/semaphore_spec.rb index 0c98e9173c..8e493c8bcf 100644 --- a/spec/mongo/semaphore_spec.rb +++ b/spec/mongo/semaphore_spec.rb @@ -16,11 +16,11 @@ # Context switch to start the thread sleep 0.1 - start_time = Time.now + start_time = Mongo::Utils.monotonic_time semaphore.signal consumer.join - (Time.now - start_time).should < 1 + (Mongo::Utils.monotonic_time - start_time).should < 1 end it 'waits until broadcast' do @@ -31,11 +31,11 @@ # Context switch to start the thread sleep 0.1 - start_time = Time.now + start_time = Mongo::Utils.monotonic_time semaphore.broadcast consumer.join - (Time.now - start_time).should < 1 + (Mongo::Utils.monotonic_time - start_time).should < 1 end it 'times out' do @@ -46,9 +46,9 @@ # Context switch to start the thread sleep 0.1 - start_time = Time.now + start_time = Mongo::Utils.monotonic_time consumer.join - (Time.now - start_time).should > 1 + (Mongo::Utils.monotonic_time - start_time).should > 1 end end diff --git a/spec/mongo/server/connection_spec.rb b/spec/mongo/server/connection_spec.rb index d03132bb9e..e332d61f34 100644 --- a/spec/mongo/server/connection_spec.rb +++ b/spec/mongo/server/connection_spec.rb @@ -886,13 +886,13 @@ class ConnectionSpecTestException < Exception; end end it 'times out and raises SocketTimeoutError' do - start = Time.now + start = Mongo::Utils.monotonic_time begin Timeout::timeout(1.5 + 15) do client[authorized_collection.name].find("$where" => "sleep(2000) || true").first end rescue => ex - end_time = Time.now + end_time = Mongo::Utils.monotonic_time expect(ex).to be_a(Mongo::Error::SocketTimeoutError) expect(ex.message).to match(/Took more than 1.5 seconds to receive data/) else diff --git a/spec/mongo/server/monitor/connection_spec.rb b/spec/mongo/server/monitor/connection_spec.rb index f248ced6b2..48969b67ac 100644 --- a/spec/mongo/server/monitor/connection_spec.rb +++ b/spec/mongo/server/monitor/connection_spec.rb @@ -59,8 +59,8 @@ monitor.connection.tap do |connection| expect(connection).not_to be nil - deadline = Time.now + 5 - while Time.now < deadline + deadline = Mongo::Utils.monotonic_time + 5 + while Mongo::Utils.monotonic_time < deadline if connection.send(:socket) break end diff --git a/spec/mongo/server/monitor_spec.rb b/spec/mongo/server/monitor_spec.rb index 744393fc94..77cafa24db 100644 --- a/spec/mongo/server/monitor_spec.rb +++ b/spec/mongo/server/monitor_spec.rb @@ -52,10 +52,10 @@ context 'when calling multiple times in succession' do it 'throttles the scans to minimum 500ms' do - start = Time.now + start = Mongo::Utils.monotonic_time monitor.scan! monitor.scan! - expect(Time.now - start).to be >= 0.5 + expect(Mongo::Utils.monotonic_time - start).to be >= 0.5 end end diff --git a/spec/runners/cmap.rb b/spec/runners/cmap.rb index 8213d9ec97..489d899c57 100644 --- a/spec/runners/cmap.rb +++ b/spec/runners/cmap.rb @@ -335,7 +335,7 @@ def run_wait_for_thread_op(state) def run_wait_for_event_op(state) subscriber = @spec.subscriber looped = 0 - deadline = Time.now + 3 + deadline = Utils.monotonic_time + 3 loop do actual_events = @spec.subscriber.published_events.select do |e| e.class.name.sub(/.*::/, '').sub(/^ConnectionPool/, 'Pool') == @event.sub(/^ConnectionPool/, 'Pool') @@ -346,7 +346,7 @@ def run_wait_for_event_op(state) if looped == 1 puts("Waiting for #{@count} #{@event} events (have #{actual_events.length}): #{@spec.description}") end - if Time.now > deadline + if Utils.monotonic_time > deadline raise "Did not receive #{@count} #{@event} events in time (have #{actual_events.length}): #{@spec.description}" end looped += 1 diff --git a/spec/runners/transactions/operation.rb b/spec/runners/transactions/operation.rb index 34a0ef300e..cb34eba3d6 100644 --- a/spec/runners/transactions/operation.rb +++ b/spec/runners/transactions/operation.rb @@ -175,13 +175,13 @@ def assert_session_unpinned(collection, context) end def wait_for_event(client, context) - deadline = Time.now + 5 + deadline = Utils.monotonic_time + 5 loop do events = _select_events(context) if events.length >= arguments['count'] break end - if Time.now >= deadline + if Utils.monotonic_time >= deadline raise "Did not receive an event matching #{arguments} in 5 seconds; received #{events.length} but expected #{arguments['count']} events" else sleep 0.1 @@ -309,13 +309,13 @@ def wait_for_primary_change(client, context) else 10 end - deadline = Time.now + timeout + deadline = Utils.monotonic_time + timeout loop do client.cluster.scan! if client.cluster.next_primary.address != context.primary_address break end - if Time.now >= deadline + if Utils.monotonic_time >= deadline raise "Failed to change primary in #{timeout} seconds" end end diff --git a/spec/stress/connection_pool_timing_spec.rb b/spec/stress/connection_pool_timing_spec.rb index 124ef61026..258b1363bb 100644 --- a/spec/stress/connection_pool_timing_spec.rb +++ b/spec/stress/connection_pool_timing_spec.rb @@ -67,11 +67,11 @@ let(:threads) { operation_threads } it 'does not error' do - start = Time.now + start = Mongo::Utils.monotonic_time expect { threads.collect { |t| t.join } }.not_to raise_error - puts "[Connection Pool Timing] Duration with no max idle time: #{Time.now - start}" + puts "[Connection Pool Timing] Duration with no max idle time: #{Mongo::Utils.monotonic_time - start}" end end @@ -83,11 +83,11 @@ let(:threads) { operation_threads } it 'does not error' do - start = Time.now + start = Mongo::Utils.monotonic_time expect { threads.collect { |t| t.join } }.not_to raise_error - puts "[Connection Pool Timing] Duration with low max idle time: #{Time.now - start}" + puts "[Connection Pool Timing] Duration with low max idle time: #{Mongo::Utils.monotonic_time - start}" end end @@ -108,11 +108,11 @@ end it 'does not error' do - start = Time.now + start = Mongo::Utils.monotonic_time expect { threads.collect { |t| t.join } }.not_to raise_error - puts "[Connection Pool Timing] Duration when clear is called periodically: #{Time.now - start}" + puts "[Connection Pool Timing] Duration when clear is called periodically: #{Mongo::Utils.monotonic_time - start}" end end @@ -156,9 +156,9 @@ # when trying to perform operations during primary change sleep 1 - @primary_change_start = Time.now + @primary_change_start = Mongo::Utils.monotonic_time ClusterTools.instance.change_primary - @primary_change_end = Time.now + @primary_change_end = Mongo::Utils.monotonic_time # Primary change is complete; execute more operations more_threads.collect { |t| t.join } @@ -172,14 +172,14 @@ retry_test tries: (BSON::Environment.jruby? ? 3 : 1) it 'does not error' do threads - start = Time.now + start = Mongo::Utils.monotonic_time expect do threads.each do |t| t.join end end.not_to raise_error puts "[Connection Pool Timing] Duration before primary change: #{@primary_change_start - start}. "\ - "Duration after primary change: #{Time.now - @primary_change_end}" + "Duration after primary change: #{Mongo::Utils.monotonic_time - @primary_change_end}" end end end diff --git a/spec/stress/fork_reconnect_stress_spec.rb b/spec/stress/fork_reconnect_stress_spec.rb index af37a9b4cb..81bf83fc4d 100644 --- a/spec/stress/fork_reconnect_stress_spec.rb +++ b/spec/stress/fork_reconnect_stress_spec.rb @@ -27,20 +27,20 @@ client['foo'].insert_one(test: 1) pids = [] - deadline = Time.now + 5 + deadline = Mongo::Utils.monotonic_time + 5 1.upto(10) do if pid = fork pids << pid else Utils.wrap_forked_child do - while Time.now < deadline + while Mongo::Utils.monotonic_time < deadline client.database.command(hello: 1).should be_a(Mongo::Operation::Result) end end end end - while Time.now < deadline + while Mongo::Utils.monotonic_time < deadline # Use a read which is retried in case of an error client['foo'].find(test: 1).to_a end @@ -78,20 +78,20 @@ end pids = [] - deadline = Time.now + 5 + deadline = Mongo::Utils.monotonic_time + 5 10.times do if pid = fork pids << pid else Utils.wrap_forked_child do - while Time.now < deadline + while Mongo::Utils.monotonic_time < deadline client.database.command(hello: 1).should be_a(Mongo::Operation::Result) end end end end - while Time.now < deadline + while Mongo::Utils.monotonic_time < deadline sleep 0.1 end diff --git a/spec/support/aws_utils/orchestrator.rb b/spec/support/aws_utils/orchestrator.rb index ce6864506a..acc095142f 100644 --- a/spec/support/aws_utils/orchestrator.rb +++ b/spec/support/aws_utils/orchestrator.rb @@ -19,7 +19,7 @@ def set_instance_profile(instance_id, ) clear_instance_profile(instance_id) - deadline = Time.now + 30 + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30 begin ec2_client.associate_iam_instance_profile( iam_instance_profile: { @@ -29,7 +29,7 @@ def set_instance_profile(instance_id, instance_id: instance_id, ) rescue Aws::EC2::Errors::RequestLimitExceeded => e - if Time.now >= deadline + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise end STDERR.puts("AWS request limit exceeded: #{e.class}: #{e}, will retry") @@ -43,13 +43,13 @@ def clear_instance_profile(instance_id) :iam_instance_profile_associations, :instance_id, instance_id) if assoc - deadline = Time.now + 30 + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30 begin ec2_client.disassociate_iam_instance_profile( association_id: assoc.association_id, ) rescue Aws::EC2::Errors::RequestLimitExceeded => e - if Time.now >= deadline + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise end STDERR.puts("AWS request limit exceeded: #{e.class}: #{e}, will retry") @@ -288,7 +288,7 @@ def wait_for_ecs_ready( service_name: AWS_AUTH_ECS_SERVICE_NAME, timeout: 20 ) - deadline = Time.now + timeout + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout # The AWS SDK waiter seems to immediately fail sometimes right after # the service is created, so wait for the service to become active @@ -313,8 +313,7 @@ def wait_for_ecs_ready( status = service.status end - now = Time.now - if now >= deadline + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise "Service #{service_name} in cluster #{cluster_name} did not become ready in #{timeout} seconds (current status: #{status})" end diff --git a/spec/support/cluster_tools.rb b/spec/support/cluster_tools.rb index 24be67e54a..5ecdd31a8d 100644 --- a/spec/support/cluster_tools.rb +++ b/spec/support/cluster_tools.rb @@ -113,7 +113,7 @@ def step_down # @param [ Mongo::Address ] address def step_up(address) client = direct_client(address) - start = Time.now + start = Mongo::Utils.monotonic_time loop do begin client.database.command(replSetStepUp: 1) @@ -128,7 +128,7 @@ def step_up(address) raise end - if Time.now > start + 10 + if Mongo::Utils.monotonic_time > start + 10 raise e end end @@ -145,7 +145,7 @@ def step_up(address) # - call step down on the existing primary # - call step up on the target in a loop until it becomes the primary def change_primary - start = Time.now + start = Mongo::Utils.monotonic_time existing_primary = admin_client.cluster.next_primary existing_primary_address = existing_primary.address @@ -175,11 +175,11 @@ def change_primary persistently_step_up(target.address) new_primary = admin_client.cluster.next_primary - puts "#{Time.now} [CT] Primary changed to #{new_primary.address}. Time to change primaries: #{Time.now - start}" + puts "#{Time.now} [CT] Primary changed to #{new_primary.address}. Time to change primaries: #{Mongo::Utils.monotonic_time - start}" end def persistently_step_up(address) - start = Time.now + start = Mongo::Utils.monotonic_time loop do puts "#{Time.now} [CT] Asking #{address} to step up" @@ -189,7 +189,7 @@ def persistently_step_up(address) break end - if Time.now - start > 10 + if Mongo::Utils.monotonic_time - start > 10 raise "Unable to get #{address} instated as primary after 10 seconds" end end diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb index f14324c204..1fccd9db21 100644 --- a/spec/support/matchers.rb +++ b/spec/support/matchers.rb @@ -60,17 +60,17 @@ RSpec::Matchers.define :take_longer_than do |min_expected_time| match do |proc| - start_time = Time.now + start_time = Mongo::Utils.monotonic_time proc.call - (Time.now - start_time).should > min_expected_time + (Mongo::Utils.monotonic_time - start_time).should > min_expected_time end end RSpec::Matchers.define :take_shorter_than do |min_expected_time| match do |proc| - start_time = Time.now + start_time = Mongo::Utils.monotonic_time proc.call - (Time.now - start_time).should < min_expected_time + (Mongo::Utils.monotonic_time - start_time).should < min_expected_time end end diff --git a/spec/support/utils.rb b/spec/support/utils.rb index c17bef20fc..bce1495565 100644 --- a/spec/support/utils.rb +++ b/spec/support/utils.rb @@ -502,7 +502,7 @@ def match_with_type?(expected, actual) end module_function def wait_for_instance_profile - deadline = Time.now + 15 + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 15 loop do begin ip = ec2_instance_profile @@ -513,7 +513,7 @@ def match_with_type?(expected, actual) rescue => e puts "Problem retrieving instance profile: #{e.class}: #{e}" end - if Time.now >= deadline + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise 'Instance profile did not get assigned in 15 seconds' end sleep 3 @@ -521,7 +521,7 @@ def match_with_type?(expected, actual) end module_function def wait_for_no_instance_profile - deadline = Time.now + 15 + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 15 loop do begin ip = ec2_instance_profile @@ -532,7 +532,7 @@ def match_with_type?(expected, actual) rescue => e puts "Problem retrieving instance profile: #{e.class}: #{e}" end - if Time.now >= deadline + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise 'Instance profile did not get cleared in 15 seconds' end sleep 3 From 1436dda2533df6ecba680a70bc7a92be30f0f646 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 24 Oct 2022 18:51:30 +0300 Subject: [PATCH 066/198] RUBY-3115 Run legacy FLE tests on serverless (#2648) --- .evergreen/config-atlas.yml | 9 +++- .evergreen/config.yml | 2 +- .evergreen/config/atlas.yml.erb | 1 + .evergreen/config/common.yml.erb | 10 ++--- .evergreen/run-tests-serverless.sh | 44 +++++++++++++++++++ .mod/drivers-evergreen-tools | 2 +- .../fle2-BypassQueryAnalysis.yml | 2 +- .../client_side_encryption/fle2-Compact.yml | 2 +- .../fle2-CreateCollection.yml | 2 +- .../fle2-DecryptExistingData.yml | 2 +- .../client_side_encryption/fle2-Delete.yml | 2 +- ...-EncryptedFields-vs-EncryptedFieldsMap.yml | 2 +- .../fle2-EncryptedFields-vs-jsonSchema.yml | 2 +- .../fle2-EncryptedFieldsMap-defaults.yml | 2 +- .../fle2-FindOneAndUpdate.yml | 2 +- .../fle2-InsertFind-Indexed.yml | 2 +- .../fle2-InsertFind-Unindexed.yml | 2 +- .../fle2-MissingKey.yml | 2 +- .../fle2-NoEncryption.yml | 2 +- .../client_side_encryption/fle2-Update.yml | 2 +- ...le2-validatorAndPartialFieldExpression.yml | 2 +- 21 files changed, 74 insertions(+), 24 deletions(-) diff --git a/.evergreen/config-atlas.yml b/.evergreen/config-atlas.yml index 6b8dcf2e28..5c16b7928b 100644 --- a/.evergreen/config-atlas.yml +++ b/.evergreen/config-atlas.yml @@ -14,7 +14,7 @@ pre_error_fails_task: true command_type: system # Protect ourself against rogue test case, or curl gone wild, that runs forever. -exec_timeout_secs: 1800 +exec_timeout_secs: 2700 # What to do when evergreen hits the timeout (`post:` tasks are run automatically) @@ -112,6 +112,9 @@ functions: export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" EOT # See what we've done cat expansion.yml @@ -421,7 +424,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" .evergreen/run-tests-serverless.sh + SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh pre: - func: "fetch source" @@ -444,6 +447,7 @@ tasks: - func: "run Atlas tests" - name: "test-serverless" commands: + - func: "export FLE credentials" - func: "run serverless tests" axes: @@ -852,6 +856,7 @@ buildvariants: - matrix_name: "serverless" matrix_spec: ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + fle: helper display_name: "Atlas serverless ${ruby} single mongos" run_on: - ubuntu1804-small diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 043c021fef..900047fdd2 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -446,7 +446,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" .evergreen/run-tests-serverless.sh + SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh pre: - func: "fetch source" diff --git a/.evergreen/config/atlas.yml.erb b/.evergreen/config/atlas.yml.erb index bc7f63a1ac..1f602b945c 100644 --- a/.evergreen/config/atlas.yml.erb +++ b/.evergreen/config/atlas.yml.erb @@ -11,6 +11,7 @@ buildvariants: - matrix_name: "serverless" matrix_spec: ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + fle: helper display_name: "Atlas serverless ${ruby} single mongos" run_on: - ubuntu1804-small diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 29bd231356..fa1d1e16f4 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -11,11 +11,7 @@ pre_error_fails_task: true command_type: system # Protect ourself against rogue test case, or curl gone wild, that runs forever. -<% if @atlas %> -exec_timeout_secs: 1800 -<% else %> exec_timeout_secs: 2700 -<% end %> <% egos_wrap = "./egos stdbuf -i0 -o0 -e0" %> @@ -115,6 +111,9 @@ functions: export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" EOT # See what we've done cat expansion.yml @@ -514,7 +513,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" .evergreen/run-tests-serverless.sh + SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh pre: - func: "fetch source" @@ -540,6 +539,7 @@ tasks: - func: "run Atlas tests" - name: "test-serverless" commands: + - func: "export FLE credentials" - func: "run serverless tests" <% else %> - name: "test-docker" diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index 221da16c38..fc12699889 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -16,6 +16,46 @@ export MONGODB_URI=`echo ${SERVERLESS_URI} | sed -r 's/mongodb\+srv:\/\//mongodb export TOPOLOGY="load-balanced" +python3 -u .evergreen/mongodl.py --component crypt_shared -V ${SERVERLESS_MONGODB_VERSION} --out `pwd`/csfle_lib --target `host_distro` || true +if test -f `pwd`/csfle_lib/lib/mongo_crypt_v1.so +then + echo Usinn crypt shared library version ${SERVERLESS_MONGODB_VERSION} + export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=`pwd`/csfle_lib/lib/mongo_crypt_v1.so +else + echo Failed to download crypt shared library + exit -1 +fi + +. .evergreen/csfle/activate_venv.sh +pip3 install boto3~=1.19 'cryptography<3.4' pykmip~=0.10.0 + +python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/server.pem --port 7999 & +python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/expired.pem --port 8000 & +python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/wrong-host.pem --port 8001 & +python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/server.pem --port 8002 --require_client_cert & +python3 -u .evergreen/csfle/kms_kmip_server.py & + +echo "Waiting for mock KMS servers to start..." +wait_for_kms_server() { + for i in $(seq 60); do + if curl -s "localhost:$1"; test $? -ne 7; then + return 0 + else + sleep 1 + fi + done + echo "Could not detect mock KMS server on port $1" + return 1 +} +wait_for_kms_server 8000 +wait_for_kms_server 8001 +wait_for_kms_server 8002 +wait_for_kms_server 5698 +echo "Waiting for mock KMS servers to start... done." + +# Obtain temporary AWS credentials +PYTHON=python3 . .evergreen/csfle/set-temp-creds.sh + echo "Running specs" bundle exec rspec \ @@ -24,6 +64,7 @@ bundle exec rspec \ spec/spec_tests/retryable_writes_spec.rb \ spec/spec_tests/transactions_spec.rb \ spec/spec_tests/change_streams_unified_spec.rb \ + spec/spec_tests/client_side_encryption_spec.rb \ spec/spec_tests/client_side_encryption_unified_spec.rb \ spec/spec_tests/command_monitoring_unified_spec.rb \ spec/spec_tests/crud_unified_spec.rb \ @@ -35,5 +76,8 @@ bundle exec rspec \ spec/spec_tests/transactions_unified_spec.rb kill_jruby +# Terminate all kmip servers... and whatever else happens to be running +# that is a python script. +pkill python3 exit ${test_status} diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index 36de96a1f6..976d3c8020 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit 36de96a1f65c6471d150d69aef7ea8b7f1e9ddc9 +Subproject commit 976d3c8020e95040e5d413b345f50c106b7bc3b7 diff --git a/spec/spec_tests/data/client_side_encryption/fle2-BypassQueryAnalysis.yml b/spec/spec_tests/data/client_side_encryption/fle2-BypassQueryAnalysis.yml index f653527d36..e6b3d66505 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-BypassQueryAnalysis.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-BypassQueryAnalysis.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Compact.yml b/spec/spec_tests/data/client_side_encryption/fle2-Compact.yml index b5e788f369..6e3a198d02 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Compact.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-Compact.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml b/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml index 160da07ff9..78d7e55e07 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml @@ -2,7 +2,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-DecryptExistingData.yml b/spec/spec_tests/data/client_side_encryption/fle2-DecryptExistingData.yml index c799eae21d..d1d0e2384a 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-DecryptExistingData.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-DecryptExistingData.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [ diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2-Delete.yml index 6b6714233a..7dfe117681 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-Delete.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml b/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml index 5d26e2e8b1..ba4e7a7361 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-jsonSchema.yml b/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-jsonSchema.yml index 6b822ff724..2ff2e4da1c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-jsonSchema.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-jsonSchema.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFieldsMap-defaults.yml b/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFieldsMap-defaults.yml index 395b8c27bd..0ee418db05 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFieldsMap-defaults.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFieldsMap-defaults.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2-FindOneAndUpdate.yml index 96ce954bec..a5596c2c24 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-FindOneAndUpdate.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Indexed.yml b/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Indexed.yml index 5b785d8cf2..5c5efbcc5e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Indexed.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Indexed.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml b/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml index 285b46d270..71f1993e56 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-MissingKey.yml b/spec/spec_tests/data/client_side_encryption/fle2-MissingKey.yml index 94c624538b..937c82ee9e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-MissingKey.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-MissingKey.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [ diff --git a/spec/spec_tests/data/client_side_encryption/fle2-NoEncryption.yml b/spec/spec_tests/data/client_side_encryption/fle2-NoEncryption.yml index 7a547eee0f..8725ac1ef7 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-NoEncryption.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-NoEncryption.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2-Update.yml index 19bb549b23..929f4a3a38 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-Update.yml @@ -1,7 +1,7 @@ runOn: - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] diff --git a/spec/spec_tests/data/client_side_encryption/fle2-validatorAndPartialFieldExpression.yml b/spec/spec_tests/data/client_side_encryption/fle2-validatorAndPartialFieldExpression.yml index b85a7c876d..9463ddecad 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-validatorAndPartialFieldExpression.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-validatorAndPartialFieldExpression.yml @@ -3,7 +3,7 @@ runOn: # Require server version 6.0.0 to get behavior added in SERVER-64911. - minServerVersion: "6.0.0" # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" From 4864e4b8bc2381eb15eefb87e71cf5d0badc4e05 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 26 Oct 2022 11:03:47 -0400 Subject: [PATCH 067/198] RUBY-3157 Remove the unified/invalid tests (#2652) --- ...ctedEventsForClient-ignoreExtraEvents-type.yml | 15 --------------- spec/spec_tests/data/unified/tests-minItems.yml | 5 ----- spec/spec_tests/unified_spec.rb | 5 ----- 3 files changed, 25 deletions(-) delete mode 100644 spec/spec_tests/data/unified/invalid/expectedEventsForClient-ignoreExtraEvents-type.yml delete mode 100644 spec/spec_tests/data/unified/tests-minItems.yml diff --git a/spec/spec_tests/data/unified/invalid/expectedEventsForClient-ignoreExtraEvents-type.yml b/spec/spec_tests/data/unified/invalid/expectedEventsForClient-ignoreExtraEvents-type.yml deleted file mode 100644 index c42886899b..0000000000 --- a/spec/spec_tests/data/unified/invalid/expectedEventsForClient-ignoreExtraEvents-type.yml +++ /dev/null @@ -1,15 +0,0 @@ -description: "expectedEventsForClient-ignoreExtraEvents-type" - -schemaVersion: "1.7" - -createEntities: - - client: - id: &client0 "client0" - -tests: - - description: "foo" - operations: [] - expectEvents: - - client: *client0 - events: [] - ignoreExtraEvents: 0 diff --git a/spec/spec_tests/data/unified/tests-minItems.yml b/spec/spec_tests/data/unified/tests-minItems.yml deleted file mode 100644 index 834120f835..0000000000 --- a/spec/spec_tests/data/unified/tests-minItems.yml +++ /dev/null @@ -1,5 +0,0 @@ -description: "tests-minItems" - -schemaVersion: "1.0" - -tests: [] diff --git a/spec/spec_tests/unified_spec.rb b/spec/spec_tests/unified_spec.rb index 609be2f067..36c8dd2f51 100644 --- a/spec/spec_tests/unified_spec.rb +++ b/spec/spec_tests/unified_spec.rb @@ -6,7 +6,6 @@ require 'runners/unified' base = "#{CURRENT_PATH}/spec_tests/data/unified" -INVALID_UNIFIED_TESTS = Dir.glob("#{base}/invalid/**/*.yml").sort PASS_UNIFIED_TESTS = Dir.glob("#{base}/valid-pass/**/*.yml").sort FAIL_UNIFIED_TESTS = Dir.glob("#{base}/valid-fail/**/*.yml").sort @@ -14,10 +13,6 @@ define_unified_spec_tests(base, PASS_UNIFIED_TESTS) end -describe 'Unified spec tests - invalid' do - define_unified_spec_tests(base, INVALID_UNIFIED_TESTS) -end - describe 'Unified spec tests - expected failures' do define_unified_spec_tests(base, FAIL_UNIFIED_TESTS, expect_failure: true) end From aff32a69a92012441257599023e5f48d161d391c Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 3 Nov 2022 11:44:27 -0400 Subject: [PATCH 068/198] RUBY-2496 Implement and test connection pool paused state (#2650) * RUBY-2496 Implement and test connection pool paused state * remove pool from server upon disconnection so that the pool can be recreated upon reconnection * redo exceptions to make more sense * client is now usable again * cannot get rid of pools upon server disconnection * make more servers not usable * fix tests * Revert "client is now usable again" This reverts commit 345c4c2593a6ad3e992cf656581492075ecc06af. * but pool is now paused * remove stress tests no longer applicable * use internal_pool * diagnostics * separate closing and disconnection * plug test issue * also remove replica_set parameter * client closed tracking * close * close * close * fix tests * repair cmap tests * light clean up and comments * fix server_spec * reconnect client after clean slate * fix connection_pool_spec tests * fix server spec * RUBY-2496 fix connection tests * RUBY-2496 fix cursor tests * RUBY-2496 fix some more tests * RUBY-2496 fix sdam_error_handling spec * RUBY-2496 require no linting * RUBY-2496 fix auto_encrpytion_reconnect specs * RUBY-2496 fix client close specs * RUBY-2496 fix more tests * update spec shared venv * update spec shared * Empty-Commit * RUBY-2496 attempt to fix errors * require spec helper * RUBY-2496 reconnect if client is closed * RUBY-2496 remove byebug * keep map reduce tests * RUBY-2496 fix client_perished? * RUBY-2496 require no linting * RUBY-2496 disallow lb * rename virtualenv method * RUBY-2496 get rid of lint error * RUBY-2496 pause on clear but dont clear on pause * RUBY-2496 don't disconnect servers on descirption change * Revert "RUBY-2496 don't disconnect servers on descirption change" This reverts commit 1e832b46390dbd7103c0bab3ad0f351580b69496. * RUBY-2496 mark the server as unknown when marked for removal * RUBY-2496 fix sdam error handling spec * RUBY-2496 fix more lint errors * RUBY-2496 dont pause a cleared load balanced pool * fix more lint tests * another lint fix * add debugging info * more debugging * typo * typo * typo * RUBY-2496 pend lb tests * RUBY-2496 make server not usable error retryable * RUBY-2496 clear description on removal * fix sdam_spec * attempt to fix step_down * debugging stuff * fix debug failures * RUBY-2496 use pool_internal on cursor reaper * Revert "RUBY-2496 use pool_internal on cursor reaper" This reverts commit 6016f5ecc5899f52e53e4ba21ec663f148d31222. * fix tests * RUBY-2496 disable flaky jruby test * RUBY-2496 retry flaky test * switch back to pool internal * retry flaky retryable tests * remove debug stuff * Update lib/mongo/monitoring/event/cmap/pool_ready.rb Co-authored-by: Dmitry Rybakov * RUBY-2496 extract generation manager code * change back return value of Server#disconnect! * add api private * Empty-Commit Co-authored-by: Oleg Pudeyev Co-authored-by: Dmitry Rybakov --- .evergreen/run-tests.sh | 2 +- lib/mongo/client.rb | 21 ++- lib/mongo/cluster.rb | 20 ++- lib/mongo/cluster/reapers/cursor_reaper.rb | 6 +- lib/mongo/cluster/reapers/socket_reaper.rb | 2 +- lib/mongo/cluster/sdam_flow.rb | 3 + lib/mongo/cursor.rb | 2 +- lib/mongo/error.rb | 4 + lib/mongo/error/client_closed.rb | 24 +++ lib/mongo/error/pool_closed_error.rb | 19 +-- lib/mongo/error/pool_error.rb | 45 +++++ lib/mongo/error/pool_paused_error.rb | 38 +++++ lib/mongo/error/server_not_usable.rb | 36 ++++ lib/mongo/monitoring/event/cmap.rb | 1 + lib/mongo/monitoring/event/cmap/pool_ready.rb | 67 ++++++++ lib/mongo/retryable.rb | 25 ++- lib/mongo/server.rb | 89 +++++++--- lib/mongo/server/connection_pool.rb | 160 ++++++++++++++++-- .../connection_pool/generation_manager.rb | 31 +++- lib/mongo/server_selector/base.rb | 20 +++ spec/integration/change_stream_spec.rb | 5 + spec/integration/client_construction_spec.rb | 1 + .../auto_encryption_reconnect_spec.rb | 8 +- .../client_close_spec.rb | 20 +-- spec/integration/client_spec.rb | 22 ++- .../connection_pool_populator_spec.rb | 10 +- spec/integration/cursor_pinning_spec.rb | 4 + spec/integration/reconnect_spec.rb | 3 + spec/integration/sdam_error_handling_spec.rb | 7 +- spec/integration/server_selector_spec.rb | 2 + spec/integration/server_spec.rb | 44 ++--- .../snapshot_query_examples_spec.rb | 2 + spec/integration/step_down_spec.rb | 6 +- spec/integration/transaction_pinning_spec.rb | 7 + spec/mongo/client_spec.rb | 6 +- spec/mongo/cluster_spec.rb | 18 +- spec/mongo/collection/view/map_reduce_spec.rb | 10 ++ spec/mongo/cursor_spec.rb | 19 ++- spec/mongo/lint_spec.rb | 2 +- .../server/connection_pool/populator_spec.rb | 4 +- spec/mongo/server/connection_pool_spec.rb | 44 ++++- spec/mongo/server/connection_spec.rb | 13 ++ spec/mongo/server_spec.rb | 36 +++- spec/runners/cmap.rb | 92 ++++++++-- spec/runners/cmap/verifier.rb | 4 +- spec/runners/crud/test_base.rb | 2 +- spec/shared | 2 +- spec/spec_tests/cmap_spec.rb | 7 + .../data/cmap/pool-checkout-no-idle.yml | 6 + .../data/cmap/pool-checkout-no-stale.yml | 8 + .../data/cmap/pool-clear-min-size.yml | 41 +++++ .../data/cmap/pool-create-min-size.yml | 6 + spec/spec_tests/retryable_reads_spec.rb | 2 + spec/spec_tests/sdam_spec.rb | 4 + spec/stress/connection_pool_stress_spec.rb | 61 ------- spec/stress/connection_pool_timing_spec.rb | 25 --- spec/support/client_registry.rb | 2 +- spec/support/common_shortcuts.rb | 22 ++- 58 files changed, 910 insertions(+), 282 deletions(-) create mode 100644 lib/mongo/error/client_closed.rb create mode 100644 lib/mongo/error/pool_error.rb create mode 100644 lib/mongo/error/pool_paused_error.rb create mode 100644 lib/mongo/error/server_not_usable.rb create mode 100644 lib/mongo/monitoring/event/cmap/pool_ready.rb create mode 100644 spec/spec_tests/data/cmap/pool-clear-min-size.yml diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index cff14598be..a359236cec 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -44,7 +44,7 @@ set_env_ruby prepare_server $arch if test "$DOCKER_PRELOAD" != 1; then - install_mlaunch_virtualenv + install_mlaunch_venv fi # Launching mongod under $MONGO_ORCHESTRATION_HOME diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index 25f289f4ea..9958100833 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -596,9 +596,9 @@ def initialize(addresses_or_uri, options = nil) rescue begin - @cluster.disconnect! + @cluster.close rescue => e - log_warn("Eror disconnecting cluster in client constructor's exception handler: #{e.class}: #{e}") + log_warn("Eror closing cluster in client constructor's exception handler: #{e.class}: #{e}") # Drop this exception so that the original exception is raised end raise @@ -859,6 +859,10 @@ def write_concern @write_concern ||= WriteConcern.get(options[:write_concern] || options[:write]) end + def closed? + !!@closed + end + # Close all connections. # # @return [ true ] Always true. @@ -866,6 +870,7 @@ def write_concern # @since 2.1.0 def close @connect_lock.synchronize do + @closed = true do_close end true @@ -899,6 +904,8 @@ def reconnect if @options[:auto_encryption_options] build_encrypter end + + @closed = false end true @@ -1133,6 +1140,8 @@ def get_session(options = {}) # # @api private def with_session(options = {}, &block) + assert_not_closed + session = get_session(options) yield session @@ -1195,7 +1204,7 @@ def default_options(options) # Implementation for #close, assumes the connect lock is already acquired. def do_close - @cluster.disconnect! + @cluster.close close_encrypter end @@ -1588,5 +1597,11 @@ def validate_read!(option, opts) end true end + + def assert_not_closed + if closed? + raise Error::ClientClosed, "The client was closed and is not usable for operations. Call #reconnect to reset this client instance or create a new client instance" + end + end end end diff --git a/lib/mongo/cluster.rb b/lib/mongo/cluster.rb index a543e22829..161c88506e 100644 --- a/lib/mongo/cluster.rb +++ b/lib/mongo/cluster.rb @@ -497,13 +497,16 @@ def summary # events in the process. Stops SRV monitoring if it is active. # Marks the cluster disconnected. # - # @return [ true ] Always true. + # A closed cluster is no longer usable. If the client is reconnected, + # it will create a new cluster instance. # - # @since 2.1.0 - def disconnect! + # @return [ nil ] Always nil. + # + # @api private + def close @state_change_lock.synchronize do unless connecting? || connected? - return true + return nil end if options[:cleanup] != false session_pool.end_sessions @@ -516,7 +519,7 @@ def disconnect! end @servers.each do |server| if server.connected? - server.disconnect! + server.close publish_sdam_event( Monitoring::SERVER_CLOSED, Monitoring::Event::ServerClosed.new(server.address, topology) @@ -531,7 +534,7 @@ def disconnect! @connecting = @connected = false end end - true + nil end # Reconnect all servers. @@ -697,12 +700,16 @@ def run_sdam_flow(previous_desc, updated_desc, options = {}) # @api private def set_server_list(server_address_strs) @sdam_flow_lock.synchronize do + # If one of the new addresses is not in the current servers list, + # add it to the servers list. server_address_strs.each do |address_str| unless servers_list.any? { |server| server.address.seed == address_str } add(address_str) end end + # If one of the servers' addresses are not in the new address list, + # remove that server from the servers list. servers_list.each do |server| unless server_address_strs.any? { |address_str| server.address.seed == address_str } remove(server.address.seed) @@ -927,6 +934,7 @@ def servers_list # @api private def disconnect_server_if_connected(server) if server.connected? + server.clear_description server.disconnect! publish_sdam_event( Monitoring::SERVER_CLOSED, diff --git a/lib/mongo/cluster/reapers/cursor_reaper.rb b/lib/mongo/cluster/reapers/cursor_reaper.rb index 1ff8b0e7df..581768182d 100644 --- a/lib/mongo/cluster/reapers/cursor_reaper.rb +++ b/lib/mongo/cluster/reapers/cursor_reaper.rb @@ -197,11 +197,7 @@ def kill_cursors op.execute(server, context: Operation::Context.new(options: options)) if session = kill_spec.session - if session.ended? - if Lint.enabled? - raise Error::LintError, "Ended session for a garbage collected cursor" - end - elsif session.implicit? + if session.implicit? session.end_session end end diff --git a/lib/mongo/cluster/reapers/socket_reaper.rb b/lib/mongo/cluster/reapers/socket_reaper.rb index 88c1078eb0..a667e1ed92 100644 --- a/lib/mongo/cluster/reapers/socket_reaper.rb +++ b/lib/mongo/cluster/reapers/socket_reaper.rb @@ -48,7 +48,7 @@ def initialize(cluster) # @since 2.5.0 def execute @cluster.servers.each do |server| - server.pool.close_idle_sockets + server.pool_internal&.close_idle_sockets end true end diff --git a/lib/mongo/cluster/sdam_flow.rb b/lib/mongo/cluster/sdam_flow.rb index 37cfd73606..b63a90c029 100644 --- a/lib/mongo/cluster/sdam_flow.rb +++ b/lib/mongo/cluster/sdam_flow.rb @@ -413,6 +413,9 @@ def remove def do_remove(address_str) servers = cluster.remove(address_str, disconnect: false) servers.each do |server| + # Clear the description so that the server is marked as unknown. + server.clear_description + # We need to publish server closed event here, but we cannot close # the server because it could be the server owning the monitor in # whose thread this flow is presently executing, in which case closing diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index 90f24a7863..d6a5db77bc 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -299,7 +299,7 @@ def close end nil - rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError + rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable # Errors are swallowed since there is noting can be done by handling them. ensure end_session diff --git a/lib/mongo/error.rb b/lib/mongo/error.rb index f647aa181e..409a8496fb 100644 --- a/lib/mongo/error.rb +++ b/lib/mongo/error.rb @@ -139,6 +139,7 @@ def write_concern_error_labels require 'mongo/error/write_retryable' require 'mongo/error/change_stream_resumable' require 'mongo/error/bulk_write_error' +require 'mongo/error/client_closed' require 'mongo/error/closed_stream' require 'mongo/error/connection_check_out_timeout' require 'mongo/error/connection_perished' @@ -194,7 +195,9 @@ def write_concern_error_labels require 'mongo/error/snapshot_session_invalid_server_version' require 'mongo/error/snapshot_session_transaction_prohibited' require 'mongo/error/operation_failure' +require 'mongo/error/pool_error' require 'mongo/error/pool_closed_error' +require 'mongo/error/pool_paused_error' require 'mongo/error/raise_original_error' require 'mongo/error/server_certificate_revoked' require 'mongo/error/socket_error' @@ -211,6 +214,7 @@ def write_concern_error_labels require 'mongo/error/missing_service_id' require 'mongo/error/server_api_conflict' require 'mongo/error/server_api_not_supported' +require 'mongo/error/server_not_usable' require 'mongo/error/unknown_payload_type' require 'mongo/error/unmet_dependency' require 'mongo/error/unsupported_option' diff --git a/lib/mongo/error/client_closed.rb b/lib/mongo/error/client_closed.rb new file mode 100644 index 0000000000..535a89b811 --- /dev/null +++ b/lib/mongo/error/client_closed.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2022 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Error + + class ClientClosed < Error + end + end +end diff --git a/lib/mongo/error/pool_closed_error.rb b/lib/mongo/error/pool_closed_error.rb index 71eca7568e..775d5e7d42 100644 --- a/lib/mongo/error/pool_closed_error.rb +++ b/lib/mongo/error/pool_closed_error.rb @@ -21,19 +21,7 @@ class Error # Exception raised if an operation is attempted on a closed connection pool. # # @since 2.9.0 - class PoolClosedError < Error - - # @return [ Mongo::Address ] address The address of the server the - # pool's connections connect to. - # - # @since 2.9.0 - attr_reader :address - - # @return [ Mongo::Server::ConnectionPool ] pool The pool that is closed. - # - # @since 2.11.0 - # @api experimental - attr_reader :pool + class PoolClosedError < PoolError # Instantiate the new exception. # @@ -43,9 +31,8 @@ class PoolClosedError < Error # @since 2.9.0 # @api private def initialize(address, pool) - @address = address - @pool = pool - super("Attempted to use a connection pool which has been closed (for #{address} " + + super(address, pool, + "Attempted to use a connection pool which has been closed (for #{address} " + "with pool 0x#{pool.object_id})") end end diff --git a/lib/mongo/error/pool_error.rb b/lib/mongo/error/pool_error.rb new file mode 100644 index 0000000000..83e39f714c --- /dev/null +++ b/lib/mongo/error/pool_error.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2020 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Error + + # Abstract base class for connection pool-related exceptions. + class PoolError < Error + + # @return [ Mongo::Address ] address The address of the server the + # pool's connections connect to. + # + # @since 2.9.0 + attr_reader :address + + # @return [ Mongo::Server::ConnectionPool ] pool The connection pool. + # + # @since 2.11.0 + attr_reader :pool + + # Instantiate the new exception. + # + # @api private + def initialize(address, pool, message) + @address = address + @pool = pool + super(message) + end + end + end +end diff --git a/lib/mongo/error/pool_paused_error.rb b/lib/mongo/error/pool_paused_error.rb new file mode 100644 index 0000000000..de0a1d3857 --- /dev/null +++ b/lib/mongo/error/pool_paused_error.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2020 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Error + + # Exception raised if an operation is attempted on a paused connection pool. + class PoolPausedError < PoolError + + # Instantiate the new exception. + # + # @example Instantiate the exception. + # Mongo::Error::PoolClosedError.new(address, pool) + # + # @since 2.9.0 + # @api private + def initialize(address, pool) + super(address, pool, + "Attempted to use a connection pool which is paused (for #{address} " + + "with pool 0x#{pool.object_id})") + end + end + end +end diff --git a/lib/mongo/error/server_not_usable.rb b/lib/mongo/error/server_not_usable.rb new file mode 100644 index 0000000000..b1ef3e8738 --- /dev/null +++ b/lib/mongo/error/server_not_usable.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2020 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Error + include WriteRetryable + include ChangeStreamResumable + + # Exception raised if an unknown server is attempted to be used for + # an operation. + class ServerNotUsable < Error + + # Instantiate the new exception. + # + # @api private + def initialize(address) + @address = address + super("Attempted to use an unknown server at #{address}") + end + end + end +end diff --git a/lib/mongo/monitoring/event/cmap.rb b/lib/mongo/monitoring/event/cmap.rb index c3465b46db..7028eea5e2 100644 --- a/lib/mongo/monitoring/event/cmap.rb +++ b/lib/mongo/monitoring/event/cmap.rb @@ -26,3 +26,4 @@ require 'mongo/monitoring/event/cmap/pool_cleared' require 'mongo/monitoring/event/cmap/pool_closed' require 'mongo/monitoring/event/cmap/pool_created' +require 'mongo/monitoring/event/cmap/pool_ready' diff --git a/lib/mongo/monitoring/event/cmap/pool_ready.rb b/lib/mongo/monitoring/event/cmap/pool_ready.rb new file mode 100644 index 0000000000..494de397aa --- /dev/null +++ b/lib/mongo/monitoring/event/cmap/pool_ready.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-present MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Monitoring + module Event + module Cmap + + # Event published when a connection pool is marked ready. + class PoolReady < Base + + # @return [ Mongo::Address ] address The address of the server the pool's connections will + # connect to. + attr_reader :address + + # @return [ Hash ] options Options specified for pool creation. + attr_reader :options + + # @return [ Mongo::Server::ConnectionPool ] pool The pool that was just + # created. + # + # @api experimental + attr_reader :pool + + # Create the event. + # + # @example Create the event. + # PoolCreated.new(address, options, pool) + # + # @since 2.9.0 + # @api private + def initialize(address, options, pool) + @address = address + @options = options.dup.freeze + @pool = pool + end + + # Returns a concise yet useful summary of the event. + # + # @return [ String ] String summary of the event. + # + # @note This method is experimental and subject to change. + # + # @api experimental + def summary + "#<#{self.class.name.sub(/^Mongo::Monitoring::Event::Cmap::/, '')} " + + "address=#{address} options=#{options} pool=0x#{pool.object_id}>" + end + end + end + end + end +end diff --git a/lib/mongo/retryable.rb b/lib/mongo/retryable.rb index 32aca9b61b..bec730238d 100644 --- a/lib/mongo/retryable.rb +++ b/lib/mongo/retryable.rb @@ -131,7 +131,7 @@ def read_with_retry(session = nil, server_selector = nil, &block) server = select_server(cluster, server_selector, session) begin yield server - rescue Error::SocketError, Error::SocketTimeoutError, Error::OperationFailure => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::OperationFailure => e e.add_note('retries disabled') raise e end @@ -163,7 +163,7 @@ def read_with_retry(session = nil, server_selector = nil, &block) # @since 2.2.6 def read_with_one_retry(options = nil) yield - rescue Error::SocketError, Error::SocketTimeoutError => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable => e retry_message = options && options[:retry_message] log_retry(e, message: retry_message) yield @@ -243,15 +243,14 @@ def write_with_retry(write_concern, ending_transaction: false, context:, &block) # it later for the retry as well. yield(connection, txn_num, context.dup) end - rescue Error::SocketError, Error::SocketTimeoutError, Auth::Unauthorized => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Auth::Unauthorized => e e.add_note('modern retry') e.add_note("attempt 1") if !e.label?('RetryableWriteError') - # If we get an auth error, it was raised when connecting the connection - # and therefore we didn't have the connection yet to add labels. - # Therefore, check if it is retryable, and if it is, add the label - # and retry it. We also want to retry this if there was a Socket error - # when trying to create the connection. + # If there was an error before the connection was successfully + # checked out and connected, there was no connection present to use + # for adding labels. Therefore, we should check if it is retryable, + # and if it is, add the label and retry it. if !connection_succeeded && !session.in_transaction? && e.write_retryable? e.add_label('RetryableWriteError') else @@ -305,7 +304,7 @@ def nro_write_with_retry(write_concern, context:, &block) server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield connection, nil, context end - rescue Error::SocketError, Error::SocketTimeoutError, Error::OperationFailure => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::OperationFailure => e e.add_note('retries disabled') raise e end @@ -368,7 +367,7 @@ def modern_read_with_retry(session, server_selector, &block) server = select_server(cluster, server_selector, session) begin yield server - rescue Error::SocketError, Error::SocketTimeoutError => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ServerNotUsable => e e.add_note('modern retry') e.add_note("attempt 1") if session.in_transaction? @@ -391,7 +390,7 @@ def legacy_read_with_retry(session, server_selector) begin attempt += 1 yield server - rescue Error::SocketError, Error::SocketTimeoutError => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable => e e.add_note('legacy retry') e.add_note("attempt #{attempt}") if attempt > client.max_read_retries || (session && session.in_transaction?) @@ -446,7 +445,7 @@ def retry_read(original_error, server_selector, session, &block) begin yield server, true - rescue Error::SocketError, Error::SocketTimeoutError => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable => e e.add_note('modern retry') e.add_note("attempt 2") raise e @@ -493,7 +492,7 @@ def retry_write(original_error, txn_num, context:, &block) server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield(connection, txn_num, context) end - rescue Error::SocketError, Error::SocketTimeoutError => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable => e e.add_note('modern retry') e.add_note('attempt 2') raise e diff --git a/lib/mongo/server.rb b/lib/mongo/server.rb index 9bc00daba9..2a3ef29ba3 100644 --- a/lib/mongo/server.rb +++ b/lib/mongo/server.rb @@ -267,25 +267,38 @@ def disconnect! if monitor monitor.stop! end - _pool = @pool_lock.synchronize do - @pool - end - if _pool - # For backwards compatibility we disconnect/clear the pool rather - # than close it here. We also stop the populator which allows the - # the pool to continue providing connections but stops it from - # connecting in background on clients/servers that are in fact - # intended to be closed and no longer used. - begin - _pool.disconnect!(stop_populator: true) - rescue Error::PoolClosedError - # If the pool was already closed, we don't need to do anything here. - end - end + @connected = false + + # The current CMAP spec requires a pool to be mostly unusable + # if its server is unknown (or, therefore, disconnected). + # However any outstanding operations should continue to completion, + # and their connections need to be checked into the pool to be + # torn down. Because of this cleanup requirement we cannot just + # close the pool and set it to nil here, to be recreated the next + # time the server is discovered. + pool_internal&.clear + true end + def close + if monitor + monitor.stop! + end + + @connected = false + + _pool = nil + @pool_lock.synchronize do + _pool, @pool = @pool, nil + end + + _pool&.close + + nil + end + # Whether the server is connected. # # @return [ true|false ] Whether the server is connected. @@ -397,8 +410,28 @@ def summary # # @since 2.0.0 def pool + if unknown? + raise Error::ServerNotUsable, address + end + + @pool_lock.synchronize do + @pool ||= ConnectionPool.new(self, options).tap do |pool| + pool.ready + end + end + end + + # Internal driver method to retrieve the connection pool for this server. + # + # Unlike +pool+, +pool_internal+ will not create a pool if one does not + # already exist. + # + # @return [ Server::ConnectionPool | nil ] The connection pool, if one exists. + # + # @api private + def pool_internal @pool_lock.synchronize do - @pool ||= ConnectionPool.new(self, options) + @pool end end @@ -544,6 +577,8 @@ def retry_writes? # # @since 2.4.0, SDAM events are sent as of version 2.7.0 def unknown!(options = {}) + pool = pool_internal + if load_balancer? # When the client is in load-balanced topology, servers (the one and # only that can be) starts out as a load balancer and stays as a @@ -558,12 +593,12 @@ def unknown!(options = {}) # this server, but the server can still be marked unknown if one # of such connections failed midway through its establishment. if service_id = options[:service_id] - pool.disconnect!(service_id: service_id) + pool&.disconnect!(service_id: service_id) end return end - if options[:generation] && options[:generation] < pool.generation + if options[:generation] && options[:generation] < pool&.generation return end @@ -596,6 +631,18 @@ def unknown!(options = {}) # @api private def update_description(description) @description = description + pool = pool_internal + if pool && !unknown? + pool.ready + end + end + + # Clear the servers description so that it is considered unknown and can be + # safely disconnected. + # + # @api private + def clear_description + @description = Mongo::Server::Description.new(address, {}) end # @param [ Object ] :service_id Close connections with the specified @@ -604,7 +651,11 @@ def update_description(description) # @api private def clear_connection_pool(service_id: nil) @pool_lock.synchronize do - if @pool + # A server being marked unknown after it is closed is technically + # incorrect but it does not meaningfully alter any state. + # Because historically the driver permitted servers to be marked + # unknown at any time, continue doing so even if the pool is closed. + if @pool && !@pool.closed? @pool.disconnect!(service_id: service_id) end end diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index 0e82c8af52..ad8f3f0527 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -112,6 +112,7 @@ def initialize(server, options = {}) @options = options.freeze @generation_manager = GenerationManager.new(server: server) + @ready = false @closed = false # A connection owned by this pool should be either in the @@ -142,15 +143,16 @@ def initialize(server, options = {}) publish_cmap_event( Monitoring::Event::Cmap::PoolCreated.new(@server.address, options, self) ) - - @populator.run! if min_size > 0 end # @return [ Hash ] options The pool options. attr_reader :options # @api private - def_delegators :@server, :address + attr_reader :server + + # @api private + def_delegators :server, :address # Get the maximum size of the connection pool. # @@ -196,7 +198,20 @@ def max_idle_time # being used by the queue. # # @api private - def_delegator :generation_manager, :generation + def_delegators :generation_manager, :generation, :generation_unlocked + + # A connection pool is paused if it is not closed and it is not ready. + # + # @return [ true | false ] whether the connection pool is paused. + # + # @raise [ Error::PoolClosedError ] If the pool has been closed. + def paused? + raise_if_closed! + + @lock.synchronize do + !@ready + end + end # Size of the connection pool. # @@ -244,14 +259,30 @@ def closed? !!@closed end + # Whether the pool is ready. + # + # @return [ true | false ] Whether the pool is ready. + def ready? + @lock.synchronize do + @ready + end + end + # @note This method is experimental and subject to change. # # @api experimental # @since 2.11.0 def summary @lock.synchronize do + state = if closed? + 'closed' + elsif !@ready + 'paused' + else + 'ready' + end "#" + "used=#{@checked_out_connections.length} avail=#{@available_connections.length} pending=#{@pending_connections.length} #{state}>" end end @@ -296,6 +327,18 @@ def check_out(connection_global_id: nil) raise Error::PoolClosedError.new(@server.address, self) end + if paused? + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( + @server.address, + # CMAP spec decided to conflate pool paused with all the other + # possible non-timeout errors. + Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR, + ), + ) + raise Error::PoolPausedError.new(@server.address, self) + end + deadline = Utils.monotonic_time + wait_timeout pid = Process.pid connection = nil @@ -520,16 +563,34 @@ def check_in(connection) check_invariants end + def pause + raise_if_closed! + + check_invariants + + if Lint.enabled? && !@server.unknown? + raise Error::LintError, "Attempting to pause pool for server #{@server.summary} which is known" + end + + return if paused? + + @lock.synchronize do + @ready = false + end + + stop_populator + ensure + check_invariants + end + # Closes all idle connections in the pool and schedules currently checked # out connections to be closed when they are checked back into the pool. - # The pool remains operational and can create new connections when - # requested. + # The pool is paused, it will not create new connections in background + # and it will fail checkout requests until marked ready. # # @option options [ true | false ] :lazy If true, do not close any of # the idle connections and instead let them be closed during a # subsequent check out operation. - # @option options [ true | false ] :stop_populator Whether to stop - # the populator background thread. For internal driver use only. # @option options [ Object ] :service_id Clear connections with # the specified service id only. # @@ -539,24 +600,49 @@ def check_in(connection) def clear(options = nil) raise_if_closed! - check_invariants - - if options && options[:stop_populator] - stop_populator + if Lint.enabled? && !@server.unknown? + raise Error::LintError, "Attempting to clear pool for server #{@server.summary} which is known" end + do_clear(options) + end + + # Disconnects the pool. + # + # Does everything that +clear+ does, except if the pool is closed + # this method does nothing but +clear+ would raise PoolClosedError. + # + # @since 2.1.0 + # @api private + def disconnect!(options = nil) + do_clear(options) + rescue Error::PoolClosedError + # The "disconnected" state is between closed and paused. + # When we are trying to disconnect the pool, permit the pool to be + # already closed. + end + + def do_clear(options = nil) + check_invariants + service_id = options && options[:service_id] @lock.synchronize do + # Generation must be bumped before emitting pool cleared event. @generation_manager.bump(service_id: service_id) + end + unless paused? publish_cmap_event( Monitoring::Event::Cmap::PoolCleared.new( @server.address, service_id: service_id ) ) + pause unless @server.load_balancer? + end + @lock.synchronize do unless options && options[:lazy] if @server.load_balancer? && service_id loop do @@ -586,9 +672,30 @@ def clear(options = nil) check_invariants end - # @since 2.1.0 - # @deprecated - alias :disconnect! :clear + # Instructs the pool to create and return connections. + def ready + raise_if_closed! + + @lock.synchronize do + return if @ready + + @ready = true + end + + # Note that the CMAP spec demands serialization of CMAP events for a + # pool. In order to implement this, event publication must be done into + # a queue which is synchronized, instead of subscribers being invoked + # from the trigger method like this one here inline. On MRI, assuming + # the threads yield to others when they stop having work to do, it is + # likely that the events would in practice always be published in the + # required order. JRuby, being truly concurrent with OS threads, + # would not offers such a guarantee. + publish_cmap_event( + Monitoring::Event::Cmap::PoolReady.new(@server.address, options, self) + ) + + @populator.run! if min_size > 0 + end # Marks the pool closed, closes all idle connections in the pool and # schedules currently checked out connections to be closed when they are @@ -647,6 +754,9 @@ def inspect if closed? "#" + elsif !ready? + "#" else "#" @@ -824,6 +934,14 @@ def create_and_add_connection connection = create_connection @pending_connections << connection else + conn = @available_connections.detect do |conn| + connection_stale_unlocked?(conn) + end + if conn + conn.disconnect!(reason: :stale) + @available_connections.delete(conn) + return true + end return false end end @@ -848,6 +966,16 @@ def create_and_add_connection true end + # Checks whether a connection is stale. + # + # @param [ Mongo::Server::Connection ] connection The connection to check. + # + # @return [ true | false ] Whether the connection is stale. + def connection_stale_unlocked?(connection) + connection.generation != generation_unlocked(service_id: connection.service_id) && + !connection.pinned? + end + # Asserts that the pool has not been closed. # # @raise [ Error::PoolClosedError ] If the pool has been closed. diff --git a/lib/mongo/server/connection_pool/generation_manager.rb b/lib/mongo/server/connection_pool/generation_manager.rb index 3f62c5ca6e..be4fbb0ef0 100644 --- a/lib/mongo/server/connection_pool/generation_manager.rb +++ b/lib/mongo/server/connection_pool/generation_manager.rb @@ -31,20 +31,19 @@ def initialize(server:) attr_reader :server def generation(service_id: nil) - if service_id - unless server.load_balancer? - raise ArgumentError, "Generation scoping to services is only available in load-balanced mode, but the server at #{server.address} is not a load balancer" - end - else - if server.load_balancer? - raise ArgumentError, "The server at #{server.address} is a load balancer and therefore does not have a single global generation" - end - end + validate_service_id!(service_id) + @lock.synchronize do @map[service_id] end end + def generation_unlocked(service_id: nil) + validate_service_id!(service_id) + + @map[service_id] + end + def bump(service_id: nil) @lock.synchronize do if service_id @@ -65,6 +64,20 @@ def bump(service_id: nil) end end end + + private + + def validate_service_id!(service_id) + if service_id + unless server.load_balancer? + raise ArgumentError, "Generation scoping to services is only available in load-balanced mode, but the server at #{server.address} is not a load balancer" + end + else + if server.load_balancer? + raise ArgumentError, "The server at #{server.address} is a load balancer and therefore does not have a single global generation" + end + end + end end end end diff --git a/lib/mongo/server_selector/base.rb b/lib/mongo/server_selector/base.rb index 95f45f0e20..88373187a4 100644 --- a/lib/mongo/server_selector/base.rb +++ b/lib/mongo/server_selector/base.rb @@ -175,6 +175,15 @@ def ==(other) # # @since 2.0.0 def select_server(cluster, ping = nil, session = nil, write_aggregation: false) + select_server_impl(cluster, ping, session, write_aggregation).tap do |server| + if Lint.enabled? && !server.pool.ready? + raise Error::LintError, 'Server selector returning a server with a pool which is not ready' + end + end + end + + # Parameters and return values are the same as for select_server. + private def select_server_impl(cluster, ping, session, write_aggregation) if cluster.topology.is_a?(Cluster::Topology::LoadBalanced) return cluster.servers.first end @@ -245,6 +254,17 @@ def select_server(cluster, ping = nil, session = nil, write_aggregation: false) =end loop do + if Lint.enabled? + cluster.servers.each do |server| + if !server.unknown? && !server.connected? + raise Error::LintError, "Server #{server.summary} is known but is not connected" + end + if !server.unknown? && !server.pool.ready? + raise Error::LintError, "Server #{server.summary} is known but has non-ready pool" + end + end + end + server = try_select_server(cluster, write_aggregation: write_aggregation) if server diff --git a/spec/integration/change_stream_spec.rb b/spec/integration/change_stream_spec.rb index d627acafdb..baefd1fa7b 100644 --- a/spec/integration/change_stream_spec.rb +++ b/spec/integration/change_stream_spec.rb @@ -289,6 +289,11 @@ def clear_fail_point_before enum.next end.to raise_error(Mongo::Error::OperationFailure, /101\b.*Failing command (due to|via) 'failCommand' failpoint/) end + + after do + # TODO see RUBY-3135. + clear_fail_point(authorized_collection) + end end end diff --git a/spec/integration/client_construction_spec.rb b/spec/integration/client_construction_spec.rb index d6e98b5d2d..124655c853 100644 --- a/spec/integration/client_construction_spec.rb +++ b/spec/integration/client_construction_spec.rb @@ -107,6 +107,7 @@ let(:options) do SpecConfig.instance.test_options.tap do |opt| opt.delete(:connect) + opt.delete(:replica_set) opt.update(direct_connection: true) end end diff --git a/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb b/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb index 5de34dd5b4..d186002bf1 100644 --- a/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb @@ -133,7 +133,9 @@ context 'after closing mongocryptd client and reconnecting' do before do - mongocryptd_client.close + # don't use the mongocryptd_client variable yet so that it will be computed + # after the client reconnects + client.encrypter.mongocryptd_client.close client.reconnect end @@ -144,7 +146,9 @@ context 'after killing mongocryptd client monitor thread and reconnecting' do before do - thread = mongocryptd_client.cluster.servers.first.monitor.instance_variable_get('@thread') + # don't use the mongocryptd_client variable yet so that it will be computed + # after the client reconnects + thread = client.encrypter.mongocryptd_client.cluster.servers.first.monitor.instance_variable_get('@thread') expect(thread).to be_alive thread.kill diff --git a/spec/integration/client_side_encryption/client_close_spec.rb b/spec/integration/client_side_encryption/client_close_spec.rb index e69916dec7..252f3196b5 100644 --- a/spec/integration/client_side_encryption/client_close_spec.rb +++ b/spec/integration/client_side_encryption/client_close_spec.rb @@ -28,17 +28,11 @@ ) end - shared_examples 'a functioning auto-encrypter' do - it 'can still perform encryption' do - result = client['users'].insert_one(ssn: '000-000-0000') - expect(result).to be_ok - - encrypted_document = authorized_client - .use('auto_encryption')['users'] - .find(_id: result.inserted_ids.first) - .first - - expect(encrypted_document['ssn']).to be_ciphertext + shared_examples 'a non-functioning auto-encrypter' do + it 'raises a client closed error' do + expect do + client['users'].insert_one(ssn: '000-000-0000') + end.to raise_error(Mongo::Error::ClientClosed) end end @@ -51,7 +45,7 @@ client.close end - it_behaves_like 'a functioning auto-encrypter' + it_behaves_like 'a non-functioning auto-encrypter' end context 'after performing operation without auto encryption' do @@ -60,7 +54,7 @@ client.close end - it_behaves_like 'a functioning auto-encrypter' + it_behaves_like 'a non-functioning auto-encrypter' end end end diff --git a/spec/integration/client_spec.rb b/spec/integration/client_spec.rb index e004c5608f..3e50d27547 100644 --- a/spec/integration/client_spec.rb +++ b/spec/integration/client_spec.rb @@ -4,6 +4,9 @@ require 'spec_helper' describe 'Client' do + # TODO after the client is closed, operations should fail with an exception + # that communicates this state, instead of failing with server selection or + # pool errors. RUBY-3102 context 'after client is disconnected' do let(:client) { authorized_client.with(server_selection_timeout: 1) } @@ -11,13 +14,18 @@ client.close end - it 'is still usable for operations' do - resp = client.database.command(ping: 1) - expect(resp).to be_a(Mongo::Operation::Result) + it 'fails in connection pool' do + lambda do + client.database.command(ping: 1) + end.should raise_error(Mongo::Error::ClientClosed) end - it 'is still usable for operations that can use sessions' do - client['collection'].insert_one(test: 1) + context 'operation that can use sessions' do + it 'fails in connection pool' do + lambda do + client['collection'].insert_one(test: 1) + end.should raise_error(Mongo::Error::ClientClosed) + end end context 'after all servers are marked unknown' do @@ -33,7 +41,7 @@ it 'fails server selection' do expect do client.database.command(ping: 1) - end.to raise_error(Mongo::Error::NoServerAvailable) + end.to raise_error(Mongo::Error::ClientClosed) end end @@ -41,7 +49,7 @@ it 'fails server selection' do expect do client['collection'].insert_one(test: 1) - end.to raise_error(Mongo::Error::NoServerAvailable) + end.to raise_error(Mongo::Error::ClientClosed) end end end diff --git a/spec/integration/connection_pool_populator_spec.rb b/spec/integration/connection_pool_populator_spec.rb index eebdf4812f..342031e947 100644 --- a/spec/integration/connection_pool_populator_spec.rb +++ b/spec/integration/connection_pool_populator_spec.rb @@ -89,12 +89,14 @@ describe '#clear' do context 'when a min size is provided' do + require_no_linting + let(:options) do { min_pool_size: 1 } end it 'repopulates the pool periodically only up to min size' do - pool + pool.ready sleep 2 expect(pool.size).to eq(1) @@ -103,7 +105,9 @@ pool.check_in(first_connection) pool.clear + expect(pool.size).to eq(0) + pool.ready sleep 2 expect(pool.size).to eq(1) expect(pool.available_count).to eq(1) @@ -282,6 +286,10 @@ it 'populates the parent and child pools' do client = ClientRegistry.instance.new_local_client([SpecConfig.instance.addresses.first], server_options.merge(min_pool_size: 2, max_pool_size: 5)) + + # force initialization of the pool + client.cluster.servers.first.pool + # let pool populate sleep 2 server = client.cluster.next_primary diff --git a/spec/integration/cursor_pinning_spec.rb b/spec/integration/cursor_pinning_spec.rb index d10e9cbc27..43b726f952 100644 --- a/spec/integration/cursor_pinning_spec.rb +++ b/spec/integration/cursor_pinning_spec.rb @@ -23,6 +23,10 @@ # When not in load-balanced topology, iterating a cursor creates # new connections as needed. + before do + client.reconnect + end + it 'creates new connections for iteration' do server.pool.size.should == 0 diff --git a/spec/integration/reconnect_spec.rb b/spec/integration/reconnect_spec.rb index 3854b35601..56b5f8e45e 100644 --- a/spec/integration/reconnect_spec.rb +++ b/spec/integration/reconnect_spec.rb @@ -51,6 +51,9 @@ end context 'with min_pool_size > 0' do + # This test causes live threads errors in jruby in other tests. + fails_on_jruby + let(:client) { authorized_client.with(min_pool_size: 1) } it 'recreates connection pool populator thread' do diff --git a/spec/integration/sdam_error_handling_spec.rb b/spec/integration/sdam_error_handling_spec.rb index 5c756d2653..3c30120b60 100644 --- a/spec/integration/sdam_error_handling_spec.rb +++ b/spec/integration/sdam_error_handling_spec.rb @@ -92,7 +92,7 @@ generation = server.pool.generation RSpec::Mocks.with_temporary_scope do operation - new_generation = server.pool.generation + new_generation = server.pool_internal.generation expect(new_generation).to eq(generation + 1) end end @@ -103,7 +103,7 @@ generation = server.pool.generation RSpec::Mocks.with_temporary_scope do operation - new_generation = server.pool.generation + new_generation = server.pool_internal.generation expect(new_generation).to eq(generation) end end @@ -248,12 +248,13 @@ it 'marks server unknown' do server = client.cluster.next_primary + pool = client.cluster.pool(server) client.cluster.servers.map(&:disconnect!) RSpec::Mocks.with_temporary_scope do Socket.should receive(:new).with(any_args).ordered.once.and_return(socket) - + allow(pool).to receive(:paused?).and_return(false) lambda do client.command(ping: 1) end.should raise_error(mapped_error_cls, /mocked failure/) diff --git a/spec/integration/server_selector_spec.rb b/spec/integration/server_selector_spec.rb index 5c846bdc36..fa15623643 100644 --- a/spec/integration/server_selector_spec.rb +++ b/spec/integration/server_selector_spec.rb @@ -4,6 +4,8 @@ require 'spec_helper' describe 'Server selector' do + require_no_linting + let(:selector) { Mongo::ServerSelector::Primary.new } let(:client) { authorized_client } let(:cluster) { client.cluster } diff --git a/spec/integration/server_spec.rb b/spec/integration/server_spec.rb index 66d77bbda1..55e90ef5b6 100644 --- a/spec/integration/server_spec.rb +++ b/spec/integration/server_spec.rb @@ -12,28 +12,34 @@ let(:view) { Mongo::Collection::View.new(collection) } describe 'operations when client/cluster are disconnected' do - shared_examples 'it performs read operations and receives the correct result type' do + context 'it performs read operations and receives the correct result type' do context 'normal server' do it 'can be used for reads' do result = view.send(:send_initial_query, server) - expect(result).to be_a(result_class) + expect(result).to be_a(Mongo::Operation::Find::Result) end end context 'known server in disconnected cluster' do + require_topology :single, :replica_set, :sharded + require_no_linting + before do - client.close + server.disconnect! expect(server).not_to be_unknown end - it 'can be used for reads' do - result = view.send(:send_initial_query, server) - expect(result).to be_a(result_class) + it 'fails in connection pool' do + # See also RUBY-3102. + lambda do + view.send(:send_initial_query, server) + end.should raise_error(Mongo::Error::PoolPausedError) end end context 'unknown server in disconnected cluster' do require_topology :single, :replica_set, :sharded + require_no_linting before do client.close @@ -41,29 +47,13 @@ expect(server).to be_unknown end - it 'can be used for reads' do - result = view.send(:send_initial_query, server) - expect(result).to be_a(result_class) + it 'is unusable' do + # See also RUBY-3102. + lambda do + view.send(:send_initial_query, server) + end.should raise_error(Mongo::Error::ServerNotUsable) end end end - - context 'for servers with FCV >= 3.4' do - min_server_fcv '3.2' - - let(:result_class) { Mongo::Operation::Find::Result } - - it_behaves_like 'it performs read operations and receives the correct result type' - end - - context 'for servers with FCV < 3.4' do - # Find command was introduced in server version 3.2, so older versions should - # receive legacy result types. - max_server_fcv '3.0' - - let(:result_class) { Mongo::Operation::Find::Legacy::Result } - - it_behaves_like 'it performs read operations and receives the correct result type' - end end end diff --git a/spec/integration/snapshot_query_examples_spec.rb b/spec/integration/snapshot_query_examples_spec.rb index 6f1a6a93dc..a1c9eeed9b 100644 --- a/spec/integration/snapshot_query_examples_spec.rb +++ b/spec/integration/snapshot_query_examples_spec.rb @@ -71,6 +71,8 @@ end context "Snapshot Query Example 2" do + retry_test + before do client = authorized_client.use('retail') client['sales'].delete_many diff --git a/spec/integration/step_down_spec.rb b/spec/integration/step_down_spec.rb index 9c69efb222..19f15b09c2 100644 --- a/spec/integration/step_down_spec.rb +++ b/spec/integration/step_down_spec.rb @@ -48,6 +48,7 @@ describe 'getMore iteration' do min_server_fcv '4.2' + require_no_linting let(:subscribed_client) do test_client.tap do |client| @@ -66,8 +67,9 @@ let(:enum) { view.to_enum } it 'continues through step down' do - - subscribed_client.cluster.next_primary.pool.clear + server = subscribed_client.cluster.next_primary + server.pool_internal.do_clear + server.pool_internal.ready subscriber.clear_events! # get the first item diff --git a/spec/integration/transaction_pinning_spec.rb b/spec/integration/transaction_pinning_spec.rb index 4fb8080c44..3d62c20f9e 100644 --- a/spec/integration/transaction_pinning_spec.rb +++ b/spec/integration/transaction_pinning_spec.rb @@ -24,6 +24,7 @@ # Force each transaction to be on its own connection. before do + client.reconnect if client.closed? 4.times do |i| # Collections cannot be created inside transactions. client["tx_pin_t#{i}"].drop @@ -65,8 +66,10 @@ # particular service. context 'when no connection is available' do + require_no_linting before do + client.reconnect if client.closed? client["tx_pin"].drop client["tx_pin"].create end @@ -90,6 +93,10 @@ context 'when connection is available' do + before do + client.reconnect if client.closed? + end + it 'uses the available connection' do sessions = [] connections = [] diff --git a/spec/mongo/client_spec.rb b/spec/mongo/client_spec.rb index 0544820176..10c2f04fc2 100644 --- a/spec/mongo/client_spec.rb +++ b/spec/mongo/client_spec.rb @@ -774,7 +774,7 @@ it 'disconnects the cluster and returns true' do RSpec::Mocks.with_temporary_scope do - expect(client.cluster).to receive(:disconnect!).and_call_original + expect(client.cluster).to receive(:close).and_call_original expect(client.close).to be(true) end end @@ -1250,7 +1250,9 @@ context 'background threads killed' do let(:client) do authorized_client.tap do |client| - client.cluster.servers.map(&:disconnect!) + client.cluster.servers.map do |server| + server.monitor&.stop! + end end end diff --git a/spec/mongo/cluster_spec.rb b/spec/mongo/cluster_spec.rb index c241fe95fa..6dccf89b06 100644 --- a/spec/mongo/cluster_spec.rb +++ b/spec/mongo/cluster_spec.rb @@ -329,7 +329,7 @@ end end - describe '#disconnect!' do + describe '#close' do let(:cluster) { cluster_with_semaphore } let(:known_servers) do @@ -340,23 +340,23 @@ cluster.instance_variable_get(:@periodic_executor) end - describe 'disconnection' do + describe 'closing' do before do known_servers.each do |server| - expect(server).to receive(:disconnect!).and_call_original + expect(server).to receive(:close).and_call_original end expect(periodic_executor).to receive(:stop!).and_call_original end - it 'disconnects each server and the cursor reaper and returns true' do - expect(cluster.disconnect!).to be(true) + it 'disconnects each server and the cursor reaper and returns nil' do + expect(cluster.close).to be nil end end - describe 'repeated disconnection' do + describe 'repeated closing' do before do known_servers.each do |server| - expect(server).to receive(:disconnect!).and_call_original + expect(server).to receive(:close).and_call_original end expect(periodic_executor).to receive(:stop!).and_call_original end @@ -366,10 +366,10 @@ it 'publishes server closed event once' do monitoring.subscribe(Mongo::Monitoring::SERVER_CLOSED, subscriber) - expect(cluster.disconnect!).to be(true) + expect(cluster.close).to be nil expect(subscriber.first_event('server_closed_event')).not_to be nil subscriber.succeeded_events.clear - expect(cluster.disconnect!).to be(true) + expect(cluster.close).to be nil expect(subscriber.first_event('server_closed_event')).to be nil end end diff --git a/spec/mongo/collection/view/map_reduce_spec.rb b/spec/mongo/collection/view/map_reduce_spec.rb index 1f6815ef25..b6e65ff436 100644 --- a/spec/mongo/collection/view/map_reduce_spec.rb +++ b/spec/mongo/collection/view/map_reduce_spec.rb @@ -40,6 +40,10 @@ end let(:view) do + authorized_client.cluster.servers.map do |server| + server.pool.ready + end + Mongo::Collection::View.new(authorized_collection, selector, view_options) end @@ -593,6 +597,7 @@ context 'when the server is not valid for writing' do clean_slate require_warning_clean + require_no_linting before do stop_monitoring(authorized_client) @@ -615,6 +620,10 @@ end let(:view) do + authorized_client.cluster.servers.map do |server| + server.pool.ready + end + Mongo::Collection::View.new(collection, selector, view_options) end @@ -674,6 +683,7 @@ context 'when the server is a valid for writing' do clean_slate require_warning_clean + require_no_linting before do stop_monitoring(authorized_client) diff --git a/spec/mongo/cursor_spec.rb b/spec/mongo/cursor_spec.rb index 3fa1768a3b..84547dbc79 100644 --- a/spec/mongo/cursor_spec.rb +++ b/spec/mongo/cursor_spec.rb @@ -39,14 +39,15 @@ # Deal with this by pre-creating pools for all known servers. cluster = authorized_collection.client.cluster cluster.next_primary - cluster.servers_list.each do |server| - server.pool + cluster.servers.each do |server| + reset_pool(server) end end end context 'cursor exhausted by initial result' do include_context 'with initialized pool' + require_no_linting let(:view) do Mongo::Collection::View.new(authorized_collection) @@ -64,6 +65,7 @@ context 'cursor not exhausted by initial result' do include_context 'with initialized pool' + require_no_linting let(:view) do Mongo::Collection::View.new(authorized_collection, {}, batch_size: 2) @@ -84,7 +86,7 @@ let(:server) do view.send(:server_selector).select_server(authorized_client.cluster).tap do |server| - authorized_client.cluster.disconnect! + authorized_client.cluster.close server.unknown! end end @@ -93,8 +95,10 @@ Mongo::Collection::View.new(authorized_collection) end - it 'works' do - cursor + it 'raises ServerNotUsable' do + lambda do + cursor + end.should raise_error(Mongo::Error::ServerNotUsable) end end end @@ -746,6 +750,11 @@ context 'when there is a socket error during close' do clean_slate + require_no_linting + + before do + reset_pool(server) + end it 'does not raise an error' do cursor diff --git a/spec/mongo/lint_spec.rb b/spec/mongo/lint_spec.rb index 0ccdc5b489..9f8aee723d 100644 --- a/spec/mongo/lint_spec.rb +++ b/spec/mongo/lint_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # encoding: utf-8 -require 'lite_spec_helper' +require 'spec_helper' describe Mongo::Lint do diff --git a/spec/mongo/server/connection_pool/populator_spec.rb b/spec/mongo/server/connection_pool/populator_spec.rb index 9d121326a8..c2abb27359 100644 --- a/spec/mongo/server/connection_pool/populator_spec.rb +++ b/spec/mongo/server/connection_pool/populator_spec.rb @@ -4,6 +4,8 @@ require 'spec_helper' describe Mongo::Server::ConnectionPool::Populator do + require_no_linting + let(:options) { {} } let(:client) do @@ -28,7 +30,7 @@ # We create our own populator to test; disable pool's background populator # and clear the pool, so ours can run pool.stop_populator - pool.clear + pool.disconnect! end describe '#log_warn' do diff --git a/spec/mongo/server/connection_pool_spec.rb b/spec/mongo/server/connection_pool_spec.rb index 235761e07a..6ed9df0e37 100644 --- a/spec/mongo/server/connection_pool_spec.rb +++ b/spec/mongo/server/connection_pool_spec.rb @@ -57,7 +57,9 @@ end let(:pool) do - register_pool(described_class.new(server, server_options)) + register_pool(described_class.new(server, server_options)).tap do |pool| + pool.ready + end end describe '#initialize' do @@ -304,7 +306,7 @@ end after do - server.disconnect! + server.close end let(:options) do @@ -666,7 +668,11 @@ client.cluster.next_primary.pool end - it 'raises an error and emits ConnectionCheckOutFailedEvent' do + before do + pool.ready + end + + it 'raises an error and emits ConnectionCheckOutFailedEvent' do pool subscriber = Mrss::EventSubscriber.new @@ -687,9 +693,25 @@ end describe '#disconnect!' do + + context 'when pool is closed' do + before do + pool.close + end + + it 'does nothing' do + expect do + pool.disconnect! + end.not_to raise_error + end + end + end + + describe '#clear' do def create_pool(min_pool_size) opts = SpecConfig.instance.test_options.merge(max_pool_size: 3, min_pool_size: min_pool_size) described_class.new(server, opts).tap do |pool| + pool.ready # kill background thread to test disconnect behavior pool.stop_populator expect(pool.instance_variable_get('@populator').running?).to be false @@ -711,6 +733,7 @@ def create_pool(min_pool_size) # require different setup and assertions because load balancers # do not have a single global generation. require_topology :single, :replica_set, :sharded + require_no_linting it 'disconnects and removes and bumps' do old_connections = [] @@ -727,6 +750,8 @@ def create_pool(min_pool_size) expect(pool.size).to eq(0) expect(pool.available_count).to eq(0) + pool.ready + new_connection = pool.check_out expect(old_connections).not_to include(new_connection) expect(new_connection.generation).to eq(2) @@ -756,7 +781,7 @@ def create_pool(min_pool_size) it 'raises PoolClosedError' do expect do - pool.disconnect! + pool.clear end.to raise_error(Mongo::Error::PoolClosedError) end end @@ -795,8 +820,8 @@ def create_pool(min_pool_size) end after do - server.disconnect! - pool.close # this will no longer be needed after server disconnect kills bg thread + server.close + pool.close # this will no longer be needed after server close kills bg thread end it 'includes the object id' do @@ -1072,15 +1097,20 @@ def create_pool(min_pool_size) end describe '#populate' do + require_no_linting + before do # Disable the populator and clear the pool to isolate populate behavior pool.stop_populator - pool.clear + pool.disconnect! + # Manually mark the pool ready. + pool.instance_variable_set('@ready', true) end let(:options) { {min_pool_size: 2, max_pool_size: 3} } context 'when pool size is at least min_pool_size' do + before do first_connection = pool.check_out second_connection = pool.check_out diff --git a/spec/mongo/server/connection_spec.rb b/spec/mongo/server/connection_spec.rb index e332d61f34..3fcac8ca07 100644 --- a/spec/mongo/server/connection_spec.rb +++ b/spec/mongo/server/connection_spec.rb @@ -967,6 +967,11 @@ class ConnectionSpecTestException < Exception; end ) end + before do + allow(server).to receive(:unknown?).and_return(false) + allow(server2).to receive(:unknown?).and_return(false) + end + it 'ids do not share namespace' do server.pool.with_connection do |conn| expect(conn.id).to eq(1) @@ -1227,6 +1232,10 @@ class ConnectionSpecTestException < Exception; end context 'non-lb' do require_topology :single, :replica_set, :sharded + before do + allow(server).to receive(:unknown?).and_return(false) + end + it 'is set' do server.with_connection do |conn| conn.service_id.should be nil @@ -1237,6 +1246,10 @@ class ConnectionSpecTestException < Exception; end context 'clean slate' do clean_slate + before do + allow(server).to receive(:unknown?).and_return(false) + end + it 'starts from 1' do server.with_connection do |conn| conn.service_id.should be nil diff --git a/spec/mongo/server_spec.rb b/spec/mongo/server_spec.rb index c7c3b712e1..b6b6a0fab0 100644 --- a/spec/mongo/server_spec.rb +++ b/spec/mongo/server_spec.rb @@ -121,16 +121,44 @@ end end - it 'disconnects the connection pool' do - expect(server.pool).to receive(:disconnect!).once.and_call_original - server.disconnect! + context 'when server has a pool' do + before do + allow(server).to receive(:unknown?).and_return(false) + allow(cluster).to receive(:run_sdam_flow) + server.pool.ready + end + + it 'pauses and clears the connection pool' do + expect(server.pool_internal).to receive(:pause).once.and_call_original + expect(server.pool_internal).to receive(:clear).once.and_call_original + RSpec::Mocks.with_temporary_scope do + # you can't disconnect from a known server, since this pauses the + # pool and we only want to pause the pools of unknown servers. + server.unknown! + allow(server).to receive(:unknown?).and_return(true) + server.disconnect! + end + end end context 'when server reconnects' do + before do + allow(server).to receive(:unknown?).and_return(false) + allow(cluster).to receive(:run_sdam_flow) + server.pool.ready + end + it 'keeps the same pool' do pool = server.pool - server.disconnect! + RSpec::Mocks.with_temporary_scope do + # you can't disconnect from a known server, since this pauses the + # pool and we only want to pause the pools of unknown servers. + # server.unknown! + allow(server).to receive(:unknown?).and_return(true) + server.disconnect! + end server.reconnect! + allow(server).to receive(:unknown?).and_return(false) expect(server.pool).to eq(pool) end end diff --git a/spec/runners/cmap.rb b/spec/runners/cmap.rb index 489d899c57..8cd7f703f8 100644 --- a/spec/runners/cmap.rb +++ b/spec/runners/cmap.rb @@ -50,20 +50,20 @@ def initialize(test_path) @expected_events = @test['events'] @ignore_events = @test['ignore'] || [] + process_run_on preprocess end + attr_reader :pool + def setup(server, subscriber) @subscriber = subscriber - @pool = server.pool - - # let pool populate - ([0.1, 0.15, 0.15] + [0.2] * 20).each do |t| - if @pool.size >= @pool.min_size - break - end - sleep t - end + # The driver always creates pools for known servers. + # There is a test which creates and destroys a pool and it only expects + # those two events, not the ready event. + # This situation cannot happen in normal driver operation, but to + # support this test, create the pool manually here. + @pool = Mongo::Server::ConnectionPool.new(server, server.options) end def run @@ -80,7 +80,7 @@ def run end result['error'] ||= nil - result['events'] = subscriber.published_events.reduce([]) do |events, event| + result['events'] = subscriber.published_events.each_with_object([]) do |event, events| next events unless event.is_a?(Mongo::Monitoring::Event::Cmap::Base) event = case event @@ -142,14 +142,35 @@ def run 'type' => 'ConnectionPoolCleared', 'address' => event.address, } + when Mongo::Monitoring::Event::Cmap::PoolReady + { + 'type' => 'ConnectionPoolReady', + 'address' => event.address, + } + else + raise "Unhandled event: #{event}" end - events << event unless @ignore_events.include?(event['type']) - events + events << event unless @ignore_events.include?(event.fetch('type')) end end end + def satisfied? + cc = ClusterConfig.instance + ok = true + if @min_server_version + ok &&= Gem::Version.new(cc.fcv_ish) >= Gem::Version.new(@min_server_version) + end + if @max_server_version + ok &&= Gem::Version.new(cc.server_version) <= Gem::Version.new(@max_server_version) + end + if @topologies + ok &&= @topologies.include?(cc.topology) + end + ok + end + private # Converts the options used by the Ruby driver to the spec test format. @@ -189,6 +210,11 @@ def process_options(options) opts[:wait_queue_size] = kv.last when 'waitQueueTimeoutMS' opts[:wait_queue_timeout] = kv.last / 1000.0 + when 'backgroundThreadIntervalMS' + # The populator busy loops, this option doesn't apply to our driver. + #opts[:pool_background_thread_interval] = kv.last / 1000.0 + when 'appName' + opts[:app_name] = kv.last else raise "Unknown option #{kv.first}" end @@ -197,6 +223,35 @@ def process_options(options) end end + def process_run_on + if run_on = @test['runOn'] + @min_server_version = run_on.detect do |doc| + doc.keys.first == 'minServerVersion' + end&.values&.first + @max_server_version = run_on.detect do |doc| + doc.keys.first == 'maxServerVersion' + end&.values&.first + + @topologies = if topologies = run_on.detect { |doc| doc.keys.first == 'topology' }['topology'] + topologies.map do |topology| + { + 'replicaset' => :replica_set, + 'single' => :single, + 'sharded' => :sharded, + 'sharded-replicaset' => :sharded, + 'load-balanced' => :load_balanced, + }[topology].tap do |v| + unless v + raise "Unknown topology #{topology}" + end + end + end + else + nil + end + end + end + # Places operations run by the non-main thread in the `thread_ops` field of the corresponding # `start` operation. def preprocess @@ -216,6 +271,7 @@ def preprocess # Represents an operation in the spec. Operations are sequential. class Operation + include RSpec::Mocks::ExampleMethods # @return [ String ] command The name of the operation to run. attr_reader :name @@ -281,6 +337,8 @@ def run(pool, state, main_thread = true) run_clear_op(state) when 'close' run_close_op(state) + when 'ready' + run_ready_op(state) else raise "invalid operation: #{name}" end @@ -368,12 +426,20 @@ def run_checkin_op(state) end def run_clear_op(state) - pool.clear(lazy: true) + RSpec::Mocks.with_temporary_scope do + allow(pool.server).to receive(:unknown?).and_return(true) + + pool.clear(lazy: true) + end end def run_close_op(state) pool.close end + + def run_ready_op(state) + pool.ready + end end end end diff --git a/spec/runners/cmap/verifier.rb b/spec/runners/cmap/verifier.rb index b17e820b14..9dac0ea709 100644 --- a/spec/runners/cmap/verifier.rb +++ b/spec/runners/cmap/verifier.rb @@ -41,9 +41,7 @@ def verify_hashes(actual, expected) end end - expected.each do |k, v| - expect(actual_modified[k]).to eq(v) - end + expect(actual_modified.slice(*expected.keys)).to eq(expected) end end end diff --git a/spec/runners/crud/test_base.rb b/spec/runners/crud/test_base.rb index e4f9a87ca8..17a872f83d 100644 --- a/spec/runners/crud/test_base.rb +++ b/spec/runners/crud/test_base.rb @@ -22,7 +22,7 @@ def setup_fail_point(client) def clear_fail_point(client) if @fail_point_command - client.use(:admin).command(BSON::Document.new(@fail_point_command).merge(mode: "off")) + ClientRegistry.instance.global_client('root_authorized').use(:admin).command(BSON::Document.new(@fail_point_command).merge(mode: "off")) end end diff --git a/spec/shared b/spec/shared index a8eadfb319..30e2f1bcab 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit a8eadfb31900d8fa5d4fa03fc4869ed5b119914b +Subproject commit 30e2f1bcabe957f76dc412dac3e91077b3cb3715 diff --git a/spec/spec_tests/cmap_spec.rb b/spec/spec_tests/cmap_spec.rb index b164866558..d2501c2c73 100644 --- a/spec/spec_tests/cmap_spec.rb +++ b/spec/spec_tests/cmap_spec.rb @@ -38,6 +38,11 @@ spec = Mongo::Cmap::Spec.new(file) context("#{spec.description} (#{file.sub(%r'.*/data/cmap/', '')})") do + unless spec.satisfied? + before(:all) do + skip "Requirements not satisfied" + end + end before do subscriber = Mrss::EventSubscriber.new @@ -66,6 +71,8 @@ rescue Mongo::Error::PoolClosedError end end + + spec.pool&.close end let!(:result) do diff --git a/spec/spec_tests/data/cmap/pool-checkout-no-idle.yml b/spec/spec_tests/data/cmap/pool-checkout-no-idle.yml index 415906bb57..4df351211c 100644 --- a/spec/spec_tests/data/cmap/pool-checkout-no-idle.yml +++ b/spec/spec_tests/data/cmap/pool-checkout-no-idle.yml @@ -3,7 +3,9 @@ style: unit description: must destroy and must not check out an idle connection if found while iterating available connections poolOptions: maxIdleTimeMS: 10 + backgroundThreadIntervalMS: -1 operations: + - name: ready - name: checkOut label: conn - name: checkIn @@ -11,6 +13,9 @@ operations: - name: wait ms: 50 - name: checkOut + - name: waitForEvent + event: ConnectionCheckedOut + count: 2 events: - type: ConnectionPoolCreated address: 42 @@ -31,5 +36,6 @@ events: address: 42 ignore: - ConnectionReady + - ConnectionPoolReady - ConnectionCreated - ConnectionCheckOutStarted diff --git a/spec/spec_tests/data/cmap/pool-checkout-no-stale.yml b/spec/spec_tests/data/cmap/pool-checkout-no-stale.yml index c434f4b065..02d827b773 100644 --- a/spec/spec_tests/data/cmap/pool-checkout-no-stale.yml +++ b/spec/spec_tests/data/cmap/pool-checkout-no-stale.yml @@ -1,13 +1,20 @@ version: 1 style: unit description: must destroy and must not check out a stale connection if found while iterating available connections +poolOptions: + backgroundThreadIntervalMS: -1 operations: + - name: ready - name: checkOut label: conn - name: checkIn connection: conn - name: clear + - name: ready - name: checkOut + - name: waitForEvent + event: ConnectionCheckedOut + count: 2 events: - type: ConnectionPoolCreated address: 42 @@ -29,5 +36,6 @@ events: address: 42 ignore: - ConnectionReady + - ConnectionPoolReady - ConnectionCreated - ConnectionCheckOutStarted diff --git a/spec/spec_tests/data/cmap/pool-clear-min-size.yml b/spec/spec_tests/data/cmap/pool-clear-min-size.yml new file mode 100644 index 0000000000..b106ff882c --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-clear-min-size.yml @@ -0,0 +1,41 @@ +version: 1 +style: unit +description: pool clear halts background minPoolSize establishments +poolOptions: + minPoolSize: 1 + backgroundThreadIntervalMS: 50 +# Remove this runOn requirement when cmap specs are adjusted for lbs +runOn: + - + topology: [ "single", "replicaset", "sharded" ] +operations: + - name: ready + - name: waitForEvent + event: ConnectionReady + count: 1 + - name: clear + # ensure no connections created after clear + - name: wait + ms: 200 + - name: ready + - name: waitForEvent + event: ConnectionReady + count: 2 +events: + - type: ConnectionPoolReady + address: 42 + - type: ConnectionCreated + address: 42 + - type: ConnectionReady + address: 42 + - type: ConnectionPoolCleared + address: 42 + - type: ConnectionPoolReady + address: 42 + - type: ConnectionCreated + address: 42 + - type: ConnectionReady + address: 42 +ignore: + - ConnectionPoolCreated + - ConnectionClosed diff --git a/spec/spec_tests/data/cmap/pool-create-min-size.yml b/spec/spec_tests/data/cmap/pool-create-min-size.yml index 6b9da01b42..a91e33f7a9 100644 --- a/spec/spec_tests/data/cmap/pool-create-min-size.yml +++ b/spec/spec_tests/data/cmap/pool-create-min-size.yml @@ -4,6 +4,10 @@ description: must be able to start a pool with minPoolSize connections poolOptions: minPoolSize: 3 operations: + # ensure no connections are created until this pool is ready + - name: wait + ms: 200 + - name: ready - name: waitForEvent event: ConnectionCreated count: 3 @@ -15,6 +19,8 @@ events: - type: ConnectionPoolCreated address: 42 options: 42 + - type: ConnectionPoolReady + address: 42 - type: ConnectionCreated connectionId: 42 address: 42 diff --git a/spec/spec_tests/retryable_reads_spec.rb b/spec/spec_tests/retryable_reads_spec.rb index 1fb849913d..d10c5a332a 100644 --- a/spec/spec_tests/retryable_reads_spec.rb +++ b/spec/spec_tests/retryable_reads_spec.rb @@ -25,6 +25,8 @@ require_no_multi_mongos define_crud_spec_tests(RETRYABLE_READS_TESTS) do |spec, req, test| + retry_test + let(:client_options) do { max_read_retries: 1, diff --git a/spec/spec_tests/sdam_spec.rb b/spec/spec_tests/sdam_spec.rb index 2c0149b581..b9484691cb 100644 --- a/spec/spec_tests/sdam_spec.rb +++ b/spec/spec_tests/sdam_spec.rb @@ -47,6 +47,10 @@ class Mongo::Server::Monitor heartbeat_frequency: 1000, connect_timeout: 0.1) end + before do + @client.reconnect if @client.closed? + end + after(:all) do @client && @client.close end diff --git a/spec/stress/connection_pool_stress_spec.rb b/spec/stress/connection_pool_stress_spec.rb index 6a3c91e4bd..4c53e5d9cf 100644 --- a/spec/stress/connection_pool_stress_spec.rb +++ b/spec/stress/connection_pool_stress_spec.rb @@ -140,65 +140,4 @@ it_behaves_like 'does not raise error' end end - - context 'when primary pool is disconnected' do - let(:threads) do - threads = operation_threads - - # thread that disconnects primary's pool - threads << Thread.new do - sleep 0.2 - server = client.cluster.next_primary - server.pool.disconnect! - end - end - - it_behaves_like 'does not raise error' - end - - context 'when all pools are disconnected' do - let(:threads) do - threads = operation_threads - - # thread that disconnects each server's pool - threads << Thread.new do - sleep 0.2 - - client.cluster.servers_list.each do |server| - if server.description.data_bearing? - server.pool.disconnect! - end - end - end - end - - it_behaves_like 'does not raise error' - end - - context 'when populator connection auth sometimes fails' do - let(:threads) { operation_threads } - - it 'does not raise error' do - server = client.cluster.next_primary - - # We only want to test connection failure on the populator; connection failures - # occurring in the check_out method should raise an error. - # The populator calls create_and_add_connection, which calls connection.connect! - # and raises if an error occurs (eg, an auth error), so in this test we - # randomly raise errors on this method. - expect(server.pool).to receive(:create_and_add_connection).at_least(:once).and_wrap_original do |m, *args| - if rand < 0.1 - raise Mongo::Error::SocketError - else - m.call(*args) - end - end - - server.pool.clear(stop_populator: false) - - expect do - threads.collect { |t| t.join } - end.not_to raise_error - end - end end diff --git a/spec/stress/connection_pool_timing_spec.rb b/spec/stress/connection_pool_timing_spec.rb index 258b1363bb..e905a0fe88 100644 --- a/spec/stress/connection_pool_timing_spec.rb +++ b/spec/stress/connection_pool_timing_spec.rb @@ -91,31 +91,6 @@ end end - context 'when clear is called periodically' do - let(:options) do - { max_pool_size: 10, min_pool_size: 5 } - end - - let(:threads) do - threads = operation_threads - threads << Thread.new do - 10.times do - sleep 0.1 - client.cluster.next_primary.pool.clear - end - end - threads - end - - it 'does not error' do - start = Mongo::Utils.monotonic_time - expect { - threads.collect { |t| t.join } - }.not_to raise_error - puts "[Connection Pool Timing] Duration when clear is called periodically: #{Mongo::Utils.monotonic_time - start}" - end - end - context 'when primary is changed, then more operations are performed' do min_server_fcv '4.2' require_topology :replica_set diff --git a/spec/support/client_registry.rb b/spec/support/client_registry.rb index 356aabba0d..f42957e55d 100644 --- a/spec/support/client_registry.rb +++ b/spec/support/client_registry.rb @@ -55,7 +55,7 @@ def initialize class << self def client_perished?(client) - if !client.cluster.connected? + if !client.cluster.connected? || client.closed? true else perished = false diff --git a/spec/support/common_shortcuts.rb b/spec/support/common_shortcuts.rb index 5d85c10cd2..e82a4e28c7 100644 --- a/spec/support/common_shortcuts.rb +++ b/spec/support/common_shortcuts.rb @@ -281,7 +281,7 @@ def make_node_shutting_down_reply def register_cluster(cluster) finalizer = lambda do |cluster| - cluster.disconnect! + cluster.close end LocalResourceRegistry.instance.register(cluster, finalizer) end @@ -289,7 +289,7 @@ def register_cluster(cluster) def register_server(server) finalizer = lambda do |server| if server.connected? - server.disconnect! + server.close end end LocalResourceRegistry.instance.register(server, finalizer) @@ -318,7 +318,15 @@ def register_pool(pool) def stop_monitoring(*clients) clients.each do |client| client.cluster.next_primary - client.cluster.disconnect! + client.cluster.close + # We have tests that stop monitoring to reduce the noise happening in + # background. These tests perform operations which requires the pools + # to function. See also RUBY-3102. + client.cluster.servers_list.each do |server| + if pool = server.pool + pool.instance_variable_set('@closed', false) + end + end end end @@ -392,5 +400,13 @@ def wait_for_snapshot(db: nil, collection: nil, client: nil) raise end end + + # Make the server usable for operations after it was marked closed. + # Used for tests that e.g. mock network operations to avoid interference + # from server monitoring. + def reset_pool(server) + server.remove_instance_variable('@pool') + server.pool.ready + end end end From 815bab52f7b46b5bcdf670702c604105d79d990a Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 4 Nov 2022 17:04:12 +0200 Subject: [PATCH 069/198] RUBY-3090 Prioritize electionId over setVersion (#2651) --- lib/mongo/cluster/sdam_flow.rb | 51 +++-- .../rs/electionId_precedence_setVersion.yml | 62 +++++++ .../data/sdam/rs/null_election_id-pre-6.0.yml | 175 ++++++++++++++++++ .../data/sdam/rs/null_election_id.yml | 38 ++-- .../sdam/rs/secondary_ignore_ok_0-pre-6.0.yml | 87 +++++++++ .../data/sdam/rs/secondary_ignore_ok_0.yml | 2 +- .../data/sdam/rs/set_version_can_rollback.yml | 101 ++++++++++ ...etversion_equal_max_without_electionid.yml | 78 ++++++++ ...ion_greaterthan_max_without_electionid.yml | 79 ++++++++ .../setversion_without_electionid-pre-6.0.yml | 79 ++++++++ .../sdam/rs/setversion_without_electionid.yml | 19 +- ..._setversion_without_electionid-pre-6.0.yml | 117 ++++++++++++ .../rs/use_setversion_without_electionid.yml | 44 ++--- 13 files changed, 869 insertions(+), 63 deletions(-) create mode 100644 spec/spec_tests/data/sdam/rs/electionId_precedence_setVersion.yml create mode 100644 spec/spec_tests/data/sdam/rs/null_election_id-pre-6.0.yml create mode 100644 spec/spec_tests/data/sdam/rs/secondary_ignore_ok_0-pre-6.0.yml create mode 100644 spec/spec_tests/data/sdam/rs/set_version_can_rollback.yml create mode 100644 spec/spec_tests/data/sdam/rs/setversion_equal_max_without_electionid.yml create mode 100644 spec/spec_tests/data/sdam/rs/setversion_greaterthan_max_without_electionid.yml create mode 100644 spec/spec_tests/data/sdam/rs/setversion_without_electionid-pre-6.0.yml create mode 100644 spec/spec_tests/data/sdam/rs/use_setversion_without_electionid-pre-6.0.yml diff --git a/lib/mongo/cluster/sdam_flow.rb b/lib/mongo/cluster/sdam_flow.rb index b63a90c029..9a1debee2e 100644 --- a/lib/mongo/cluster/sdam_flow.rb +++ b/lib/mongo/cluster/sdam_flow.rb @@ -240,17 +240,25 @@ def update_rs_from_primary return end - max_election_id = topology.new_max_election_id(updated_desc) - max_set_version = topology.new_max_set_version(updated_desc) - - if max_election_id != topology.max_election_id || - max_set_version != topology.max_set_version - then + if updated_desc.max_wire_version >= 17 @topology = Topology::ReplicaSetWithPrimary.new( topology.options.merge( - max_election_id: max_election_id, - max_set_version: max_set_version + max_election_id: updated_desc.election_id, + max_set_version: updated_desc.set_version ), topology.monitoring, self) + else + max_election_id = topology.new_max_election_id(updated_desc) + max_set_version = topology.new_max_set_version(updated_desc) + + if max_election_id != topology.max_election_id || + max_set_version != topology.max_set_version + then + @topology = Topology::ReplicaSetWithPrimary.new( + topology.options.merge( + max_election_id: max_election_id, + max_set_version: max_set_version + ), topology.monitoring, self) + end end # At this point we have accepted the updated server description @@ -576,13 +584,30 @@ def check_if_has_primary # Whether updated_desc is for a stale primary. def stale_primary? - if updated_desc.election_id && updated_desc.set_version - if topology.max_set_version && topology.max_election_id && - (updated_desc.set_version < topology.max_set_version || - (updated_desc.set_version == topology.max_set_version && - updated_desc.election_id < topology.max_election_id)) + if updated_desc.max_wire_version >= 17 + if updated_desc.election_id.nil? && !topology.max_election_id.nil? + return true + end + if updated_desc.election_id && topology.max_election_id && updated_desc.election_id < topology.max_election_id return true end + if updated_desc.election_id == topology.max_election_id + if updated_desc.set_version.nil? && !topology.max_set_version.nil? + return true + end + if updated_desc.set_version && topology.max_set_version && updated_desc.set_version < topology.max_set_version + return true + end + end + else + if updated_desc.election_id && updated_desc.set_version + if topology.max_set_version && topology.max_election_id && + (updated_desc.set_version < topology.max_set_version || + (updated_desc.set_version == topology.max_set_version && + updated_desc.election_id < topology.max_election_id)) + return true + end + end end false end diff --git a/spec/spec_tests/data/sdam/rs/electionId_precedence_setVersion.yml b/spec/spec_tests/data/sdam/rs/electionId_precedence_setVersion.yml new file mode 100644 index 0000000000..4b9563fcfd --- /dev/null +++ b/spec/spec_tests/data/sdam/rs/electionId_precedence_setVersion.yml @@ -0,0 +1,62 @@ +description: ElectionId is considered higher precedence than setVersion +uri: "mongodb://a/?replicaSet=rs" +phases: + - responses: + - - "a:27017" + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - "a:27017" + - "b:27017" + setName: rs + setVersion: 1 + electionId: + $oid: "000000000000000000000001" + minWireVersion: 0 + maxWireVersion: 17 + - - "b:27017" + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - "a:27017" + - "b:27017" + setName: rs + setVersion: 2 # Even though "B" reports the newer setVersion, "A" will report the newer electionId which should allow it to remain the primary + electionId: + $oid: "000000000000000000000001" + minWireVersion: 0 + maxWireVersion: 17 + - - "a:27017" + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - "a:27017" + - "b:27017" + setName: rs + setVersion: 1 + electionId: + $oid: "000000000000000000000002" + minWireVersion: 0 + maxWireVersion: 17 + outcome: + servers: + "a:27017": + type: RSPrimary + setName: rs + setVersion: 1 + electionId: + $oid: "000000000000000000000002" + "b:27017": + type: Unknown + setName: null + setVersion: null + electionId: null + topologyType: ReplicaSetWithPrimary + logicalSessionTimeoutMinutes: null + setName: rs + maxSetVersion: 1 + maxElectionId: + $oid: "000000000000000000000002" diff --git a/spec/spec_tests/data/sdam/rs/null_election_id-pre-6.0.yml b/spec/spec_tests/data/sdam/rs/null_election_id-pre-6.0.yml new file mode 100644 index 0000000000..7e9bd55021 --- /dev/null +++ b/spec/spec_tests/data/sdam/rs/null_election_id-pre-6.0.yml @@ -0,0 +1,175 @@ +description: "Pre 6.0 Primaries with and without electionIds" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A has no electionId. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setVersion: 1, + setName: "rs", + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + } + }, + + # B is elected, it has an electionId. + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"}, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"} + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + }, + + # A still claims to be primary, no electionId, we have to trust it. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setVersion: 1, + setName: "rs", + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + }, + + # But we remember B's electionId, so when we finally hear from C + # claiming it is primary, we ignore it due to its outdated electionId + { + responses: [ + ["c:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000001"}, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + outcome: { + servers: { + # Still primary. + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + } +] diff --git a/spec/spec_tests/data/sdam/rs/null_election_id.yml b/spec/spec_tests/data/sdam/rs/null_election_id.yml index 7de496cd79..54c1af272a 100644 --- a/spec/spec_tests/data/sdam/rs/null_election_id.yml +++ b/spec/spec_tests/data/sdam/rs/null_election_id.yml @@ -15,7 +15,7 @@ phases: [ setVersion: 1, setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -57,7 +57,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -88,7 +88,7 @@ phases: [ } }, - # A still claims to be primary, no electionId, we have to trust it. + # A still claims to be primary, no electionId, we don't trust it. { responses: [ ["a:27017", { @@ -99,22 +99,24 @@ phases: [ setVersion: 1, setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { + # A ignored for missing electionId "a:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 1, - electionId: - }, - "b:27017": { type: "Unknown", setName: , + setVersion: , electionId: }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: { "$oid": "000000000000000000000002" } + }, "c:27017": { type: "Unknown", setName: , @@ -142,23 +144,23 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { - # Still primary. "a:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 1, - electionId: - }, - "b:27017": { type: "Unknown", setName: , + setVersion: , electionId: }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: { "$oid": "000000000000000000000002" } + }, "c:27017": { type: "Unknown", setName: , diff --git a/spec/spec_tests/data/sdam/rs/secondary_ignore_ok_0-pre-6.0.yml b/spec/spec_tests/data/sdam/rs/secondary_ignore_ok_0-pre-6.0.yml new file mode 100644 index 0000000000..de9048d401 --- /dev/null +++ b/spec/spec_tests/data/sdam/rs/secondary_ignore_ok_0-pre-6.0.yml @@ -0,0 +1,87 @@ +description: "Pre 6.0 New primary" + +uri: "mongodb://a,b/?replicaSet=rs" + +phases: [ + + { + responses: [ + + ["a:27017", { + + ok: 1, + helloOk: true, + isWritablePrimary: true, + setName: "rs", + hosts: ["a:27017", "b:27017"], + minWireVersion: 0, + maxWireVersion: 6 + }], + ["b:27017", { + + ok: 1, + helloOk: true, + isWritablePrimary: false, + secondary: true, + setName: "rs", + hosts: ["a:27017", "b:27017"], + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + + servers: { + + "a:27017": { + + type: "RSPrimary", + setName: "rs" + }, + + "b:27017": { + + type: "RSSecondary", + setName: "rs" + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs" + } + }, + + { + responses: [ + + ["b:27017", { + + ok: 0, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + + servers: { + "a:27017": { + + type: "RSPrimary", + setName: "rs" + }, + + "b:27017": { + + type: "Unknown", + setName: + } + + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs" + } + } +] diff --git a/spec/spec_tests/data/sdam/rs/secondary_ignore_ok_0.yml b/spec/spec_tests/data/sdam/rs/secondary_ignore_ok_0.yml index c7e605d662..d94fafaf99 100644 --- a/spec/spec_tests/data/sdam/rs/secondary_ignore_ok_0.yml +++ b/spec/spec_tests/data/sdam/rs/secondary_ignore_ok_0.yml @@ -1,4 +1,4 @@ -description: "New primary" +description: "Secondary ignored when ok is zero" uri: "mongodb://a,b/?replicaSet=rs" diff --git a/spec/spec_tests/data/sdam/rs/set_version_can_rollback.yml b/spec/spec_tests/data/sdam/rs/set_version_can_rollback.yml new file mode 100644 index 0000000000..c30a8660ec --- /dev/null +++ b/spec/spec_tests/data/sdam/rs/set_version_can_rollback.yml @@ -0,0 +1,101 @@ +description: Set version rolls back after new primary with higher election Id +uri: mongodb://a/?replicaSet=rs +phases: + - responses: + - - a:27017 + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - a:27017 + - b:27017 + setName: rs + setVersion: 2 + electionId: + $oid: '000000000000000000000001' + minWireVersion: 0 + maxWireVersion: 17 + outcome: + servers: + a:27017: + type: RSPrimary + setName: rs + setVersion: 2 + electionId: + $oid: '000000000000000000000001' + b:27017: + type: Unknown + setName: null + electionId: null + topologyType: ReplicaSetWithPrimary + logicalSessionTimeoutMinutes: null + setName: rs + maxSetVersion: 2 + maxElectionId: + $oid: '000000000000000000000001' + - # Response from new primary with newer election Id + responses: + - - b:27017 + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - a:27017 + - b:27017 + setName: rs + setVersion: 1 + electionId: + $oid: '000000000000000000000002' + minWireVersion: 0 + maxWireVersion: 17 + outcome: + servers: + a:27017: + type: Unknown + setName: null + electionId: null + b:27017: + type: RSPrimary + setName: rs + setVersion: 1 + electionId: + $oid: '000000000000000000000002' + topologyType: ReplicaSetWithPrimary + logicalSessionTimeoutMinutes: null + setName: rs + maxSetVersion: 1 + maxElectionId: + $oid: '000000000000000000000002' + - # Response from stale primary + responses: + - - a:27017 + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - a:27017 + - b:27017 + setName: rs + setVersion: 2 + electionId: + $oid: '000000000000000000000001' + minWireVersion: 0 + maxWireVersion: 17 + outcome: + servers: + a:27017: + type: Unknown + setName: null + electionId: null + b:27017: + type: RSPrimary + setName: rs + setVersion: 1 + electionId: + $oid: '000000000000000000000002' + topologyType: ReplicaSetWithPrimary + logicalSessionTimeoutMinutes: null + setName: rs + maxSetVersion: 1 + maxElectionId: + $oid: '000000000000000000000002' diff --git a/spec/spec_tests/data/sdam/rs/setversion_equal_max_without_electionid.yml b/spec/spec_tests/data/sdam/rs/setversion_equal_max_without_electionid.yml new file mode 100644 index 0000000000..3733a12e56 --- /dev/null +++ b/spec/spec_tests/data/sdam/rs/setversion_equal_max_without_electionid.yml @@ -0,0 +1,78 @@ +description: "setVersion version that is equal is treated the same as greater than if there is no electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A is discovered and tells us about B. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + minWireVersion: 0, + maxWireVersion: 17 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + } + }, + + # B is elected, its setVersion is older so it is stale + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + minWireVersion: 0, + maxWireVersion: 17 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, # Max is still 1, there wasn't an actual larger setVersion seen + } + } +] diff --git a/spec/spec_tests/data/sdam/rs/setversion_greaterthan_max_without_electionid.yml b/spec/spec_tests/data/sdam/rs/setversion_greaterthan_max_without_electionid.yml new file mode 100644 index 0000000000..3252e0f611 --- /dev/null +++ b/spec/spec_tests/data/sdam/rs/setversion_greaterthan_max_without_electionid.yml @@ -0,0 +1,79 @@ +description: "setVersion that is greater than maxSetVersion is used if there is no electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A is discovered and tells us about B. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + minWireVersion: 0, + maxWireVersion: 17 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + } + }, + + # B is elected, its setVersion is greater than our current maxSetVersion + # B is primary, A is marked Unknown + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 2, + minWireVersion: 0, + maxWireVersion: 17 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2, + electionId: + }, + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + } + } +] diff --git a/spec/spec_tests/data/sdam/rs/setversion_without_electionid-pre-6.0.yml b/spec/spec_tests/data/sdam/rs/setversion_without_electionid-pre-6.0.yml new file mode 100644 index 0000000000..54bfa4d256 --- /dev/null +++ b/spec/spec_tests/data/sdam/rs/setversion_without_electionid-pre-6.0.yml @@ -0,0 +1,79 @@ +description: "Pre 6.0 setVersion is ignored if there is no electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A is discovered and tells us about B. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 2, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2 , + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + } + }, + + # B is elected, its setVersion is older but we believe it anyway, because + # setVersion is only used in conjunction with electionId. + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + } + } +] diff --git a/spec/spec_tests/data/sdam/rs/setversion_without_electionid.yml b/spec/spec_tests/data/sdam/rs/setversion_without_electionid.yml index 55c841f24b..04992929a1 100644 --- a/spec/spec_tests/data/sdam/rs/setversion_without_electionid.yml +++ b/spec/spec_tests/data/sdam/rs/setversion_without_electionid.yml @@ -1,4 +1,4 @@ -description: "setVersion is ignored if there is no electionId" +description: "setVersion that is less than maxSetVersion is ignored if there is no electionId" uri: "mongodb://a/?replicaSet=rs" @@ -15,7 +15,7 @@ phases: [ setName: "rs", setVersion: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -40,8 +40,7 @@ phases: [ } }, - # B is elected, its setVersion is older but we believe it anyway, because - # setVersion is only used in conjunction with electionId. + # B is elected, its setVersion is older so it is stale { responses: [ ["b:27017", { @@ -52,21 +51,21 @@ phases: [ setName: "rs", setVersion: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { "a:27017": { - type: "Unknown", - setName: , + type: "RSPrimary", + setName: "rs", + setVersion: 2 , electionId: }, "b:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 1, + type: "Unknown", + setName: , electionId: } }, diff --git a/spec/spec_tests/data/sdam/rs/use_setversion_without_electionid-pre-6.0.yml b/spec/spec_tests/data/sdam/rs/use_setversion_without_electionid-pre-6.0.yml new file mode 100644 index 0000000000..a0434eb45c --- /dev/null +++ b/spec/spec_tests/data/sdam/rs/use_setversion_without_electionid-pre-6.0.yml @@ -0,0 +1,117 @@ +description: "Pre 6.0 Record max setVersion, even from primary without electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A has setVersion and electionId, tells us about B. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000001"}, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000001"} + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + }, + + # Reconfig the set and elect B, it has a new setVersion but no electionId. + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 2, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2 + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + }, + + # Delayed response from A, reporting its reelection. Its setVersion shows + # the election preceded B's so we ignore it. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"}, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2 + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + } +] diff --git a/spec/spec_tests/data/sdam/rs/use_setversion_without_electionid.yml b/spec/spec_tests/data/sdam/rs/use_setversion_without_electionid.yml index 300f9d9304..68c88bc503 100644 --- a/spec/spec_tests/data/sdam/rs/use_setversion_without_electionid.yml +++ b/spec/spec_tests/data/sdam/rs/use_setversion_without_electionid.yml @@ -4,7 +4,7 @@ uri: "mongodb://a/?replicaSet=rs" phases: [ - # Primary A has setVersion and electionId, tells us about B. + # Primary A has electionId and setVersion, tells us about B. { responses: [ ["a:27017", { @@ -16,7 +16,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -42,7 +42,7 @@ phases: [ } }, - # Reconfig the set and elect B, it has a new setVersion but no electionId. + # Reconfig, B reports as primary, B is missing the electionId but reports setVersion { responses: [ ["b:27017", { @@ -53,33 +53,34 @@ phases: [ setName: "rs", setVersion: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { - "a:27017": { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: { "$oid": "000000000000000000000001" } + }, + "b:27017": { type: "Unknown", setName: , electionId: - }, - "b:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 2 } }, topologyType: "ReplicaSetWithPrimary", logicalSessionTimeoutMinutes: null, setName: "rs", - maxSetVersion: 2, + maxSetVersion: 1, maxElectionId: {"$oid": "000000000000000000000001"}, } }, - # Delayed response from A, reporting its reelection. Its setVersion shows - # the election preceded B's so we ignore it. + # A reports as primary, A has been reelection (electionId greater than our recorded maxElectionId). + # A's setVersion is less than our maxSetVersion, but electionId takes precedence so B's primary claim is ignored { responses: [ ["a:27017", { @@ -91,27 +92,28 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"} + }, + "b:27017":{ type: "Unknown", setName: , electionId: - }, - "b:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 2 } }, topologyType: "ReplicaSetWithPrimary", logicalSessionTimeoutMinutes: null, setName: "rs", - maxSetVersion: 2, - maxElectionId: {"$oid": "000000000000000000000001"}, + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, } } ] From cabe95bba974253ed7873f37f2eb5be4d47437b2 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Tue, 8 Nov 2022 09:43:09 -0500 Subject: [PATCH 070/198] RUBY-3177 Update valid-pass tests (#2655) * add modify coll test * commit working tests * fix crud test * update estimated document count * RUBY-3177 fix tests * fix tests --- spec/runners/unified/assertions.rb | 152 +++++++++--------- .../unified/change_stream_operations.rb | 5 +- spec/runners/unified/ddl_operations.rb | 5 +- spec/runners/unified/support_operations.rb | 12 ++ spec/runners/unified/test.rb | 10 ++ .../modifyCollection-errorResponse.yml | 59 +++++++ .../crud_unified/estimatedDocumentCount.yml | 44 +++++ .../assertNumberConnectionsCheckedOut.yml | 17 ++ .../valid-pass/entity-client-cmap-events.yml | 40 +++++ .../entity-client-storeEventsAsEntities.yml | 37 +++++ .../expectedEventsForClient-eventType.yml | 66 ++++++++ .../valid-pass/ignoreResultAndError.yml | 34 ++++ .../unified/valid-pass/poc-change-streams.yml | 18 +++ .../data/unified/valid-pass/poc-crud.yml | 30 ++-- .../valid-pass/poc-retryable-reads.yml | 6 +- .../valid-pass/poc-retryable-writes.yml | 4 +- 16 files changed, 445 insertions(+), 94 deletions(-) create mode 100644 spec/spec_tests/data/collection_management/modifyCollection-errorResponse.yml create mode 100644 spec/spec_tests/data/unified/valid-pass/assertNumberConnectionsCheckedOut.yml create mode 100644 spec/spec_tests/data/unified/valid-pass/entity-client-cmap-events.yml create mode 100644 spec/spec_tests/data/unified/valid-pass/entity-client-storeEventsAsEntities.yml create mode 100644 spec/spec_tests/data/unified/valid-pass/expectedEventsForClient-eventType.yml create mode 100644 spec/spec_tests/data/unified/valid-pass/ignoreResultAndError.yml diff --git a/spec/runners/unified/assertions.rb b/spec/runners/unified/assertions.rb index a0136a9c26..79c55517d9 100644 --- a/spec/runners/unified/assertions.rb +++ b/spec/runners/unified/assertions.rb @@ -8,102 +8,106 @@ module Assertions def assert_result_matches(actual, expected) if Hash === expected - use_all(expected, 'expected result', expected) do |expected| - %w(deleted inserted matched modified upserted).each do |k| - if count = expected.use("#{k}Count") - if Hash === count || count > 0 - actual_count = case actual - when Mongo::BulkWrite::Result, Mongo::Operation::Delete::Result, Mongo::Operation::Update::Result - actual.send("#{k}_count") - else - actual["n_#{k}"] + if expected.keys == ["$$unsetOrMatches"] + assert_result_matches(actual, UsingHash[expected.values.first]) + else + use_all(expected, 'expected result', expected) do |expected| + %w(deleted inserted matched modified upserted).each do |k| + if count = expected.use("#{k}Count") + if Hash === count || count > 0 + actual_count = case actual + when Mongo::BulkWrite::Result, Mongo::Operation::Delete::Result, Mongo::Operation::Update::Result + actual.send("#{k}_count") + else + actual["n_#{k}"] + end + assert_value_matches(actual_count, count, "#{k} count") end - assert_value_matches(actual_count, count, "#{k} count") end end - end - %w(inserted upserted).each do |k| - expected_v = expected.use("#{k}Ids") - next unless expected_v - actual_v = case actual - when Mongo::BulkWrite::Result, Mongo::Operation::Update::Result - # Ruby driver returns inserted ids as an array of ids. - # The yaml file specifies them as a map from operation. - if Hash === expected_v && expected_v.keys == %w($$unsetOrMatches) - expected_v = expected_v.values.first.values - elsif Hash === expected_v - expected_v = expected_v.values - end - actual.send("#{k}_ids") - else - actual["#{k}_ids"] - end - if expected_v - if expected_v.empty? - if actual_v && !actual_v.empty? - raise Error::ResultMismatch, "Actual not empty" + %w(inserted upserted).each do |k| + expected_v = expected.use("#{k}Ids") + next unless expected_v + actual_v = case actual + when Mongo::BulkWrite::Result, Mongo::Operation::Update::Result + # Ruby driver returns inserted ids as an array of ids. + # The yaml file specifies them as a map from operation. + if Hash === expected_v && expected_v.keys == %w($$unsetOrMatches) + expected_v = expected_v.values.first.values + elsif Hash === expected_v + expected_v = expected_v.values end + actual.send("#{k}_ids") else - if actual_v != expected_v - raise Error::ResultMismatch, "Mismatch: actual #{actual_v}, expected #{expected_v}" + actual["#{k}_ids"] + end + if expected_v + if expected_v.empty? + if actual_v && !actual_v.empty? + raise Error::ResultMismatch, "Actual not empty" + end + else + if actual_v != expected_v + raise Error::ResultMismatch, "Mismatch: actual #{actual_v}, expected #{expected_v}" + end end end end - end - %w(acknowledged).each do |k| - expected_v = expected.use(k) - next unless expected_v - actual_v = case actual - when Mongo::BulkWrite::Result, Mongo::Operation::Result - if Hash === expected_v && expected_v.keys == %w($$unsetOrMatches) - expected_v = expected_v.values.first - end - actual.send("#{k}?") - else - actual[k] - end - if expected_v - if expected_v.empty? - if actual_v && !actual_v.empty? - raise Error::ResultMismatch, "Actual not empty" + %w(acknowledged).each do |k| + expected_v = expected.use(k) + next unless expected_v + actual_v = case actual + when Mongo::BulkWrite::Result, Mongo::Operation::Result + if Hash === expected_v && expected_v.keys == %w($$unsetOrMatches) + expected_v = expected_v.values.first end + actual.send("#{k}?") else - if actual_v != expected_v - raise Error::ResultMismatch, "Mismatch: actual #{actual_v}, expected #{expected_v}" + actual[k] + end + if expected_v + if expected_v.empty? + if actual_v && !actual_v.empty? + raise Error::ResultMismatch, "Actual not empty" + end + else + if actual_v != expected_v + raise Error::ResultMismatch, "Mismatch: actual #{actual_v}, expected #{expected_v}" + end end end end - end - %w(bulkWriteResult).each do |k| - expected_v = expected.use(k) - next unless expected_v - actual_v = case actual - when Mongo::Crypt::RewrapManyDataKeyResult - actual.send(Utils.underscore(k)) - else - raise Error::ResultMismatch, "Mismatch: actual #{actual_v}, expected #{expected_v}" - end - if expected_v - if expected_v.empty? - if actual_v && !actual_v.empty? - raise Error::ResultMismatch, "Actual not empty" - end + %w(bulkWriteResult).each do |k| + expected_v = expected.use(k) + next unless expected_v + actual_v = case actual + when Mongo::Crypt::RewrapManyDataKeyResult + actual.send(Utils.underscore(k)) else - %w(deleted inserted matched modified upserted).each do |k| - if count = expected_v.use("#{k}Count") - if Hash === count || count > 0 - actual_count = actual_v.send("#{k}_count") - assert_value_matches(actual_count, count, "#{k} count") + raise Error::ResultMismatch, "Mismatch: actual #{actual_v}, expected #{expected_v}" + end + if expected_v + if expected_v.empty? + if actual_v && !actual_v.empty? + raise Error::ResultMismatch, "Actual not empty" + end + else + %w(deleted inserted matched modified upserted).each do |k| + if count = expected_v.use("#{k}Count") + if Hash === count || count > 0 + actual_count = actual_v.send("#{k}_count") + assert_value_matches(actual_count, count, "#{k} count") + end end end end end end + assert_matches(actual, expected, 'result') + expected.clear end - assert_matches(actual, expected, 'result') - expected.clear end else assert_matches(actual, expected, 'result') diff --git a/spec/runners/unified/change_stream_operations.rb b/spec/runners/unified/change_stream_operations.rb index ffe1cb98b3..1b4f890a16 100644 --- a/spec/runners/unified/change_stream_operations.rb +++ b/spec/runners/unified/change_stream_operations.rb @@ -27,8 +27,9 @@ def create_change_stream(op) opts[:show_expanded_events] = args.use!('showExpandedEvents') end cs = object.watch(pipeline, **opts) - name = op.use!('saveResultAsEntity') - entities.set(:change_stream, name, cs) + if name = op.use('saveResultAsEntity') + entities.set(:change_stream, name, cs) + end end end diff --git a/spec/runners/unified/ddl_operations.rb b/spec/runners/unified/ddl_operations.rb index affa4410f7..06cfcf9bba 100644 --- a/spec/runners/unified/ddl_operations.rb +++ b/spec/runners/unified/ddl_operations.rb @@ -39,6 +39,9 @@ def create_collection(op) if view_on = args.use('viewOn') collection_opts[:view_on] = view_on end + if pipeline = args.use('pipeline') + collection_opts[:pipeline] = pipeline + end database[args.use!('collection'), collection_opts].create(**opts) end end @@ -125,7 +128,7 @@ def create_index(op) collection.indexes.create_one( args.use!('keys'), - name: args.use!('name'), + name: args.use('name'), **opts, ) end diff --git a/spec/runners/unified/support_operations.rb b/spec/runners/unified/support_operations.rb index 9e5f7fe8b0..e52666fc85 100644 --- a/spec/runners/unified/support_operations.rb +++ b/spec/runners/unified/support_operations.rb @@ -293,6 +293,18 @@ def select_events(subscriber, event) end end + def assert_number_connections_checked_out(op) + consume_test_runner(op) + use_arguments(op) do |args| + client = entities.get(:client, args.use!('client')) + connections = args.use!('connections') + actual_c = client.cluster.servers.map(&:pool_internal).compact.sum do |p| + p.instance_variable_get(:@checked_out_connections).length + end + assert_eq(actual_c, connections, "Expected client #{client} to have #{connections} checked out connections but there are #{actual_c}.") + end + end + private def assert_no_arguments(op) diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index e5d973d29f..566c14250c 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -446,6 +446,16 @@ def execute_operation(op) else raise Error::ErrorMismatch, "Expected exception but none was raised" end + elsif op.use('ignoreResultAndError') + unless respond_to?(method_name) + raise Error::UnsupportedOperation, "Mongo Ruby Driver does not support #{name.to_s}" + end + + begin + send(method_name, op) + # We can possibly rescue more errors here, add as needed. + rescue Mongo::Error + end else unless respond_to?(method_name, true) raise Error::UnsupportedOperation, "Mongo Ruby Driver does not support #{name.to_s}" diff --git a/spec/spec_tests/data/collection_management/modifyCollection-errorResponse.yml b/spec/spec_tests/data/collection_management/modifyCollection-errorResponse.yml new file mode 100644 index 0000000000..e61a01211c --- /dev/null +++ b/spec/spec_tests/data/collection_management/modifyCollection-errorResponse.yml @@ -0,0 +1,59 @@ +description: "modifyCollection-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name collMod-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 1 } + - { _id: 2, x: 1 } + +tests: + - description: "modifyCollection prepareUnique violations are accessible" + runOnRequirements: + - minServerVersion: "5.2" # SERVER-61158 + operations: + - name: createIndex + object: *collection0 + arguments: + keys: { x: 1 } + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + index: + keyPattern: { x: 1 } + prepareUnique: true + - name: insertOne + object: *collection0 + arguments: + document: { _id: 3, x: 1 } + expectError: + errorCode: 11000 # DuplicateKey + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + index: + keyPattern: { x: 1 } + unique: true + expectError: + isClientError: false + errorCode: 359 # CannotConvertIndexToUnique + errorResponse: + violations: + - { ids: [ 1, 2 ] } diff --git a/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml b/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml index 786b23bc95..12f33cc7e5 100644 --- a/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +++ b/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml @@ -21,6 +21,10 @@ createEntities: id: &collection1 collection1 database: *database0 collectionName: &collection1Name coll1 + - collection: + id: &collection0View collection0View + database: *database0 + collectionName: &collection0ViewName coll0view initialData: - collectionName: *collection0Name @@ -135,3 +139,43 @@ tests: count: *collection0Name commandName: count databaseName: *database0Name + + - description: "estimatedDocumentCount works correctly on views" + # viewOn option was added to the create command in 3.4 + runOnRequirements: + - minServerVersion: "3.4.0" + operations: + - name: dropCollection + object: *database0 + arguments: + collection: *collection0ViewName + - name: createCollection + object: *database0 + arguments: + collection: *collection0ViewName + viewOn: *collection0Name + pipeline: &pipeline + - { $match: { _id: { $gt: 1 } } } + - name: estimatedDocumentCount + object: *collection0View + expectResult: 2 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + drop: *collection0ViewName + commandName: drop + databaseName: *database0Name + - commandStartedEvent: + command: + create: *collection0ViewName + viewOn: *collection0Name + pipeline: *pipeline + commandName: create + databaseName: *database0Name + - commandStartedEvent: + command: + count: *collection0ViewName + commandName: count + databaseName: *database0Name diff --git a/spec/spec_tests/data/unified/valid-pass/assertNumberConnectionsCheckedOut.yml b/spec/spec_tests/data/unified/valid-pass/assertNumberConnectionsCheckedOut.yml new file mode 100644 index 0000000000..603552fc20 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/assertNumberConnectionsCheckedOut.yml @@ -0,0 +1,17 @@ +description: assertNumberConnectionsCheckedOut + +schemaVersion: '1.3' + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + +tests: + - description: basic assertion succeeds + operations: + - name: assertNumberConnectionsCheckedOut + object: testRunner + arguments: + client: *client0 + connections: 0 diff --git a/spec/spec_tests/data/unified/valid-pass/entity-client-cmap-events.yml b/spec/spec_tests/data/unified/valid-pass/entity-client-cmap-events.yml new file mode 100644 index 0000000000..277134551c --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/entity-client-cmap-events.yml @@ -0,0 +1,40 @@ +description: entity-client-cmap-events + +schemaVersion: '1.3' + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: + - connectionReadyEvent + - connectionCheckedOutEvent + - connectionCheckedInEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - description: events are captured during an operation + operations: + - name: insertOne + object: *collection0 + arguments: + document: { x: 1 } + expectEvents: + - client: *client0 + eventType: cmap + events: + - connectionReadyEvent: {} + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} diff --git a/spec/spec_tests/data/unified/valid-pass/entity-client-storeEventsAsEntities.yml b/spec/spec_tests/data/unified/valid-pass/entity-client-storeEventsAsEntities.yml new file mode 100644 index 0000000000..52a9e0ddc7 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/entity-client-storeEventsAsEntities.yml @@ -0,0 +1,37 @@ +description: "entity-client-storeEventsAsEntities" + +schemaVersion: "1.2" + +createEntities: + - client: + id: &client0 client0 + storeEventsAsEntities: + - id: client0_events + events: ["CommandStartedEvent", "CommandSucceededEvent", "CommandFailedEvent"] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + +tests: + # Note: this test does not assert that the events are actually saved to the + # entity since there is presently no assertion syntax to do so. We are only + # asserting that the test executes successfully. + - description: "storeEventsAsEntities captures events" + operations: + - name: find + object: *collection0 + arguments: + filter: {} + expectResult: + - { _id: 1, x: 11 } diff --git a/spec/spec_tests/data/unified/valid-pass/expectedEventsForClient-eventType.yml b/spec/spec_tests/data/unified/valid-pass/expectedEventsForClient-eventType.yml new file mode 100644 index 0000000000..e437c80489 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/expectedEventsForClient-eventType.yml @@ -0,0 +1,66 @@ +description: expectedEventsForClient-eventType + +schemaVersion: '1.3' + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: + - commandStartedEvent + - connectionReadyEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - description: eventType can be set to command and cmap + operations: + - name: insertOne + object: *collection0 + arguments: + document: &insertDocument { _id: 1 } + expectEvents: + - client: *client0 + eventType: command + events: + - commandStartedEvent: + command: + insert: *collection0Name + documents: + - *insertDocument + commandName: insert + - client: *client0 + eventType: cmap + events: + - connectionReadyEvent: {} + + - description: eventType defaults to command if unset + operations: + - name: insertOne + object: *collection0 + arguments: + document: *insertDocument + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection0Name + documents: + - *insertDocument + commandName: insert + - client: *client0 + eventType: cmap + events: + - connectionReadyEvent: {} diff --git a/spec/spec_tests/data/unified/valid-pass/ignoreResultAndError.yml b/spec/spec_tests/data/unified/valid-pass/ignoreResultAndError.yml new file mode 100644 index 0000000000..847e296e72 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/ignoreResultAndError.yml @@ -0,0 +1,34 @@ +description: ignoreResultAndError + +schemaVersion: '1.3' + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - description: operation errors are ignored if ignoreResultAndError is true + operations: + - name: insertOne + object: *collection0 + arguments: + document: &insertDocument { _id: 1 } + - name: insertOne + object: *collection0 + arguments: + document: *insertDocument + ignoreResultAndError: true diff --git a/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml b/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml index b066cf0b89..a7daafceb7 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml @@ -59,6 +59,24 @@ initialData: documents: [] tests: + - description: "saveResultAsEntity is optional for createChangeStream" + runOnRequirements: + - minServerVersion: "3.8.0" + topologies: [ replicaset ] + operations: + - name: createChangeStream + object: *client0 + arguments: + pipeline: [] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: 1 + commandName: aggregate + databaseName: admin + - description: "Executing a watch helper on a MongoClient results in notifications for changes to all collections in all databases in the cluster." runOnRequirements: - minServerVersion: "3.8.0" diff --git a/spec/spec_tests/data/unified/valid-pass/poc-crud.yml b/spec/spec_tests/data/unified/valid-pass/poc-crud.yml index 4361ca6d42..b7d05d75af 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-crud.yml @@ -1,6 +1,6 @@ description: "poc-crud" -schemaVersion: "1.0" +schemaVersion: "1.4" createEntities: - client: @@ -101,16 +101,19 @@ tests: ordered: false expectError: expectResult: - deletedCount: 0 - insertedCount: 2 - # Since the map of insertedIds is generated before execution it - # could indicate inserts that did not actually succeed. We omit - # this field rather than expect drivers to provide an accurate - # map filtered by write errors. - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } + # insertMany throws BulkWriteException, which may optionally include + # an intermediary BulkWriteResult + $$unsetOrMatches: + deletedCount: 0 + insertedCount: 2 + # Since the map of insertedIds is generated before execution it + # could indicate inserts that did not actually succeed. We omit + # this field rather than expect drivers to provide an accurate + # map filtered by write errors. + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } outcome: - collectionName: *collection1Name databaseName: *database0Name @@ -120,7 +123,6 @@ tests: - { _id: 3, x: 33 } - description: "ReplaceOne prohibits atomic modifiers" - skipReason: RUBY-2437 operations: - name: replaceOne object: *collection1 @@ -142,6 +144,7 @@ tests: runOnRequirements: - minServerVersion: "4.1.0" topologies: [ replicaset, sharded-replicaset ] + serverless: "forbid" operations: - name: aggregate object: *collection2 @@ -171,6 +174,9 @@ tests: - description: "Aggregate with $listLocalSessions" runOnRequirements: - minServerVersion: "3.6.0" + # serverless does not support either of the current database-level aggregation stages ($listLocalSessions and + # $currentOp) + serverless: forbid operations: - name: aggregate object: *database1 diff --git a/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml b/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml index c1ea7ec696..b06b437f61 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml @@ -93,7 +93,7 @@ tests: closeConnection: true # Find options and expected result changed to use common initialData - name: find - object: collection0 + object: *collection0 arguments: filter: {} sort: { _id: 1 } @@ -127,7 +127,7 @@ tests: failCommands: [ find ] closeConnection: true - name: find - object: collection1 # client uses retryReads=false + object: *collection1 # client uses retryReads=false arguments: filter: {} # Other arguments in the original test are not relevant @@ -154,7 +154,7 @@ tests: failCommands: [ find ] closeConnection: true - name: find - object: collection0 + object: *collection0 arguments: filter: {} # Other arguments in the original test are not relevant diff --git a/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml b/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml index d8d5157b28..fa882e2836 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml @@ -141,8 +141,8 @@ tests: - { _id: 4, x: 44 } ordered: true expectResult: - insertedCount: { $$unsetOrMatches: 2 } - insertedIds: { $$unsetOrMatches: { 0: 3, 1: 4 } } + # InsertManyResult is optional because all of its fields are optional + $$unsetOrMatches: { insertedIds: { $$unsetOrMatches: { 0: 3, 1: 4 } } } outcome: - collectionName: *collectionName databaseName: *databaseName From fe5a87fedf3c977a5e893fb47ee08904e413b36c Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 8 Nov 2022 16:05:14 +0100 Subject: [PATCH 071/198] RUBY-3130 Test no mcd connected if crypt shared (#2657) --- .../omit_mongocryptd_client_prose_spec.rb | 73 +++++++++++++++++++ spec/support/crypt.rb | 1 + 2 files changed, 74 insertions(+) create mode 100644 spec/integration/client_side_encryption/omit_mongocryptd_client_prose_spec.rb diff --git a/spec/integration/client_side_encryption/omit_mongocryptd_client_prose_spec.rb b/spec/integration/client_side_encryption/omit_mongocryptd_client_prose_spec.rb new file mode 100644 index 0000000000..e8204d4a2b --- /dev/null +++ b/spec/integration/client_side_encryption/omit_mongocryptd_client_prose_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'spec_helper' + +describe 'Do not connect to mongocryptd if shared library is loaded' do + require_libmongocrypt + require_enterprise + + include_context 'define shared FLE helpers' + include_context 'with local kms_providers' + + let(:extra_options) do + { + crypt_shared_lib_path: SpecConfig.instance.crypt_shared_lib_path, + mongocryptd_uri: "mongodb://localhost:27777" + } + end + + let(:encryption_client) do + new_local_client( + SpecConfig.instance.addresses, + SpecConfig.instance.test_options.merge( + auto_encryption_options: { + kms_providers: kms_providers, + kms_tls_options: kms_tls_options, + key_vault_namespace: key_vault_namespace, + schema_map: { "auto_encryption.users" => schema_map }, + extra_options: extra_options, + }, + database: 'auto_encryption', + ), + ) + end + + let!(:connect_attempt) do + Class.new do + def lock + @lock ||= Mutex.new + end + + def done? + lock.synchronize do + !!@done + end + end + + def done! + lock.synchronize do + @done = true + end + end + end.new + end + + let!(:listener) do + Thread.new do + TCPServer.new(27777).accept + connect_attempt.done! + end + end + + after do + listener.exit + end + + it 'does not try to connect to mongocryptd' do + skip 'This test requires crypt shared library' unless SpecConfig.instance.crypt_shared_lib_path + + encryption_client[:users].insert_one(ssn: ssn) + expect(connect_attempt.done?).to eq(false) + end +end diff --git a/spec/support/crypt.rb b/spec/support/crypt.rb index e270b5d78b..81a6b819e3 100644 --- a/spec/support/crypt.rb +++ b/spec/support/crypt.rb @@ -139,6 +139,7 @@ module Crypt let(:schema_map) do BSON::ExtJSON.parse(File.read(schema_map_file_path)) end + let(:data_key_options) { {} } let(:encrypted_ssn) do From 9936c1197c8b72295bf55a4d2e4baa63178e1cc5 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 8 Nov 2022 16:06:26 +0100 Subject: [PATCH 072/198] RUBY-3062 Add GCP KMS auto credentials (#2653) --- .evergreen/config-atlas.yml | 43 ++++- .evergreen/config.yml | 88 ++++++++- .evergreen/config/axes.yml.erb | 3 + .evergreen/config/common.yml.erb | 74 +++++++- .evergreen/config/standard.yml.erb | 13 ++ .evergreen/run-tests-gcp.sh | 22 +++ docs/reference/client-side-encryption.txt | 15 ++ docs/release-notes.txt | 2 + gemfiles/standard.rb | 2 +- lib/mongo/crypt/binding.rb | 2 +- lib/mongo/crypt/context.rb | 9 + lib/mongo/crypt/handle.rb | 2 +- lib/mongo/crypt/kms.rb | 6 + lib/mongo/crypt/kms/aws.rb | 120 +----------- lib/mongo/crypt/kms/aws/credentials.rb | 83 +++++++++ lib/mongo/crypt/kms/aws/master_document.rb | 78 ++++++++ lib/mongo/crypt/kms/azure.rb | 129 +------------ lib/mongo/crypt/kms/azure/credentials.rb | 90 +++++++++ lib/mongo/crypt/kms/azure/master_document.rb | 79 ++++++++ lib/mongo/crypt/kms/gcp.rb | 174 +----------------- lib/mongo/crypt/kms/gcp/credentials.rb | 127 +++++++++++++ .../crypt/kms/gcp/credentials_retriever.rb | 56 ++++++ lib/mongo/crypt/kms/gcp/master_document.rb | 99 ++++++++++ lib/mongo/crypt/kms/kmip.rb | 101 +--------- lib/mongo/crypt/kms/kmip/credentials.rb | 68 +++++++ lib/mongo/crypt/kms/kmip/master_document.rb | 74 ++++++++ lib/mongo/crypt/kms/local.rb | 67 +------ lib/mongo/crypt/kms/local/credentials.rb | 65 +++++++ lib/mongo/crypt/kms/local/master_document.rb | 43 +++++ .../on_demand_gcp_credentials_spec.rb | 51 +++++ spec/mongo/crypt/handle_spec.rb | 4 +- spec/mongo/crypt/kms/credentials_spec.rb | 16 ++ spec/shared | 2 +- spec/support/crypt.rb | 2 +- 34 files changed, 1219 insertions(+), 590 deletions(-) create mode 100755 .evergreen/run-tests-gcp.sh create mode 100644 lib/mongo/crypt/kms/aws/credentials.rb create mode 100644 lib/mongo/crypt/kms/aws/master_document.rb create mode 100644 lib/mongo/crypt/kms/azure/credentials.rb create mode 100644 lib/mongo/crypt/kms/azure/master_document.rb create mode 100644 lib/mongo/crypt/kms/gcp/credentials.rb create mode 100644 lib/mongo/crypt/kms/gcp/credentials_retriever.rb create mode 100644 lib/mongo/crypt/kms/gcp/master_document.rb create mode 100644 lib/mongo/crypt/kms/kmip/credentials.rb create mode 100644 lib/mongo/crypt/kms/kmip/master_document.rb create mode 100644 lib/mongo/crypt/kms/local/credentials.rb create mode 100644 lib/mongo/crypt/kms/local/master_document.rb create mode 100644 spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb diff --git a/.evergreen/config-atlas.yml b/.evergreen/config-atlas.yml index 5c16b7928b..d2bf84e20c 100644 --- a/.evergreen/config-atlas.yml +++ b/.evergreen/config-atlas.yml @@ -65,7 +65,7 @@ functions: CURRENT_VERSION=latest fi - export DRIVERS_TOOLS="$(pwd)/../drivers-tools" + export DRIVERS_TOOLS="$(pwd)/.mod/drivers-evergreen-tools" # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin @@ -440,6 +440,44 @@ post: - func: "upload test results to s3" - func: "delete serverless instance" +task_groups: + - name: testgcpkms_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: "create expansions" + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + echo '${testgcpkms_key_file}' > /tmp/testgcpkms_key_file.json + export GCPKMS_KEYFILE=/tmp/testgcpkms_key_file.json + export GCPKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS + export GCPKMS_SERVICEACCOUNT="${testgcpkms_service_account}" + export GCPKMS_MACHINETYPE="e2-standard-4" + .evergreen/csfle/gcpkms/create-and-setup-instance.sh + # Load the GCPKMS_GCLOUD, GCPKMS_INSTANCE, GCPKMS_REGION, and GCPKMS_ZONE expansions. + - command: expansions.update + params: + file: src/testgcpkms-expansions.yml + teardown_group: + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + .evergreen/csfle/gcpkms/delete-instance.sh + tasks: + - testgcpkms-task + tasks: - name: "test-atlas" @@ -616,6 +654,9 @@ axes: - id: "os" display_name: OS values: + - id: debian11 + display_name: "Debian 11" + run_on: debian11-small - id: ubuntu2004 display_name: "Ubuntu 20.04" run_on: ubuntu2004-small diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 900047fdd2..576c89153e 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -65,7 +65,7 @@ functions: CURRENT_VERSION=latest fi - export DRIVERS_TOOLS="$(pwd)/../drivers-tools" + export DRIVERS_TOOLS="$(pwd)/.mod/drivers-evergreen-tools" # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin @@ -461,6 +461,44 @@ post: #- func: "upload test results" - func: "upload test results to s3" +task_groups: + - name: testgcpkms_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: "create expansions" + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + echo '${testgcpkms_key_file}' > /tmp/testgcpkms_key_file.json + export GCPKMS_KEYFILE=/tmp/testgcpkms_key_file.json + export GCPKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS + export GCPKMS_SERVICEACCOUNT="${testgcpkms_service_account}" + export GCPKMS_MACHINETYPE="e2-standard-4" + .evergreen/csfle/gcpkms/create-and-setup-instance.sh + # Load the GCPKMS_GCLOUD, GCPKMS_INSTANCE, GCPKMS_REGION, and GCPKMS_ZONE expansions. + - command: expansions.update + params: + file: src/testgcpkms-expansions.yml + teardown_group: + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + .evergreen/csfle/gcpkms/delete-instance.sh + tasks: + - testgcpkms-task + tasks: - name: "test-docker" @@ -491,6 +529,38 @@ tasks: commands: - func: "export AWS auth credentials" - func: "run AWS auth tests" + - name: "testgcpkms-task" + commands: + - command: shell.exec + type: setup + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + echo "Copying files ... begin" + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + tar czf /tmp/mongo-ruby-driver.tgz . + GCPKMS_SRC=/tmp/mongo-ruby-driver.tgz GCPKMS_DST=$GCPKMS_INSTANCENAME: .evergreen/csfle/gcpkms/copy-file.sh + echo "Copying files ... end" + echo "Untarring file ... begin" + GCPKMS_CMD="tar xf mongo-ruby-driver.tgz" .evergreen/csfle/gcpkms/run-command.sh + echo "Untarring file ... end" + - command: shell.exec + type: test + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + GCPKMS_CMD="TEST_FLE_GCP_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}" MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY='${fle_gcp_private_key}' MONGO_RUBY_DRIVER_GCP_PROJECT_ID='${fle_gcp_project_id}' MONGO_RUBY_DRIVER_GCP_LOCATION='${fle_gcp_location}' MONGO_RUBY_DRIVER_GCP_KEY_RING='${fle_gcp_key_ring}' MONGO_RUBY_DRIVER_GCP_KEY_NAME='${fle_gcp_key_name}' ./.evergreen/run-tests-gcp.sh" .evergreen/csfle/gcpkms/run-command.sh axes: - id: preload @@ -658,6 +728,9 @@ axes: - id: "os" display_name: OS values: + - id: debian11 + display_name: "Debian 11" + run_on: debian11-small - id: ubuntu2004 display_name: "Ubuntu 20.04" run_on: ubuntu2004-small @@ -1645,3 +1718,16 @@ buildvariants: display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch + + - matrix_name: testgcpkms-variant + matrix_spec: + ruby: ruby-3.0 + fle: helper + topology: standalone + os: debian11 + mongodb-version: 6.0 + display_name: "GCP KMS" + tasks: + - name: testgcpkms_task_group + batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index ad4d8b25b9..961bfdc9c4 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -165,6 +165,9 @@ axes: - id: "os" display_name: OS values: + - id: debian11 + display_name: "Debian 11" + run_on: debian11-small - id: ubuntu2004 display_name: "Ubuntu 20.04" run_on: ubuntu2004-small diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index fa1d1e16f4..cc57262c06 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -64,7 +64,7 @@ functions: CURRENT_VERSION=latest fi - export DRIVERS_TOOLS="$(pwd)/../drivers-tools" + export DRIVERS_TOOLS="$(pwd)/.mod/drivers-evergreen-tools" # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin @@ -131,7 +131,7 @@ functions: CURRENT_VERSION=latest fi - export DRIVERS_TOOLS="$(pwd)/../drivers-tools" + export DRIVERS_TOOLS="$(pwd)/.mod/drivers-evergreen-tools" # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin @@ -531,6 +531,44 @@ post: - func: "delete serverless instance" <% end %> +task_groups: + - name: testgcpkms_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: "create expansions" + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + echo '${testgcpkms_key_file}' > /tmp/testgcpkms_key_file.json + export GCPKMS_KEYFILE=/tmp/testgcpkms_key_file.json + export GCPKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS + export GCPKMS_SERVICEACCOUNT="${testgcpkms_service_account}" + export GCPKMS_MACHINETYPE="e2-standard-4" + .evergreen/csfle/gcpkms/create-and-setup-instance.sh + # Load the GCPKMS_GCLOUD, GCPKMS_INSTANCE, GCPKMS_REGION, and GCPKMS_ZONE expansions. + - command: expansions.update + params: + file: src/testgcpkms-expansions.yml + teardown_group: + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + .evergreen/csfle/gcpkms/delete-instance.sh + tasks: + - testgcpkms-task + tasks: <% if @atlas %> @@ -570,4 +608,36 @@ tasks: commands: - func: "export AWS auth credentials" - func: "run AWS auth tests" + - name: "testgcpkms-task" + commands: + - command: shell.exec + type: setup + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + echo "Copying files ... begin" + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + tar czf /tmp/mongo-ruby-driver.tgz . + GCPKMS_SRC=/tmp/mongo-ruby-driver.tgz GCPKMS_DST=$GCPKMS_INSTANCENAME: .evergreen/csfle/gcpkms/copy-file.sh + echo "Copying files ... end" + echo "Untarring file ... begin" + GCPKMS_CMD="tar xf mongo-ruby-driver.tgz" .evergreen/csfle/gcpkms/run-command.sh + echo "Untarring file ... end" + - command: shell.exec + type: test + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + GCPKMS_CMD="TEST_FLE_GCP_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}" MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY='${fle_gcp_private_key}' MONGO_RUBY_DRIVER_GCP_PROJECT_ID='${fle_gcp_project_id}' MONGO_RUBY_DRIVER_GCP_LOCATION='${fle_gcp_location}' MONGO_RUBY_DRIVER_GCP_KEY_RING='${fle_gcp_key_ring}' MONGO_RUBY_DRIVER_GCP_KEY_NAME='${fle_gcp_key_name}' ./.evergreen/run-tests-gcp.sh" .evergreen/csfle/gcpkms/run-command.sh <% end %> diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index e2e73448cb..a7b8f662e2 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -642,3 +642,16 @@ buildvariants: display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch + + - matrix_name: testgcpkms-variant + matrix_spec: + ruby: ruby-3.0 + fle: helper + topology: standalone + os: debian11 + mongodb-version: 6.0 + display_name: "GCP KMS" + tasks: + - name: testgcpkms_task_group + batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + diff --git a/.evergreen/run-tests-gcp.sh b/.evergreen/run-tests-gcp.sh new file mode 100755 index 0000000000..32032fbeff --- /dev/null +++ b/.evergreen/run-tests-gcp.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -ex + +. `dirname "$0"`/../spec/shared/shlib/distro.sh +. `dirname "$0"`/../spec/shared/shlib/set_env.sh +. `dirname "$0"`/functions.sh + +set_env_vars +set_env_python +set_env_ruby + +sudo apt-get -y install libyaml-dev + +bundle_install + +echo "Running specs" +export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=${CRYPT_SHARED_LIB_PATH} +bundle exec rake spec:prepare +bundle exec rspec spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb + +exit ${test_status} diff --git a/docs/reference/client-side-encryption.txt b/docs/reference/client-side-encryption.txt index 359e421a16..1c02a956e6 100644 --- a/docs/reference/client-side-encryption.txt +++ b/docs/reference/client-side-encryption.txt @@ -910,6 +910,21 @@ as KMS provider options for AWS: See :ref:`"Automatically Retrieving Credentials" ` for more detailed information about the credential retrieval. +The client can retrieve GCP credentials from the Google Compute Engine +metadata endpoints. To retrieve credentials automatically, specify an empty Hash +as KMS provider options for GCP: + +.. code-block:: ruby + + Mongo::Client.new(['localhost:27017'], + auto_encryption_options: { + key_vault_namespace: 'encryption.__keyVault', + kms_providers: { + gcp: {} + } + } + ) + ``:kms_tls_options`` ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/release-notes.txt b/docs/release-notes.txt index a8687efe20..5e9f8841f8 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -27,6 +27,8 @@ This release includes the following new features: - Added support for automatic AWS credentials retrieval when AWS KMS is used for client side encryption. +- Added support for automatic GCP credentials retrieval when Google Cloud Key + Management is used for client side encryption. .. _release-notes-2.18: diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index 117366c1b3..81a27c9fb5 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -60,6 +60,6 @@ def standard_dependencies end if ENV['FLE'] == 'helper' - gem 'libmongocrypt-helper', '~> 1.5.2' + gem 'libmongocrypt-helper', '~> 1.6.1' end end diff --git a/lib/mongo/crypt/binding.rb b/lib/mongo/crypt/binding.rb index b0a6cbd6ad..64e17d0aa7 100644 --- a/lib/mongo/crypt/binding.rb +++ b/lib/mongo/crypt/binding.rb @@ -83,7 +83,7 @@ class Binding # will cause a `LoadError`. # # @api private - MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new("1.5.2") + MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new("1.6.1") # @!method self.mongocrypt_version(len) # @api private diff --git a/lib/mongo/crypt/context.rb b/lib/mongo/crypt/context.rb index c5167e3619..ed35eec58a 100644 --- a/lib/mongo/crypt/context.rb +++ b/lib/mongo/crypt/context.rb @@ -125,6 +125,15 @@ def run_state_machine ) end providers[:aws] = aws_credentials.to_h + elsif kms_providers.gcp&.empty? + begin + gcp_access_token = KMS::GCP::CredentialsRetriever.get_access_token + rescue KMS::CredentialsNotFound => e + raise Error::CryptError.new( + "Could not locate GCP credentials: #{e.class}: #{e.message}" + ) + end + providers[:gcp] = { access_token: gcp_access_token } end Binding.ctx_provide_kms_providers( self, diff --git a/lib/mongo/crypt/handle.rb b/lib/mongo/crypt/handle.rb index 596c24e02b..512cf10a6e 100644 --- a/lib/mongo/crypt/handle.rb +++ b/lib/mongo/crypt/handle.rb @@ -98,7 +98,7 @@ def initialize(kms_providers, kms_tls_options, options={}) Binding.setopt_kms_providers(self, @kms_providers.to_document) - if @kms_providers.aws&.empty? + if @kms_providers.aws&.empty? || @kms_providers.gcp&.empty? Binding.setopt_use_need_kms_credentials_state(self) end diff --git a/lib/mongo/crypt/kms.rb b/lib/mongo/crypt/kms.rb index f871aa145b..975e119716 100644 --- a/lib/mongo/crypt/kms.rb +++ b/lib/mongo/crypt/kms.rb @@ -18,6 +18,12 @@ module Mongo module Crypt module KMS + # This error indicates that we could not obtain credential for + # a KMS service. + # + # @api private + class CredentialsNotFound < RuntimeError; end + # This module contains helper methods for validating KMS parameters. # # @api private diff --git a/lib/mongo/crypt/kms/aws.rb b/lib/mongo/crypt/kms/aws.rb index a4f7fead88..c39c8a9f83 100644 --- a/lib/mongo/crypt/kms/aws.rb +++ b/lib/mongo/crypt/kms/aws.rb @@ -15,122 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -module Mongo - module Crypt - module KMS - module AWS +require 'mongo/crypt/kms/aws/credentials' +require 'mongo/crypt/kms/aws/master_document' - # AWS KMS Credentials object contains credentials for using AWS KMS provider. - # - # @api private - class Credentials - extend Forwardable - include KMS::Validations - - # @return [ String ] AWS access key. - attr_reader :access_key_id - - # @return [ String ] AWS secret access key. - attr_reader :secret_access_key - - # @return [ String | nil ] AWS session token. - attr_reader :session_token - - # @api private - def_delegator :@opts, :empty? - - FORMAT_HINT = "AWS KMS provider options must be in the format: " + - "{ access_key_id: 'YOUR-ACCESS-KEY-ID', secret_access_key: 'SECRET-ACCESS-KEY' }" - - # Creates an AWS KMS credentials object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains credentials for - # AWS KMS provider - # @option opts [ String ] :access_key_id AWS access key id. - # @option opts [ String ] :secret_access_key AWS secret access key. - # @option opts [ String | nil ] :session_token AWS session token, optional. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly - # formatted. - def initialize(opts) - @opts = opts - unless empty? - @access_key_id = validate_param(:access_key_id, opts, FORMAT_HINT) - @secret_access_key = validate_param(:secret_access_key, opts, FORMAT_HINT) - @session_token = validate_param(:session_token, opts, FORMAT_HINT, required: false) - end - end - - # Convert credentials object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] AWS KMS credentials in libmongocrypt format. - def to_document - return BSON::Document.new if empty? - BSON::Document.new({ - accessKeyId: access_key_id, - secretAccessKey: secret_access_key, - }).tap do |bson| - unless session_token.nil? - bson.update({ sessionToken: session_token }) - end - end - end - end - - # AWS KMS master key document object contains KMS master key parameters. - # - # @api private - class MasterKeyDocument - include KMS::Validations - - # @return [ String ] AWS region. - attr_reader :region - - # @return [ String ] AWS KMS key. - attr_reader :key - - # @return [ String | nil ] AWS KMS endpoint. - attr_reader :endpoint - - FORMAT_HINT = "AWS key document must be in the format: " + - "{ region: 'REGION', key: 'KEY' }" - - # Creates a master key document object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains master key options for - # the AWS KMS provider. - # @option opts [ String ] :region AWS region. - # @option opts [ String ] :key AWS KMS key. - # @option opts [ String | nil ] :endpoint AWS KMS endpoint, optional. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly. - def initialize(opts) - unless opts.is_a?(Hash) - raise ArgumentError.new( - 'Key document options must contain a key named :master_key with a Hash value' - ) - end - @region = validate_param(:region, opts, FORMAT_HINT) - @key = validate_param(:key, opts, FORMAT_HINT) - @endpoint = validate_param(:endpoint, opts, FORMAT_HINT, required: false) - end - - # Convert master key document object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] AWS KMS master key document in libmongocrypt format. - def to_document - BSON::Document.new({ - provider: 'aws', - region: region, - key: key, - }).tap do |bson| - unless endpoint.nil? - bson.update({ endpoint: endpoint }) - end - end - end - end - end - end - end -end diff --git a/lib/mongo/crypt/kms/aws/credentials.rb b/lib/mongo/crypt/kms/aws/credentials.rb new file mode 100644 index 0000000000..fba9e596f7 --- /dev/null +++ b/lib/mongo/crypt/kms/aws/credentials.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module AWS + + # AWS KMS Credentials object contains credentials for using AWS KMS provider. + # + # @api private + class Credentials + extend Forwardable + include KMS::Validations + + # @return [ String ] AWS access key. + attr_reader :access_key_id + + # @return [ String ] AWS secret access key. + attr_reader :secret_access_key + + # @return [ String | nil ] AWS session token. + attr_reader :session_token + + # @api private + def_delegator :@opts, :empty? + + FORMAT_HINT = "AWS KMS provider options must be in the format: " + + "{ access_key_id: 'YOUR-ACCESS-KEY-ID', secret_access_key: 'SECRET-ACCESS-KEY' }" + + # Creates an AWS KMS credentials object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains credentials for + # AWS KMS provider + # @option opts [ String ] :access_key_id AWS access key id. + # @option opts [ String ] :secret_access_key AWS secret access key. + # @option opts [ String | nil ] :session_token AWS session token, optional. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly + # formatted. + def initialize(opts) + @opts = opts + unless empty? + @access_key_id = validate_param(:access_key_id, opts, FORMAT_HINT) + @secret_access_key = validate_param(:secret_access_key, opts, FORMAT_HINT) + @session_token = validate_param(:session_token, opts, FORMAT_HINT, required: false) + end + end + + # Convert credentials object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] AWS KMS credentials in libmongocrypt format. + def to_document + return BSON::Document.new if empty? + BSON::Document.new({ + accessKeyId: access_key_id, + secretAccessKey: secret_access_key, + }).tap do |bson| + unless session_token.nil? + bson.update({ sessionToken: session_token }) + end + end + end + end + end + end + end +end + diff --git a/lib/mongo/crypt/kms/aws/master_document.rb b/lib/mongo/crypt/kms/aws/master_document.rb new file mode 100644 index 0000000000..2cd365ac88 --- /dev/null +++ b/lib/mongo/crypt/kms/aws/master_document.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module AWS + # AWS KMS master key document object contains KMS master key parameters. + # + # @api private + class MasterKeyDocument + include KMS::Validations + + # @return [ String ] AWS region. + attr_reader :region + + # @return [ String ] AWS KMS key. + attr_reader :key + + # @return [ String | nil ] AWS KMS endpoint. + attr_reader :endpoint + + FORMAT_HINT = "AWS key document must be in the format: " + + "{ region: 'REGION', key: 'KEY' }" + + # Creates a master key document object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains master key options for + # the AWS KMS provider. + # @option opts [ String ] :region AWS region. + # @option opts [ String ] :key AWS KMS key. + # @option opts [ String | nil ] :endpoint AWS KMS endpoint, optional. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly. + def initialize(opts) + unless opts.is_a?(Hash) + raise ArgumentError.new( + 'Key document options must contain a key named :master_key with a Hash value' + ) + end + @region = validate_param(:region, opts, FORMAT_HINT) + @key = validate_param(:key, opts, FORMAT_HINT) + @endpoint = validate_param(:endpoint, opts, FORMAT_HINT, required: false) + end + + # Convert master key document object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] AWS KMS master key document in libmongocrypt format. + def to_document + BSON::Document.new({ + provider: 'aws', + region: region, + key: key, + }).tap do |bson| + unless endpoint.nil? + bson.update({ endpoint: endpoint }) + end + end + end + end + end + end + end +end diff --git a/lib/mongo/crypt/kms/azure.rb b/lib/mongo/crypt/kms/azure.rb index ed596fb48a..e6bda28b70 100644 --- a/lib/mongo/crypt/kms/azure.rb +++ b/lib/mongo/crypt/kms/azure.rb @@ -15,130 +15,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -module Mongo - module Crypt - module KMS - module Azure - # Azure KMS Credentials object contains credentials for using Azure KMS provider. - # - # @api private - class Credentials - extend Forwardable - include KMS::Validations - - # @return [ String ] Azure tenant id. - attr_reader :tenant_id - - # @return [ String ] Azure client id. - attr_reader :client_id - - # @return [ String ] Azure client secret. - attr_reader :client_secret - - # @return [ String | nil ] Azure identity platform endpoint. - attr_reader :identity_platform_endpoint - - # @api private - def_delegator :@opts, :empty? - - FORMAT_HINT = "Azure KMS provider options must be in the format: " + - "{ tenant_id: 'TENANT-ID', client_id: 'TENANT_ID', client_secret: 'CLIENT_SECRET' }" - - # Creates an Azure KMS credentials object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains credentials for - # Azure KMS provider - # @option opts [ String ] :tenant_id Azure tenant id. - # @option opts [ String ] :client_id Azure client id. - # @option opts [ String ] :client_secret Azure client secret. - # @option opts [ String | nil ] :identity_platform_endpoint Azure - # identity platform endpoint, optional. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly - # formatted. - def initialize(opts) - @opts = opts - unless empty? - @tenant_id = validate_param(:tenant_id, opts, FORMAT_HINT) - @client_id = validate_param(:client_id, opts, FORMAT_HINT) - @client_secret = validate_param(:client_secret, opts, FORMAT_HINT) - @identity_platform_endpoint = validate_param( - :identity_platform_endpoint, opts, FORMAT_HINT, required: false - ) - end - end - - # Convert credentials object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] Azure KMS credentials in libmongocrypt format. - def to_document - return BSON::Document.new if empty? - BSON::Document.new({ - tenantId: @tenant_id, - clientId: @client_id, - clientSecret: @client_secret, - }).tap do |bson| - unless identity_platform_endpoint.nil? - bson.update({ identityPlatformEndpoint: identity_platform_endpoint }) - end - end - end - end - - # Azure KMS master key document object contains KMS master key parameters. - # - # @api private - class MasterKeyDocument - include KMS::Validations - - # @return [ String ] Azure key vault endpoint. - attr_reader :key_vault_endpoint - - # @return [ String ] Azure KMS key name. - attr_reader :key_name - - # @return [ String | nil ] Azure KMS key version. - attr_reader :key_version - - FORMAT_HINT = "Azure key document must be in the format: " + - "{ key_vault_endpoint: 'KEY_VAULT_ENDPOINT', key_name: 'KEY_NAME' }" - - # Creates a master key document object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains master key options for - # the Azure KMS provider. - # @option opts [ String ] :key_vault_endpoint Azure key vault endpoint. - # @option opts [ String ] :key_name Azure KMS key name. - # @option opts [ String | nil ] :key_version Azure KMS key version, optional. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly. - def initialize(opts) - unless opts.is_a?(Hash) - raise ArgumentError.new( - 'Key document options must contain a key named :master_key with a Hash value' - ) - end - @key_vault_endpoint = validate_param(:key_vault_endpoint, opts, FORMAT_HINT) - @key_name = validate_param(:key_name, opts, FORMAT_HINT) - @key_version = validate_param(:key_version, opts, FORMAT_HINT, required: false) - end - - # Convert master key document object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] Azure KMS credentials in libmongocrypt format. - def to_document - BSON::Document.new({ - provider: 'azure', - keyVaultEndpoint: key_vault_endpoint, - keyName: key_name, - }).tap do |bson| - unless key_version.nil? - bson.update({ keyVersion: key_version }) - end - end - end - end - end - end - end -end +require 'mongo/crypt/kms/azure/credentials' +require 'mongo/crypt/kms/azure/master_document' diff --git a/lib/mongo/crypt/kms/azure/credentials.rb b/lib/mongo/crypt/kms/azure/credentials.rb new file mode 100644 index 0000000000..d9a266fa03 --- /dev/null +++ b/lib/mongo/crypt/kms/azure/credentials.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module Azure + # Azure KMS Credentials object contains credentials for using Azure KMS provider. + # + # @api private + class Credentials + extend Forwardable + include KMS::Validations + + # @return [ String ] Azure tenant id. + attr_reader :tenant_id + + # @return [ String ] Azure client id. + attr_reader :client_id + + # @return [ String ] Azure client secret. + attr_reader :client_secret + + # @return [ String | nil ] Azure identity platform endpoint. + attr_reader :identity_platform_endpoint + + # @api private + def_delegator :@opts, :empty? + + FORMAT_HINT = "Azure KMS provider options must be in the format: " + + "{ tenant_id: 'TENANT-ID', client_id: 'TENANT_ID', client_secret: 'CLIENT_SECRET' }" + + # Creates an Azure KMS credentials object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains credentials for + # Azure KMS provider + # @option opts [ String ] :tenant_id Azure tenant id. + # @option opts [ String ] :client_id Azure client id. + # @option opts [ String ] :client_secret Azure client secret. + # @option opts [ String | nil ] :identity_platform_endpoint Azure + # identity platform endpoint, optional. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly + # formatted. + def initialize(opts) + @opts = opts + unless empty? + @tenant_id = validate_param(:tenant_id, opts, FORMAT_HINT) + @client_id = validate_param(:client_id, opts, FORMAT_HINT) + @client_secret = validate_param(:client_secret, opts, FORMAT_HINT) + @identity_platform_endpoint = validate_param( + :identity_platform_endpoint, opts, FORMAT_HINT, required: false + ) + end + end + + # Convert credentials object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] Azure KMS credentials in libmongocrypt format. + def to_document + return BSON::Document.new if empty? + BSON::Document.new({ + tenantId: @tenant_id, + clientId: @client_id, + clientSecret: @client_secret, + }).tap do |bson| + unless identity_platform_endpoint.nil? + bson.update({ identityPlatformEndpoint: identity_platform_endpoint }) + end + end + end + end + end + end + end +end diff --git a/lib/mongo/crypt/kms/azure/master_document.rb b/lib/mongo/crypt/kms/azure/master_document.rb new file mode 100644 index 0000000000..60e1f0b06f --- /dev/null +++ b/lib/mongo/crypt/kms/azure/master_document.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module Azure + # Azure KMS master key document object contains KMS master key parameters. + # + # @api private + class MasterKeyDocument + include KMS::Validations + + # @return [ String ] Azure key vault endpoint. + attr_reader :key_vault_endpoint + + # @return [ String ] Azure KMS key name. + attr_reader :key_name + + # @return [ String | nil ] Azure KMS key version. + attr_reader :key_version + + FORMAT_HINT = "Azure key document must be in the format: " + + "{ key_vault_endpoint: 'KEY_VAULT_ENDPOINT', key_name: 'KEY_NAME' }" + + # Creates a master key document object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains master key options for + # the Azure KMS provider. + # @option opts [ String ] :key_vault_endpoint Azure key vault endpoint. + # @option opts [ String ] :key_name Azure KMS key name. + # @option opts [ String | nil ] :key_version Azure KMS key version, optional. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly. + def initialize(opts) + unless opts.is_a?(Hash) + raise ArgumentError.new( + 'Key document options must contain a key named :master_key with a Hash value' + ) + end + @key_vault_endpoint = validate_param(:key_vault_endpoint, opts, FORMAT_HINT) + @key_name = validate_param(:key_name, opts, FORMAT_HINT) + @key_version = validate_param(:key_version, opts, FORMAT_HINT, required: false) + end + + # Convert master key document object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] Azure KMS credentials in libmongocrypt format. + def to_document + BSON::Document.new({ + provider: 'azure', + keyVaultEndpoint: key_vault_endpoint, + keyName: key_name, + }).tap do |bson| + unless key_version.nil? + bson.update({ keyVersion: key_version }) + end + end + end + end + end + end + end +end + diff --git a/lib/mongo/crypt/kms/gcp.rb b/lib/mongo/crypt/kms/gcp.rb index 66461e69c9..43591e3593 100644 --- a/lib/mongo/crypt/kms/gcp.rb +++ b/lib/mongo/crypt/kms/gcp.rb @@ -15,175 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -module Mongo - module Crypt - module KMS - module GCP - # GCP Cloud Key Management Credentials object contains credentials for - # using GCP KMS provider. - # - # @api private - class Credentials - extend Forwardable - include KMS::Validations +require 'mongo/crypt/kms/gcp/credentials' +require 'mongo/crypt/kms/gcp/credentials_retriever' +require 'mongo/crypt/kms/gcp/master_document' - # @return [ String ] GCP email to authenticate with. - attr_reader :email - - # @return [ String ] GCP private key, base64 encoded DER format. - attr_reader :private_key - - # @return [ String | nil ] GCP KMS endpoint. - attr_reader :endpoint - - # @api private - def_delegator :@opts, :empty? - - FORMAT_HINT = "GCP KMS provider options must be in the format: " + - "{ email: 'EMAIL', private_key: 'PRIVATE-KEY' }" - - # Creates an GCP KMS credentials object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains credentials for - # GCP KMS provider - # @option opts [ String ] :email GCP email. - # @option opts [ String ] :private_key GCP private key. This method accepts - # private key in either base64 encoded DER format, or PEM format. - # @option opts [ String | nil ] :endpoint GCP endpoint, optional. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly - # formatted. - def initialize(opts) - @opts = opts - return if empty? - - @email = validate_param(:email, opts, FORMAT_HINT) - @private_key = begin - private_key_opt = validate_param(:private_key, opts, FORMAT_HINT) - if BSON::Environment.jruby? - # We cannot really validate private key on JRuby, so we assume - # it is in base64 encoded DER format. - private_key_opt - else - # Check if private key is in PEM format. - pkey = OpenSSL::PKey::RSA.new(private_key_opt) - # PEM it is, need to be converted to base64 encoded DER. - der = if pkey.respond_to?(:private_to_der) - pkey.private_to_der - else - pkey.to_der - end - Base64.encode64(der) - end - rescue OpenSSL::PKey::RSAError - # Check if private key is in DER. - begin - OpenSSL::PKey.read(Base64.decode64(private_key_opt)) - # Private key is fine, use it. - private_key_opt - rescue OpenSSL::PKey::PKeyError - raise ArgumentError.new( - "The private_key option must be either either base64 encoded DER format, or PEM format." - ) - end - end - - @endpoint = validate_param( - :endpoint, opts, FORMAT_HINT, required: false - ) - end - - # Convert credentials object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] Azure KMS credentials in libmongocrypt format. - def to_document - return BSON::Document.new if empty? - BSON::Document.new({ - email: email, - privateKey: BSON::Binary.new(private_key, :generic), - }).tap do |bson| - unless endpoint.nil? - bson.update({ endpoint: endpoint }) - end - end - end - end - - # GCP KMS master key document object contains KMS master key parameters. - # - # @api private - class MasterKeyDocument - include KMS::Validations - - # @return [ String ] GCP project id. - attr_reader :project_id - - # @return [ String ] GCP location. - attr_reader :location - - # @return [ String ] GCP KMS key ring. - attr_reader :key_ring - - # @return [ String ] GCP KMS key name. - attr_reader :key_name - - # @return [ String | nil ] GCP KMS key version. - attr_reader :key_version - - # @return [ String | nil ] GCP KMS endpoint. - attr_reader :endpoint - - FORMAT_HINT = "GCP key document must be in the format: " + - "{ project_id: 'PROJECT_ID', location: 'LOCATION', " + - "key_ring: 'KEY-RING', key_name: 'KEY-NAME' }" - - # Creates a master key document object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains master key options for - # the GCP KMS provider. - # @option opts [ String ] :project_id GCP project id. - # @option opts [ String ] :location GCP location. - # @option opts [ String ] :key_ring GCP KMS key ring. - # @option opts [ String ] :key_name GCP KMS key name. - # @option opts [ String | nil ] :key_version GCP KMS key version, optional. - # @option opts [ String | nil ] :endpoint GCP KMS key endpoint, optional. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly. - def initialize(opts) - if opts.empty? - @empty = true - return - end - @project_id = validate_param(:project_id, opts, FORMAT_HINT) - @location = validate_param(:location, opts, FORMAT_HINT) - @key_ring = validate_param(:key_ring, opts, FORMAT_HINT) - @key_name = validate_param(:key_name, opts, FORMAT_HINT) - @key_version = validate_param(:key_version, opts, FORMAT_HINT, required: false) - @endpoint = validate_param(:endpoint, opts, FORMAT_HINT, required: false) - end - - # Convert master key document object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] GCP KMS credentials in libmongocrypt format. - def to_document - return BSON::Document.new({}) if @empty - BSON::Document.new({ - provider: 'gcp', - projectId: project_id, - location: location, - keyRing: key_ring, - keyName: key_name - }).tap do |bson| - unless key_version.nil? - bson.update({ keyVersion: key_version }) - end - unless endpoint.nil? - bson.update({ endpoint: endpoint }) - end - end - end - end - end - end - end -end diff --git a/lib/mongo/crypt/kms/gcp/credentials.rb b/lib/mongo/crypt/kms/gcp/credentials.rb new file mode 100644 index 0000000000..903b15642c --- /dev/null +++ b/lib/mongo/crypt/kms/gcp/credentials.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module GCP + # GCP Cloud Key Management Credentials object contains credentials for + # using GCP KMS provider. + # + # @api private + class Credentials + extend Forwardable + include KMS::Validations + + # @return [ String ] GCP email to authenticate with. + attr_reader :email + + # @return [ String ] GCP private key, base64 encoded DER format. + attr_reader :private_key + + # @return [ String | nil ] GCP KMS endpoint. + attr_reader :endpoint + + # @return [ String | nil ] GCP access token. + attr_reader :access_token + + # @api private + def_delegator :@opts, :empty? + + FORMAT_HINT = "GCP KMS provider options must be in the format: " + + "{ email: 'EMAIL', private_key: 'PRIVATE-KEY' }" + + # Creates an GCP KMS credentials object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains credentials for + # GCP KMS provider + # @option opts [ String ] :email GCP email. + # @option opts [ String ] :private_key GCP private key. This method accepts + # private key in either base64 encoded DER format, or PEM format. + # @option opts [ String | nil ] :endpoint GCP endpoint, optional. + # @option opts [ String | nil ] :access_token GCP access token, optional. + # If this option is not null, other options are ignored. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly + # formatted. + def initialize(opts) + @opts = opts + return if empty? + + if opts[:access_token] + @access_token = opts[:access_token] + else + @email = validate_param(:email, opts, FORMAT_HINT) + @private_key = begin + private_key_opt = validate_param(:private_key, opts, FORMAT_HINT) + if BSON::Environment.jruby? + # We cannot really validate private key on JRuby, so we assume + # it is in base64 encoded DER format. + private_key_opt + else + # Check if private key is in PEM format. + pkey = OpenSSL::PKey::RSA.new(private_key_opt) + # PEM it is, need to be converted to base64 encoded DER. + der = if pkey.respond_to?(:private_to_der) + pkey.private_to_der + else + pkey.to_der + end + Base64.encode64(der) + end + rescue OpenSSL::PKey::RSAError + # Check if private key is in DER. + begin + OpenSSL::PKey.read(Base64.decode64(private_key_opt)) + # Private key is fine, use it. + private_key_opt + rescue OpenSSL::PKey::PKeyError + raise ArgumentError.new( + "The private_key option must be either either base64 encoded DER format, or PEM format." + ) + end + end + + @endpoint = validate_param( + :endpoint, opts, FORMAT_HINT, required: false + ) + end + end + + # Convert credentials object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] Azure KMS credentials in libmongocrypt format. + def to_document + return BSON::Document.new if empty? + if access_token + BSON::Document.new({ accessToken: access_token }) + else + BSON::Document.new({ + email: email, + privateKey: BSON::Binary.new(private_key, :generic), + }).tap do |bson| + unless endpoint.nil? + bson.update({ endpoint: endpoint }) + end + end + end + end + end + end + end + end +end diff --git a/lib/mongo/crypt/kms/gcp/credentials_retriever.rb b/lib/mongo/crypt/kms/gcp/credentials_retriever.rb new file mode 100644 index 0000000000..9c931f9852 --- /dev/null +++ b/lib/mongo/crypt/kms/gcp/credentials_retriever.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module GCP + # This class retrieves GPC credentials using Google Compute Engine + # metadata host. This should be used when the driver is used on the + # Google Compute Engine instance. + # + # @api private + class CredentialsRetriever + + METADATA_HOST_ENV = 'GCE_METADATA_HOST' + + DEFAULT_HOST = 'metadata.google.internal' + + def self.get_access_token + host = ENV.fetch(METADATA_HOST_ENV) { DEFAULT_HOST } + uri = URI("http://#{host}/computeMetadata/v1/instance/service-accounts/default/token") + req = Net::HTTP::Get.new(uri) + req['Metadata-Flavor'] = 'Google' + resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: false) do |http| + http.request(req) + end + if resp.code != '200' + raise KMS::CredentialsNotFound, + "GCE metadata host responded with code #{resp.code}" + end + parsed_resp = JSON.parse(resp.body) + parsed_resp.fetch('access_token') + rescue JSON::ParserError, KeyError => e + raise KMS::CredentialsNotFound, + "GCE metadata response is invalid: '#{resp.body}'; #{e.class}: #{e.message}" + end + end + end + end + end +end + diff --git a/lib/mongo/crypt/kms/gcp/master_document.rb b/lib/mongo/crypt/kms/gcp/master_document.rb new file mode 100644 index 0000000000..369a6e1837 --- /dev/null +++ b/lib/mongo/crypt/kms/gcp/master_document.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module GCP + # GCP KMS master key document object contains KMS master key parameters. + # + # @api private + class MasterKeyDocument + include KMS::Validations + + # @return [ String ] GCP project id. + attr_reader :project_id + + # @return [ String ] GCP location. + attr_reader :location + + # @return [ String ] GCP KMS key ring. + attr_reader :key_ring + + # @return [ String ] GCP KMS key name. + attr_reader :key_name + + # @return [ String | nil ] GCP KMS key version. + attr_reader :key_version + + # @return [ String | nil ] GCP KMS endpoint. + attr_reader :endpoint + + FORMAT_HINT = "GCP key document must be in the format: " + + "{ project_id: 'PROJECT_ID', location: 'LOCATION', " + + "key_ring: 'KEY-RING', key_name: 'KEY-NAME' }" + + # Creates a master key document object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains master key options for + # the GCP KMS provider. + # @option opts [ String ] :project_id GCP project id. + # @option opts [ String ] :location GCP location. + # @option opts [ String ] :key_ring GCP KMS key ring. + # @option opts [ String ] :key_name GCP KMS key name. + # @option opts [ String | nil ] :key_version GCP KMS key version, optional. + # @option opts [ String | nil ] :endpoint GCP KMS key endpoint, optional. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly. + def initialize(opts) + if opts.empty? + @empty = true + return + end + @project_id = validate_param(:project_id, opts, FORMAT_HINT) + @location = validate_param(:location, opts, FORMAT_HINT) + @key_ring = validate_param(:key_ring, opts, FORMAT_HINT) + @key_name = validate_param(:key_name, opts, FORMAT_HINT) + @key_version = validate_param(:key_version, opts, FORMAT_HINT, required: false) + @endpoint = validate_param(:endpoint, opts, FORMAT_HINT, required: false) + end + + # Convert master key document object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] GCP KMS credentials in libmongocrypt format. + def to_document + return BSON::Document.new({}) if @empty + BSON::Document.new({ + provider: 'gcp', + projectId: project_id, + location: location, + keyRing: key_ring, + keyName: key_name + }).tap do |bson| + unless key_version.nil? + bson.update({ keyVersion: key_version }) + end + unless endpoint.nil? + bson.update({ endpoint: endpoint }) + end + end + end + end + end + end + end +end diff --git a/lib/mongo/crypt/kms/kmip.rb b/lib/mongo/crypt/kms/kmip.rb index 33ef2edd1b..00d32d2390 100644 --- a/lib/mongo/crypt/kms/kmip.rb +++ b/lib/mongo/crypt/kms/kmip.rb @@ -15,102 +15,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -module Mongo - module Crypt - module KMS - module KMIP - # KMIP KMS Credentials object contains credentials for a - # remote KMIP KMS provider. - # - # @api private - class Credentials - extend Forwardable - include KMS::Validations - - # @return [ String ] KMIP KMS endpoint with optional port. - attr_reader :endpoint - - # @api private - def_delegator :@opts, :empty? - - FORMAT_HINT = "KMIP KMS provider options must be in the format: " + - "{ endpoint: 'ENDPOINT' }" - - # Creates a KMIP KMS credentials object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains credentials for - # KMIP KMS provider. - # @option opts [ String ] :endpoint KMIP endpoint. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly - # formatted. - def initialize(opts) - @opts = opts - unless empty? - @endpoint = validate_param(:endpoint, opts, FORMAT_HINT) - end - end - - # Convert credentials object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] Local KMS credentials in libmongocrypt format. - def to_document - return BSON::Document.new({}) if empty? - BSON::Document.new({ - endpoint: endpoint, - }) - end - end - - # KMIP KMS master key document object contains KMS master key parameters. - # - # @api private - class MasterKeyDocument - include KMS::Validations - - # @return [ String | nil ] The KMIP Unique Identifier to a 96 byte - # KMIP Secret Data managed object. - attr_reader :key_id - - # @return [ String | nil ] KMIP KMS endpoint with optional port. - attr_reader :endpoint - - FORMAT_HINT = "KMIP KMS key document must be in the format: " + - "{ key_id: 'KEY-ID', endpoint: 'ENDPOINT' }" - - # Creates a master key document object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains master key options for - # KMIP KMS provider - # @option opts [ String | nil ] :key_id KMIP Unique Identifier to - # a 96 byte KMIP Secret Data managed object, optional. If key_id - # is omitted, the driver creates a random 96 byte identifier. - # @option opts [ String | nil ] :endpoint KMIP endpoint, optional. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly - # formatted. - def initialize(opts = {}) - @key_id = validate_param( - :key_id, opts, FORMAT_HINT, required: false - ) - @endpoint = validate_param( - :endpoint, opts, FORMAT_HINT, required: false - ) - end - - # Convert master key document object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] KMIP KMS credentials in libmongocrypt format. - def to_document - BSON::Document.new({ - provider: 'kmip', - }).tap do |bson| - bson.update({ endpoint: endpoint }) unless endpoint.nil? - bson.update({ keyId: key_id }) unless key_id.nil? - end - end - end - end - end - end -end +require 'mongo/crypt/kms/kmip/credentials' +require 'mongo/crypt/kms/kmip/master_document' diff --git a/lib/mongo/crypt/kms/kmip/credentials.rb b/lib/mongo/crypt/kms/kmip/credentials.rb new file mode 100644 index 0000000000..f9dda9a0cf --- /dev/null +++ b/lib/mongo/crypt/kms/kmip/credentials.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module KMIP + # KMIP KMS Credentials object contains credentials for a + # remote KMIP KMS provider. + # + # @api private + class Credentials + extend Forwardable + include KMS::Validations + + # @return [ String ] KMIP KMS endpoint with optional port. + attr_reader :endpoint + + # @api private + def_delegator :@opts, :empty? + + FORMAT_HINT = "KMIP KMS provider options must be in the format: " + + "{ endpoint: 'ENDPOINT' }" + + # Creates a KMIP KMS credentials object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains credentials for + # KMIP KMS provider. + # @option opts [ String ] :endpoint KMIP endpoint. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly + # formatted. + def initialize(opts) + @opts = opts + unless empty? + @endpoint = validate_param(:endpoint, opts, FORMAT_HINT) + end + end + + # Convert credentials object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] Local KMS credentials in libmongocrypt format. + def to_document + return BSON::Document.new({}) if empty? + BSON::Document.new({ + endpoint: endpoint, + }) + end + end + end + end + end +end + diff --git a/lib/mongo/crypt/kms/kmip/master_document.rb b/lib/mongo/crypt/kms/kmip/master_document.rb new file mode 100644 index 0000000000..d3166008e4 --- /dev/null +++ b/lib/mongo/crypt/kms/kmip/master_document.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module KMIP + # KMIP KMS master key document object contains KMS master key parameters. + # + # @api private + class MasterKeyDocument + include KMS::Validations + + # @return [ String | nil ] The KMIP Unique Identifier to a 96 byte + # KMIP Secret Data managed object. + attr_reader :key_id + + # @return [ String | nil ] KMIP KMS endpoint with optional port. + attr_reader :endpoint + + FORMAT_HINT = "KMIP KMS key document must be in the format: " + + "{ key_id: 'KEY-ID', endpoint: 'ENDPOINT' }" + + # Creates a master key document object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains master key options for + # KMIP KMS provider + # @option opts [ String | nil ] :key_id KMIP Unique Identifier to + # a 96 byte KMIP Secret Data managed object, optional. If key_id + # is omitted, the driver creates a random 96 byte identifier. + # @option opts [ String | nil ] :endpoint KMIP endpoint, optional. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly + # formatted. + def initialize(opts = {}) + @key_id = validate_param( + :key_id, opts, FORMAT_HINT, required: false + ) + @endpoint = validate_param( + :endpoint, opts, FORMAT_HINT, required: false + ) + end + + # Convert master key document object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] KMIP KMS credentials in libmongocrypt format. + def to_document + BSON::Document.new({ + provider: 'kmip', + }).tap do |bson| + bson.update({ endpoint: endpoint }) unless endpoint.nil? + bson.update({ keyId: key_id }) unless key_id.nil? + end + end + end + end + end + end +end + diff --git a/lib/mongo/crypt/kms/local.rb b/lib/mongo/crypt/kms/local.rb index 35ea341f5d..bab5f02bbd 100644 --- a/lib/mongo/crypt/kms/local.rb +++ b/lib/mongo/crypt/kms/local.rb @@ -15,68 +15,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -module Mongo - module Crypt - module KMS - module Local - # Local KMS Credentials object contains credentials for using local KMS provider. - # - # @api private - class Credentials - extend Forwardable - include KMS::Validations - - # @return [ String ] Master key. - attr_reader :key - - # @api private - def_delegator :@opts, :empty? - - FORMAT_HINT = "Local KMS provider options must be in the format: " + - "{ key: 'MASTER-KEY' }" - - # Creates a local KMS credentials object form a parameters hash. - # - # @param [ Hash ] opts A hash that contains credentials for - # local KMS provider - # @option opts [ String ] :key Master key. - # - # @raise [ ArgumentError ] If required options are missing or incorrectly - # formatted. - def initialize(opts) - @opts = opts - unless empty? - @key = validate_param(:key, opts, FORMAT_HINT) - end - end - - # @return [ BSON::Document ] Local KMS credentials in libmongocrypt format. - def to_document - return BSON::Document.new({}) if empty? - BSON::Document.new({ - key: BSON::Binary.new(@key, :generic), - }) - end - end - - # Local KMS master key document object contains KMS master key parameters. - # - # @api private - class MasterKeyDocument - - # Creates a master key document object form a parameters hash. - # This empty method is to keep a uniform interface for all KMS providers. - def initialize(_opts) - end - - # Convert master key document object to a BSON document in libmongocrypt format. - # - # @return [ BSON::Document ] Local KMS credentials in libmongocrypt format. - def to_document - BSON::Document.new({ provider: "local" }) - end - end - end - end - end -end +require 'mongo/crypt/kms/local/credentials' +require 'mongo/crypt/kms/local/master_document' diff --git a/lib/mongo/crypt/kms/local/credentials.rb b/lib/mongo/crypt/kms/local/credentials.rb new file mode 100644 index 0000000000..56beb2a70f --- /dev/null +++ b/lib/mongo/crypt/kms/local/credentials.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module Local + # Local KMS Credentials object contains credentials for using local KMS provider. + # + # @api private + class Credentials + extend Forwardable + include KMS::Validations + + # @return [ String ] Master key. + attr_reader :key + + # @api private + def_delegator :@opts, :empty? + + FORMAT_HINT = "Local KMS provider options must be in the format: " + + "{ key: 'MASTER-KEY' }" + + # Creates a local KMS credentials object form a parameters hash. + # + # @param [ Hash ] opts A hash that contains credentials for + # local KMS provider + # @option opts [ String ] :key Master key. + # + # @raise [ ArgumentError ] If required options are missing or incorrectly + # formatted. + def initialize(opts) + @opts = opts + unless empty? + @key = validate_param(:key, opts, FORMAT_HINT) + end + end + + # @return [ BSON::Document ] Local KMS credentials in libmongocrypt format. + def to_document + return BSON::Document.new({}) if empty? + BSON::Document.new({ + key: BSON::Binary.new(@key, :generic), + }) + end + end + end + end + end +end + diff --git a/lib/mongo/crypt/kms/local/master_document.rb b/lib/mongo/crypt/kms/local/master_document.rb new file mode 100644 index 0000000000..ce34688d11 --- /dev/null +++ b/lib/mongo/crypt/kms/local/master_document.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module Local + # Local KMS master key document object contains KMS master key parameters. + # + # @api private + class MasterKeyDocument + + # Creates a master key document object form a parameters hash. + # This empty method is to keep a uniform interface for all KMS providers. + def initialize(_opts) + end + + # Convert master key document object to a BSON document in libmongocrypt format. + # + # @return [ BSON::Document ] Local KMS credentials in libmongocrypt format. + def to_document + BSON::Document.new({ provider: "local" }) + end + end + end + end + end +end + diff --git a/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb b/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb new file mode 100644 index 0000000000..5fa7b55317 --- /dev/null +++ b/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'spec_helper' + +describe 'On-demand GCP Credentials' do + require_libmongocrypt + include_context 'define shared FLE helpers' + include_context 'with GCP kms_providers' + + let(:client) { ClientRegistry.instance.new_local_client(SpecConfig.instance.addresses) } + + let(:client_encryption_opts) do + { + kms_providers: { gcp: {} }, + kms_tls_options: kms_tls_options, + key_vault_namespace: key_vault_namespace + } + end + + let(:client_encryption) do + Mongo::ClientEncryption.new( + client, + client_encryption_opts + ) + end + + context 'when credentials are available' do + it 'authenticates successfully' do + skip 'This tests should be run inside Google Cloud only' unless ENV['TEST_FLE_GCP_AUTO'] + expect do + client_encryption.create_data_key('gcp', data_key_options) + end.not_to raise_error + end + end + + context 'when credentials are not available' do + it 'raises an error' do + expect( + Mongo::Crypt::KMS::GCP::CredentialsRetriever + ).to receive(:get_access_token).with(no_args).once.and_raise( + Mongo::Crypt::KMS::CredentialsNotFound + ) + + expect do + client_encryption.create_data_key('gcp', data_key_options) + end.to raise_error(Mongo::Error::CryptError, /GCP credentials/) + end + end +end + diff --git a/spec/mongo/crypt/handle_spec.rb b/spec/mongo/crypt/handle_spec.rb index 208aaae4b2..be5331e858 100644 --- a/spec/mongo/crypt/handle_spec.rb +++ b/spec/mongo/crypt/handle_spec.rb @@ -213,7 +213,9 @@ end it 'instructs libmongocrypt to handle empty AWS credentials' do - expect(Mongo::Crypt::Binding).to receive(:setopt_use_need_kms_credentials_state).once + expect(Mongo::Crypt::Binding).to receive( + :setopt_use_need_kms_credentials_state + ).once.and_call_original handle end end diff --git a/spec/mongo/crypt/kms/credentials_spec.rb b/spec/mongo/crypt/kms/credentials_spec.rb index 608c779c3c..4e2c08df42 100644 --- a/spec/mongo/crypt/kms/credentials_spec.rb +++ b/spec/mongo/crypt/kms/credentials_spec.rb @@ -289,6 +289,22 @@ end end end + + context 'with access token' do + let(:kms_provider) do + { + access_token: 'access_token' + } + end + + it 'returns valid libmongocrypt credentials' do + expect(params.to_document).to eq( + BSON::Document.new({ + accessToken: 'access_token' + }) + ) + end + end end context 'KMIP' do diff --git a/spec/shared b/spec/shared index 30e2f1bcab..6fca775f07 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit 30e2f1bcabe957f76dc412dac3e91077b3cb3715 +Subproject commit 6fca775f07e17dc1b6637eadaff768f8a87d8201 diff --git a/spec/support/crypt.rb b/spec/support/crypt.rb index 81a6b819e3..70860f8ae8 100644 --- a/spec/support/crypt.rb +++ b/spec/support/crypt.rb @@ -301,7 +301,7 @@ module Crypt 'MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY, ' + 'MONGO_RUBY_DRIVER_GCP_PROJECT_ID, MONGO_RUBY_DRIVER_GCP_LOCATION, ' + 'MONGO_RUBY_DRIVER_GCP_KEY_RING, MONGO_RUBY_DRIVER_GCP_KEY_NAME ' + - 'environment variables to be set information from Azure.' + 'environment variables to be set information from GCP.' if SpecConfig.instance.fle? fail(reason) From d6f4d28464816ad3968c02a423ae2f026f489be0 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 9 Nov 2022 10:22:59 -0500 Subject: [PATCH 073/198] RUBY-3049 Provide access to raw result document when the server returns an error for a command (#2656) --- spec/runners/unified/crud_operations.rb | 1 + spec/runners/unified/ddl_operations.rb | 3 + spec/runners/unified/test.rb | 4 +- .../aggregate-merge-errorResponse.yml | 42 +++++++++++ .../crud_unified/bulkWrite-errorResponse.yml | 50 ++++++++++++++ .../crud_unified/deleteOne-errorResponse.yml | 46 +++++++++++++ .../findOneAndUpdate-errorResponse.yml | 69 +++++++++++++++++++ .../crud_unified/insertOne-errorResponse.yml | 46 +++++++++++++ .../crud_unified/updateOne-errorResponse.yml | 47 +++++++++++++ .../expectedError-errorResponse.yml | 39 +++++++++++ 10 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 spec/spec_tests/data/crud_unified/aggregate-merge-errorResponse.yml create mode 100644 spec/spec_tests/data/crud_unified/bulkWrite-errorResponse.yml create mode 100644 spec/spec_tests/data/crud_unified/deleteOne-errorResponse.yml create mode 100644 spec/spec_tests/data/crud_unified/findOneAndUpdate-errorResponse.yml create mode 100644 spec/spec_tests/data/crud_unified/insertOne-errorResponse.yml create mode 100644 spec/spec_tests/data/crud_unified/updateOne-errorResponse.yml create mode 100644 spec/spec_tests/data/unified/valid-pass/expectedError-errorResponse.yml diff --git a/spec/runners/unified/crud_operations.rb b/spec/runners/unified/crud_operations.rb index 99d2196452..5544757617 100644 --- a/spec/runners/unified/crud_operations.rb +++ b/spec/runners/unified/crud_operations.rb @@ -93,6 +93,7 @@ def find_one_and_update(op) let: args.use('let'), comment: args.use('comment'), hint: args.use('hint'), + upsert: args.use('upsert'), } if return_document = args.use('returnDocument') opts[:return_document] = return_document.downcase.to_sym diff --git a/spec/runners/unified/ddl_operations.rb b/spec/runners/unified/ddl_operations.rb index 06cfcf9bba..1efaedcbab 100644 --- a/spec/runners/unified/ddl_operations.rb +++ b/spec/runners/unified/ddl_operations.rb @@ -125,6 +125,9 @@ def create_index(op) if session = args.use('session') opts[:session] = entities.get(:session, session) end + if args.key?('unique') + opts[:unique] = args.use('unique') + end collection.indexes.create_one( args.use!('keys'), diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index 566c14250c..31215dae0f 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -433,9 +433,11 @@ def execute_operation(op) end end end + if error_response = expected_error.use("errorResponse") + assert_result_matches(e.document, error_response) + end if expected_result = expected_error.use('expectResult') assert_result_matches(e.result, expected_result) - #expected_result.clear # Important: this must be the last branch. elsif expected_error.use('isError') # Nothing but we consume the key. diff --git a/spec/spec_tests/data/crud_unified/aggregate-merge-errorResponse.yml b/spec/spec_tests/data/crud_unified/aggregate-merge-errorResponse.yml new file mode 100644 index 0000000000..5fd679bffb --- /dev/null +++ b/spec/spec_tests/data/crud_unified/aggregate-merge-errorResponse.yml @@ -0,0 +1,42 @@ +description: "aggregate-merge-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 1 } + - { _id: 2, x: 1 } + +tests: + - description: "aggregate $merge DuplicateKey error is accessible" + runOnRequirements: + - minServerVersion: "5.1" # SERVER-59097 + # Exclude sharded topologies since the aggregate command fails with + # IllegalOperation(20) instead of DuplicateKey(11000) + topologies: [ single, replicaset ] + operations: + - name: aggregate + object: *database0 + arguments: + pipeline: + - { $documents: [ { _id: 2, x: 1 } ] } + - { $merge: { into: *collection0Name, whenMatched: "fail" } } + expectError: + errorCode: 11000 # DuplicateKey + errorResponse: + keyPattern: { _id: 1 } + keyValue: { _id: 2 } diff --git a/spec/spec_tests/data/crud_unified/bulkWrite-errorResponse.yml b/spec/spec_tests/data/crud_unified/bulkWrite-errorResponse.yml new file mode 100644 index 0000000000..d4f335dfd3 --- /dev/null +++ b/spec/spec_tests/data/crud_unified/bulkWrite-errorResponse.yml @@ -0,0 +1,50 @@ +description: "bulkWrite-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # This test intentionally executes only a single insert operation in the bulk + # write to make the error code and response assertions less ambiguous. That + # said, some drivers may still need to skip this test because the CRUD spec + # does not prescribe how drivers should formulate a BulkWriteException beyond + # collecting write and write concern errors. + - description: "bulkWrite operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: &errorCode 8 # UnknownError + - name: bulkWrite + object: *collection0 + arguments: + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/spec/spec_tests/data/crud_unified/deleteOne-errorResponse.yml b/spec/spec_tests/data/crud_unified/deleteOne-errorResponse.yml new file mode 100644 index 0000000000..dcf013060e --- /dev/null +++ b/spec/spec_tests/data/crud_unified/deleteOne-errorResponse.yml @@ -0,0 +1,46 @@ +description: "deleteOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "delete operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ delete ] + errorCode: &errorCode 8 # UnknownError + - name: deleteOne + object: *collection0 + arguments: + filter: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/spec/spec_tests/data/crud_unified/findOneAndUpdate-errorResponse.yml b/spec/spec_tests/data/crud_unified/findOneAndUpdate-errorResponse.yml new file mode 100644 index 0000000000..8faed76809 --- /dev/null +++ b/spec/spec_tests/data/crud_unified/findOneAndUpdate-errorResponse.yml @@ -0,0 +1,69 @@ +description: "findOneAndUpdate-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: "foo" } + +tests: + - description: "findOneAndUpdate DuplicateKey error is accessible" + runOnRequirements: + - minServerVersion: "4.2" # SERVER-37124 + operations: + - name: createIndex + object: *collection0 + arguments: + keys: { x: 1 } + unique: true + - name: findOneAndUpdate + object: *collection0 + arguments: + filter: { _id: 2 } + update: { $set: { x: "foo" } } + upsert: true + expectError: + errorCode: 11000 # DuplicateKey + errorResponse: + keyPattern: { x: 1 } + keyValue: { x: "foo" } + + - description: "findOneAndUpdate document validation errInfo is accessible" + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + validator: + x: { $type: "string" } + - name: findOneAndUpdate + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorCode: 121 # DocumentValidationFailure + errorResponse: + # Avoid asserting the exact contents of errInfo as it may vary by + # server version. Likewise, this is why drivers do not model the + # document. The following is sufficient to test that validation + # details are accessible. See SERVER-20547 for more context. + errInfo: + failingDocumentId: 1 + details: { $$type: "object" } diff --git a/spec/spec_tests/data/crud_unified/insertOne-errorResponse.yml b/spec/spec_tests/data/crud_unified/insertOne-errorResponse.yml new file mode 100644 index 0000000000..b14caa1737 --- /dev/null +++ b/spec/spec_tests/data/crud_unified/insertOne-errorResponse.yml @@ -0,0 +1,46 @@ +description: "insertOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "insert operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: &errorCode 8 # UnknownError + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/spec/spec_tests/data/crud_unified/updateOne-errorResponse.yml b/spec/spec_tests/data/crud_unified/updateOne-errorResponse.yml new file mode 100644 index 0000000000..6d42195b0b --- /dev/null +++ b/spec/spec_tests/data/crud_unified/updateOne-errorResponse.yml @@ -0,0 +1,47 @@ +description: "updateOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "update operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: &errorCode 8 # UnknownError + - name: updateOne + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/spec/spec_tests/data/unified/valid-pass/expectedError-errorResponse.yml b/spec/spec_tests/data/unified/valid-pass/expectedError-errorResponse.yml new file mode 100644 index 0000000000..e10c25a1ed --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/expectedError-errorResponse.yml @@ -0,0 +1,39 @@ +description: "expectedError-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +tests: + - description: "Unsupported command" + operations: + - name: runCommand + object: *database0 + arguments: + commandName: unsupportedCommand + command: { unsupportedCommand: 1 } + expectError: + # Avoid asserting the exact error since it may vary by server version + errorResponse: + errmsg: { $$type: "string" } + + - description: "Unsupported query operator" + operations: + - name: find + object: *collection0 + arguments: + filter: { $unsupportedQueryOperator: 1 } + expectError: + # Avoid asserting the exact error since it may vary by server version + errorResponse: + errmsg: { $$type: "string" } From e4df91b3410284926ff85ea06589242c14f5d8c4 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 9 Nov 2022 17:19:32 -0500 Subject: [PATCH 074/198] RUBY-2927 Preemptively cancel in progress operations when SDAM heartbeats timeout (#2649) * RUBY-2599 Account for backgroundThreadIntervalMS option * RUBY-2927 implement first test, partial version of option * fix typo, drive-by * RUBY-2927 add failpoint and runon * RUBY-2927 dont use mock socket * RUBY-2927 fix interrupting pending connections * RUBY-2927 fix all cmap tests * create pool cleared error * RUBY-2927 fix all spec tests; almost one TODO left * RUBY-2927 fix error * RUBY-2927 patch test * RUBY-2927 move code * RUBY-2927 raise on scan * RUBY_2927 fix tests * RUBY-2927 fix tests * move do check in * RUBY-2927 interrupt connections in populator * RUBY-2927 add connection pool tests * RUBY-2927 fix live thread errors * RUBY-2927 add lint error for readying a pool for a disconnected server * fix tests * use select * RUBY-2927 dont start pool thread when monitoring_io is off * remove debug stuff * RUBY-2927 start populator threads in cmap spec * Update lib/mongo/server/connection_pool.rb * Update lib/mongo/server/connection_pool.rb * RUBY-2927 close pools at the end of tests * fix live background threads * RUBY-2927 close pool after transaction spec * RUBY-2927 fix more live background thread errors * add comment * add backtrace * more debugging * disable populator on srv_monitoring tests * add comment * add comment * kill threads after run, store client in instance variable * fix error * make select timeout smaller * dont go past select_timeout * RUBY-2927 update socket_timeout * wait for condition * put back select and skip linux * update test to skip linux * RUBY-2927 select_timeout * Interrupt all connections at once * fix connection_pool_spec * RUBY-2927 Add pipes to select and interrupt * make pool_cleared_error inherit from PoolError * RUBY-2927 fix test failures * RUBY-2927 make populator_io a valid option * update pool cleared error creation * abstract pool cleared raising * RUBY-2927 attempt to fix live background thread errors * more live background thread fixes * add additional check * test formatting * remove byebug * RUBY-2927 reconnect closed client * RUBY-2927 clean up * attempt to fix fle tests * RUBY-2927 fix error * remove duplicate code * remove more duplicate code * restrict test to no lbs * RUBY-2927 fix select fds * RUBY-2927 pend more tests on lb * fix lb tests * pend another lb test * make pool paused retryable * fix server unusable error * RUBY-2927 comments * get rid of debug * fix retryable changes * rename method * rename fd to pipe * fix comment * refactor remove interrupt * RUBY-2927 update comment and check for generation * Apply suggestions from code review Co-authored-by: Dmitry Rybakov * correct connection_pool_spec tests * correct the test * RUBY-2927 ix error * Revert "get rid of debug" This reverts commit 42ca1b4a62f9d95dafc5b4b091af2da493445727. * fix fle tests * fix race condition * get rid of debug * fix deadlock * further cleanup * RUBY-2927 refactor * add punctuation * fix client_update_spec * attempt to fix timeout error * Don't pause the pool when server is known * fix connection pool tests * fix test * ensure pool is paused * fix more jruby tests * fix lb tests * add pause debugging * update logs * more debugging * more debug * add debugging to pool cleared * put back client construction and fail jruby * add interrupt for load balancers * ready the pool before updating the description * Revert "get rid of debug" This reverts commit e0359a513dc8f908fc4e430e366e078b4fdf4ec4. * get rid of problematic assertion * RUBY-2927 get rid of pause debugging * get rid of all debugging * add test backtrace * Apply suggestions from code review * Revert "add test backtrace" This reverts commit 745bf58df1610fe185e7960f091c6d2d814eef32. * Revert "get rid of debug" This reverts commit e0359a513dc8f908fc4e430e366e078b4fdf4ec4. * Revert "Revert "get rid of debug"" This reverts commit cabcc4a33c9433c3615955244554ace82129d1af. * Revert "Revert "Revert "get rid of debug""" This reverts commit 5803a8826116fcff8a0c26c0673bb644183fb6b9. * fix live background thread * Revert "Revert "Revert "Revert "get rid of debug"""" This reverts commit 889d67751f88d2d7e9ab426999161d4f4696675f. Co-authored-by: Dmitry Rybakov --- lib/mongo/client.rb | 1 + lib/mongo/cluster.rb | 15 +- lib/mongo/crypt/auto_encrypter.rb | 1 + lib/mongo/error.rb | 1 + lib/mongo/error/pool_cleared_error.rb | 40 ++ lib/mongo/error/pool_paused_error.rb | 2 + .../monitoring/event/cmap/pool_cleared.rb | 9 +- lib/mongo/retryable.rb | 37 +- lib/mongo/server.rb | 19 +- lib/mongo/server/connection.rb | 50 ++- lib/mongo/server/connection_pool.rb | 341 +++++++++++----- .../connection_pool/generation_manager.rb | 35 +- lib/mongo/server/monitor.rb | 16 +- lib/mongo/socket.rb | 26 +- .../auto_encryption_reconnect_spec.rb | 5 +- spec/integration/client_update_spec.rb | 4 + .../connection_pool_populator_spec.rb | 22 +- spec/integration/cursor_pinning_spec.rb | 10 +- spec/integration/sdam_error_handling_spec.rb | 1 + spec/integration/server_spec.rb | 15 +- spec/integration/srv_monitoring_spec.rb | 1 + spec/integration/transaction_pinning_spec.rb | 9 +- spec/mongo/client_construction_spec.rb | 4 +- spec/mongo/cursor_spec.rb | 12 + .../server/connection_pool/populator_spec.rb | 14 +- spec/mongo/server/connection_pool_spec.rb | 380 +++++++++++++++++- spec/mongo/server/monitor_spec.rb | 5 +- spec/mongo/server_spec.rb | 5 +- spec/runners/cmap.rb | 77 +++- spec/runners/unified/assertions.rb | 3 + spec/runners/unified/test.rb | 2 +- spec/spec_tests/cmap_spec.rb | 21 +- .../cmap/pool-clear-interrupt-immediately.yml | 49 +++ ...clear-interrupting-pending-connections.yml | 43 ++ ...le-run-interruptInUseConnections-false.yml | 48 +++ .../data/sdam_unified/hello-timeout.yml | 2 +- .../interruptInUse-pool-clear.yml | 340 ++++++++++++++++ .../rediscover-quickly-after-step-down.yml | 2 +- spec/support/common_shortcuts.rb | 5 + 39 files changed, 1474 insertions(+), 198 deletions(-) create mode 100644 lib/mongo/error/pool_cleared_error.rb create mode 100644 spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml create mode 100644 spec/spec_tests/data/cmap/pool-clear-interrupting-pending-connections.yml create mode 100644 spec/spec_tests/data/cmap/pool-clear-schedule-run-interruptInUseConnections-false.yml create mode 100644 spec/spec_tests/data/sdam_unified/interruptInUse-pool-clear.yml diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index 9958100833..d740425cf5 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -80,6 +80,7 @@ class Client :monitoring_io, :password, :platform, + :populator_io, :read, :read_concern, :read_retry_interval, diff --git a/lib/mongo/cluster.rb b/lib/mongo/cluster.rb index 161c88506e..561c5d20f3 100644 --- a/lib/mongo/cluster.rb +++ b/lib/mongo/cluster.rb @@ -625,10 +625,12 @@ def scan!(sync=true) # respective server is cleared. Set this option to true to keep the # existing connection pool (required when handling not master errors # on 4.2+ servers). - # @option aptions [ true | false ] :awaited Whether the updated description + # @option options [ true | false ] :awaited Whether the updated description # was a result of processing an awaited hello. # @option options [ Object ] :service_id Change state for the specified # service id only. + # @option options [ Mongo::Error | nil ] :scan_error The error encountered + # while scanning, or nil if no error was raised. # # @api private def run_sdam_flow(previous_desc, updated_desc, options = {}) @@ -639,7 +641,9 @@ def run_sdam_flow(previous_desc, updated_desc, options = {}) # TODO should service id be taken out of updated_desc? # We could also assert that # options[:service_id] == updated_desc.service_id - server.clear_connection_pool(service_id: options[:service_id]) + err = options[:scan_error] + interrupt = err && (err.is_a?(Error::SocketError) || err.is_a?(Error::SocketTimeoutError)) + server.clear_connection_pool(service_id: options[:service_id], interrupt_in_use_connections: interrupt) end end end @@ -659,7 +663,9 @@ def run_sdam_flow(previous_desc, updated_desc, options = {}) if flow.became_unknown? servers_list.each do |server| if server.address == updated_desc.address - server.clear_connection_pool + err = options[:scan_error] + interrupt = err && (err.is_a?(Error::SocketError) || err.is_a?(Error::SocketTimeoutError)) + server.clear_connection_pool(interrupt_in_use_connections: interrupt) end end end @@ -831,6 +837,9 @@ def add(host, add_options=nil) address = Address.new(host, options) if !addresses.include?(address) opts = options.merge(monitor: false) + # If we aren't starting the montoring threads, we also don't want to + # start the pool's populator thread. + opts.merge!(populator_io: false) unless options.fetch(:monitoring_io, true) # Note that in a load-balanced topology, every server must be a # load balancer (load_balancer: true is specified in the options) # but this option isn't set here because we are required by the diff --git a/lib/mongo/crypt/auto_encrypter.rb b/lib/mongo/crypt/auto_encrypter.rb index 413e282e1f..1ac24430fd 100644 --- a/lib/mongo/crypt/auto_encrypter.rb +++ b/lib/mongo/crypt/auto_encrypter.rb @@ -123,6 +123,7 @@ def initialize(options) @mongocryptd_client = Client.new( @options[:extra_options][:mongocryptd_uri], monitoring_io: @options[:client].options[:monitoring_io], + populator_io: @options[:client].options[:populator_io], server_selection_timeout: 10, database: @options[:client].options[:database] ) diff --git a/lib/mongo/error.rb b/lib/mongo/error.rb index 409a8496fb..bf5df68851 100644 --- a/lib/mongo/error.rb +++ b/lib/mongo/error.rb @@ -201,6 +201,7 @@ def write_concern_error_labels require 'mongo/error/raise_original_error' require 'mongo/error/server_certificate_revoked' require 'mongo/error/socket_error' +require 'mongo/error/pool_cleared_error' require 'mongo/error/socket_timeout_error' require 'mongo/error/failed_string_prep_validation' require 'mongo/error/unchangeable_collection_option' diff --git a/lib/mongo/error/pool_cleared_error.rb b/lib/mongo/error/pool_cleared_error.rb new file mode 100644 index 0000000000..00de42b7af --- /dev/null +++ b/lib/mongo/error/pool_cleared_error.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-present MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Error + + # Exception raised if an operation is attempted connection that was + # interrupted due to server monitor timeout. + class PoolClearedError < PoolError + include WriteRetryable + include ChangeStreamResumable + + # Instantiate the new exception. + # + # @example Instantiate the exception. + # Mongo::Error::PoolClearedError.new(address, pool) + # + # @api private + def initialize(address, pool) + super(address, pool, + "Connection to #{address} interrupted due to server monitor timeout " + + "(for pool 0x#{pool.object_id})") + end + end + end +end diff --git a/lib/mongo/error/pool_paused_error.rb b/lib/mongo/error/pool_paused_error.rb index de0a1d3857..3f3d71b5b8 100644 --- a/lib/mongo/error/pool_paused_error.rb +++ b/lib/mongo/error/pool_paused_error.rb @@ -20,6 +20,8 @@ class Error # Exception raised if an operation is attempted on a paused connection pool. class PoolPausedError < PoolError + include WriteRetryable + include ChangeStreamResumable # Instantiate the new exception. # diff --git a/lib/mongo/monitoring/event/cmap/pool_cleared.rb b/lib/mongo/monitoring/event/cmap/pool_cleared.rb index 82ce9dd252..6725d0cc35 100644 --- a/lib/mongo/monitoring/event/cmap/pool_cleared.rb +++ b/lib/mongo/monitoring/event/cmap/pool_cleared.rb @@ -34,15 +34,22 @@ class PoolCleared < Base # @return [ nil | Object ] The service id, if any. attr_reader :service_id + # @return [ Hash ] options The options + attr_reader :options + # Create the event. # # @param [ Address ] address # @param [ Object ] service_id The service id, if any. + # @param [ true | false | nil ] interrupt_in_use_connections The + # interrupt_in_use_connections flag, if given. # # @api private - def initialize(address, service_id: nil) + def initialize(address, service_id: nil, interrupt_in_use_connections: nil) @address = address @service_id = service_id + @options = {} + @options[:interrupt_in_use_connections] = interrupt_in_use_connections end # Returns a concise yet useful summary of the event. diff --git a/lib/mongo/retryable.rb b/lib/mongo/retryable.rb index bec730238d..5a75d1a896 100644 --- a/lib/mongo/retryable.rb +++ b/lib/mongo/retryable.rb @@ -131,7 +131,7 @@ def read_with_retry(session = nil, server_selector = nil, &block) server = select_server(cluster, server_selector, session) begin yield server - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::OperationFailure => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError, Error::OperationFailure => e e.add_note('retries disabled') raise e end @@ -163,10 +163,14 @@ def read_with_retry(session = nil, server_selector = nil, &block) # @since 2.2.6 def read_with_one_retry(options = nil) yield - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable => e - retry_message = options && options[:retry_message] - log_retry(e, message: retry_message) - yield + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError => e + if e.write_retryable? + retry_message = options && options[:retry_message] + log_retry(e, message: retry_message) + yield + else + raise e + end end # Implements write retrying functionality by yielding to the passed @@ -243,7 +247,7 @@ def write_with_retry(write_concern, ending_transaction: false, context:, &block) # it later for the retry as well. yield(connection, txn_num, context.dup) end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Auth::Unauthorized => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError, Auth::Unauthorized => e e.add_note('modern retry') e.add_note("attempt 1") if !e.label?('RetryableWriteError') @@ -304,7 +308,7 @@ def nro_write_with_retry(write_concern, context:, &block) server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield connection, nil, context end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::OperationFailure => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError, Error::OperationFailure => e e.add_note('retries disabled') raise e end @@ -374,7 +378,7 @@ def modern_read_with_retry(session, server_selector, &block) raise e end retry_read(e, server_selector, session, &block) - rescue Error::OperationFailure, Auth::Unauthorized => e + rescue Error::OperationFailure, Auth::Unauthorized, Error::PoolError => e e.add_note('modern retry') e.add_note("attempt 1") if session.in_transaction? || !e.write_retryable? @@ -399,7 +403,7 @@ def legacy_read_with_retry(session, server_selector) log_retry(e, message: 'Legacy read retry') server = select_server(cluster, server_selector, session) retry - rescue Error::OperationFailure => e + rescue Error::OperationFailure, Error::PoolError => e e.add_note('legacy retry') e.add_note("attempt #{attempt}") if e.retryable? && !(session && session.in_transaction?) @@ -449,7 +453,7 @@ def retry_read(original_error, server_selector, session, &block) e.add_note('modern retry') e.add_note("attempt 2") raise e - rescue Error::OperationFailure => e + rescue Error::OperationFailure, Error::PoolError => e e.add_note('modern retry') unless e.write_retryable? original_error.add_note("later retry failed: #{e.class}: #{e}") @@ -492,10 +496,15 @@ def retry_write(original_error, txn_num, context:, &block) server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield(connection, txn_num, context) end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable => e - e.add_note('modern retry') - e.add_note('attempt 2') - raise e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError => e + if e.write_retryable? + e.add_note('modern retry') + e.add_note('attempt 2') + raise e + else + original_error.add_note("later retry failed: #{e.class}: #{e}") + raise original_error + end rescue Error::OperationFailure => e e.add_note('modern retry') if e.label?('RetryableWriteError') && !e.label?('NoWritesPerformed') diff --git a/lib/mongo/server.rb b/lib/mongo/server.rb index 2a3ef29ba3..9eec1de2c9 100644 --- a/lib/mongo/server.rb +++ b/lib/mongo/server.rb @@ -54,6 +54,15 @@ class Server # done by this server. Note: setting this option to false will make # the server non-functional. It is intended for use in tests which # manually invoke SDAM state transitions. + # @option options [ true, false ] :populator_io For internal driver + # use only. Set to false to prevent the populator threads from being + # created and started in the server's connection pool. It is intended + # for use in tests that also turn off monitoring_io, unless the populator + # is explicitly needed. If monitoring_io is off, but the populator_io + # is on, the populator needs to be manually closed at the end of the + # test, since a cluster without monitoring is considered not connected, + # and thus will not clean up the connection pool populator threads on + # close. # @option options [ true | false ] :load_balancer Whether this server # is a load balancer. # @option options [ String ] :connect The client connection mode. @@ -630,11 +639,11 @@ def unknown!(options = {}) # @api private def update_description(description) - @description = description pool = pool_internal - if pool && !unknown? + if pool && !description.unknown? pool.ready end + @description = description end # Clear the servers description so that it is considered unknown and can be @@ -647,16 +656,18 @@ def clear_description # @param [ Object ] :service_id Close connections with the specified # service id only. + # @param [ true | false ] :interrupt_in_use_connections Whether or not the + # cleared connections should be interrupted as well. # # @api private - def clear_connection_pool(service_id: nil) + def clear_connection_pool(service_id: nil, interrupt_in_use_connections: false) @pool_lock.synchronize do # A server being marked unknown after it is closed is technically # incorrect but it does not meaningfully alter any state. # Because historically the driver permitted servers to be marked # unknown at any time, continue doing so even if the pool is closed. if @pool && !@pool.closed? - @pool.disconnect!(service_id: service_id) + @pool.disconnect!(service_id: service_id, interrupt_in_use_connections: interrupt_in_use_connections) end end end diff --git a/lib/mongo/server/connection.rb b/lib/mongo/server/connection.rb index ce19d8143b..d5344eba2c 100644 --- a/lib/mongo/server/connection.rb +++ b/lib/mongo/server/connection.rb @@ -87,6 +87,9 @@ class Connection < ConnectionBase # @param [ Mongo::Server ] server The server the connection is for. # @param [ Hash ] options The connection options. # + # @option options :pipe [ IO ] The file descriptor for the read end of the + # pipe to listen on during the select system call when reading from the + # socket. # @option options [ Integer ] :generation The generation of this # connection. The generation should only be specified in this option # when not in load-balancing mode, and it should be the generation @@ -156,6 +159,21 @@ def closed? !!@closed end + # Whether the connection was interrupted. + # + # Interrupted connections were already removed from the pool and should + # not be checked back into the pool. + # + # @return [ true | false ] Whether connection was interrupted. + def interrupted? + !!@interrupted + end + + # Mark the connection as interrupted. + def interrupted! + @interrupted = true + end + # @api private def error? !!@error @@ -212,7 +230,8 @@ def connect! unless @socket # When @socket is assigned, the socket should have handshaken and # authenticated and be usable. - @socket, @description, @compressor = do_connect + @socket = create_socket + @description, @compressor = do_connect if server.load_balancer? if Lint.enabled? @@ -232,27 +251,35 @@ def connect! true end - # Separate method to permit easier mocking in the test suite. + # Creates the socket. The method is separate from do_connect, so that + # pending connections can be closed if they are interrupted during hello. # - # @return [ Array ] Connected socket and - # a server description instance from the hello response of the - # returned socket. - private def do_connect - socket = add_server_diagnostics do + # + # @return [ Socket ] The created socket. + private def create_socket + add_server_diagnostics do address.socket(socket_timeout, ssl_options.merge( - connection_address: address, connection_generation: generation)) + connection_address: address, connection_generation: generation, pipe: options[:pipe])) end + end + # Separate method to permit easier mocking in the test suite. + # + # @return [ Array ] A server + # description instance from the hello response of the returned socket + # and the compressor to use. + private def do_connect begin pending_connection = PendingConnection.new( socket, @server, monitoring, options.merge(id: id)) pending_connection.handshake_and_authenticate! rescue Exception - socket.close + socket&.close + @socket = nil raise end - [socket, pending_connection.description, pending_connection.compressor] + [pending_connection.description, pending_connection.compressor] end # Disconnect the connection. @@ -268,6 +295,8 @@ def connect! # # @option options [ Symbol ] :reason The reason why the connection is # being closed. + # @option options [ true | false ] :interrupted Whether or not the + # connection was interrupted. # # @return [ true ] If the disconnect succeeded. # @@ -282,6 +311,7 @@ def disconnect!(options = nil) @socket = nil end @closed = true + interrupted! if options && options[:interrupted] # To satisfy CMAP spec tests, publish close events even if the # socket was never connected (and thus the ready event was never diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index ad8f3f0527..645084a538 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -75,6 +75,15 @@ class ConnectionPool # are given, their values must be identical. # @option options [ Float ] :max_idle_time The time, in seconds, # after which idle connections should be closed by the pool. + # @option options [ true, false ] :populator_io For internal driver + # use only. Set to false to prevent the populator threads from being + # created and started in the server's connection pool. It is intended + # for use in tests that also turn off monitoring_io, unless the populator + # is explicitly needed. If monitoring_io is off, but the populator_io + # is on, the populator needs to be manually closed at the end of the + # test, since a cluster without monitoring is considered not connected, + # and thus will not clean up the connection pool populator threads on + # close. # Note: Additionally, options for connections created by this pool should # be included in the options passed here, and they will be forwarded to # any connections created by the pool. @@ -121,6 +130,7 @@ def initialize(server, options = {}) @available_connections = available_connections = [] @checked_out_connections = Set.new @pending_connections = Set.new + @interrupt_connections = [] # Mutex used for synchronizing access to @available_connections and # @checked_out_connections. The pool object is thread-safe, thus @@ -510,77 +520,101 @@ def check_in(connection) check_invariants @lock.synchronize do - unless connection.connection_pool == self - raise ArgumentError, "Trying to check in a connection which was not checked out by this pool: #{connection} checked out from pool #{connection.connection_pool} (for #{self})" - end + do_check_in(connection) + end + ensure + check_invariants + end - unless @checked_out_connections.include?(connection) - raise ArgumentError, "Trying to check in a connection which is not currently checked out by this pool: #{connection} (for #{self})" - end + # Executes the check in after having already acquired the lock. + # + # @param [ Mongo::Server::Connection ] connection The connection. + def do_check_in(connection) + # When a connection is interrupted it is checked back into the pool + # and closed. The operation that was using the connection before it was + # interrupted will attempt to check it back into the pool, and we + # should ignore it since its already been closed and removed from the pool. + return if connection.closed? && connection.interrupted? + + unless connection.connection_pool == self + raise ArgumentError, "Trying to check in a connection which was not checked out by this pool: #{connection} checked out from pool #{connection.connection_pool} (for #{self})" + end - # Note: if an event handler raises, resource will not be signaled. - # This means threads waiting for a connection to free up when - # the pool is at max size may time out. - # Threads that begin waiting after this method completes (with - # the exception) should be fine. + unless @checked_out_connections.include?(connection) + raise ArgumentError, "Trying to check in a connection which is not currently checked out by this pool: #{connection} (for #{self})" + end - @checked_out_connections.delete(connection) - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckedIn.new(@server.address, connection.id, self) - ) + # Note: if an event handler raises, resource will not be signaled. + # This means threads waiting for a connection to free up when + # the pool is at max size may time out. + # Threads that begin waiting after this method completes (with + # the exception) should be fine. - if connection.error? - connection.disconnect!(reason: :error) - return - end + @checked_out_connections.delete(connection) + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckedIn.new(@server.address, connection.id, self) + ) - if closed? - connection.disconnect!(reason: :pool_closed) - return - end + if connection.interrupted? + connection.disconnect!(reason: :stale) + return + end - if connection.closed? - # Connection was closed - for example, because it experienced - # a network error. Nothing else needs to be done here. - @populate_semaphore.signal - elsif connection.generation != generation(service_id: connection.service_id) && !connection.pinned? - # If connection is marked as pinned, it is used by a transaction - # or a series of cursor operations in a load balanced setup. - # In this case connection should not be disconnected until - # unpinned. - connection.disconnect!(reason: :stale) - @populate_semaphore.signal - else - connection.record_checkin! - @available_connections << connection + if connection.error? + connection.disconnect!(reason: :error) + return + end - # Wake up only one thread waiting for an available connection, - # since only one connection was checked in. - @available_semaphore.signal - end + if closed? + connection.disconnect!(reason: :pool_closed) + return + end + + if connection.closed? + # Connection was closed - for example, because it experienced + # a network error. Nothing else needs to be done here. + @populate_semaphore.signal + elsif connection.generation != generation(service_id: connection.service_id) && !connection.pinned? + # If connection is marked as pinned, it is used by a transaction + # or a series of cursor operations in a load balanced setup. + # In this case connection should not be disconnected until + # unpinned. + connection.disconnect!(reason: :stale) + @populate_semaphore.signal + else + connection.record_checkin! + @available_connections << connection + + # Wake up only one thread waiting for an available connection, + # since only one connection was checked in. + @available_semaphore.signal end - ensure - check_invariants end + # Mark the connection pool as paused. def pause raise_if_closed! check_invariants + @lock.synchronize do + do_pause + end + ensure + check_invariants + end + + # Mark the connection pool as paused without acquiring the lock. + # + # @api private + def do_pause if Lint.enabled? && !@server.unknown? raise Error::LintError, "Attempting to pause pool for server #{@server.summary} which is known" end - return if paused? - - @lock.synchronize do - @ready = false - end + return if !@ready - stop_populator - ensure - check_invariants + @ready = false end # Closes all idle connections in the pool and schedules currently checked @@ -590,7 +624,10 @@ def pause # # @option options [ true | false ] :lazy If true, do not close any of # the idle connections and instead let them be closed during a - # subsequent check out operation. + # subsequent check out operation. Defaults to false. + # @option options [ true | false ] :interrupt_in_use_connections If true, + # close all checked out connections immediately. If it is false, do not + # close any of the checked out connections. Defaults to true. # @option options [ Object ] :service_id Clear connections with # the specified service id only. # @@ -630,43 +667,33 @@ def do_clear(options = nil) @lock.synchronize do # Generation must be bumped before emitting pool cleared event. @generation_manager.bump(service_id: service_id) - end - unless paused? - publish_cmap_event( - Monitoring::Event::Cmap::PoolCleared.new( - @server.address, - service_id: service_id - ) - ) - pause unless @server.load_balancer? - end - - @lock.synchronize do unless options && options[:lazy] - if @server.load_balancer? && service_id - loop do - conn = @available_connections.detect do |conn| - conn.service_id == service_id - end - if conn - @available_connections.delete(conn) - conn.disconnect!(reason: :stale) - @populate_semaphore.signal - else - break - end - end - else - until @available_connections.empty? - connection = @available_connections.pop - connection.disconnect!(reason: :stale) - @populate_semaphore.signal - end - end + close_connections(@available_connections, service_id) + end + + if options && options[:interrupt_in_use_connections] + schedule_for_interruption(@checked_out_connections, service_id) + schedule_for_interruption(@pending_connections, service_id) + end + + if @ready + publish_cmap_event( + Monitoring::Event::Cmap::PoolCleared.new( + @server.address, + service_id: service_id, + interrupt_in_use_connections: options&.[](:interrupt_in_use_connections) + ) + ) + # Only pause the connection pool if the server was marked unknown, + # otherwise, allow the retry to be attempted with a ready pool. + do_pause if !@server.load_balancer? && @server.unknown? end end + # "Schedule the background thread" after clearing. This is responsible + # for cleaning up stale threads, and interrupting in use connections. + @populate_semaphore.signal true ensure check_invariants @@ -676,6 +703,12 @@ def do_clear(options = nil) def ready raise_if_closed! + if Lint.enabled? + unless @server.connected? + raise Error::LintError, "Attempting to ready a pool for server #{@server.summary} which is disconnected" + end + end + @lock.synchronize do return if @ready @@ -694,7 +727,13 @@ def ready Monitoring::Event::Cmap::PoolReady.new(@server.address, options, self) ) - @populator.run! if min_size > 0 + if options.fetch(:populator_io, true) + if @populator.running? + @populate_semaphore.signal + else + @populator.run! + end + end end # Marks the pool closed, closes all idle connections in the pool and @@ -733,6 +772,7 @@ def close(options = nil) # mark pool as closed before releasing lock so # no connections can be created, checked in, or checked out @closed = true + @ready = false end publish_cmap_event( @@ -778,6 +818,8 @@ def with_connection(connection_global_id: nil) connection = check_out(connection_global_id: connection_global_id) yield(connection) + rescue Error::SocketError, Error::SocketTimeoutError => e + maybe_raise_pool_cleared!(connection, e) ensure if connection check_in(connection) @@ -834,9 +876,13 @@ def stop_populator end end - # Creates and adds a connection to the pool, if the pool's size is below - # min_size. Retries once if a socket-related error is encountered during - # this process and raises if a second error or a non socket-related error occurs. + # This method does three things: + # 1. Creates and adds a connection to the pool, if the pool's size is + # below min_size. Retries once if a socket-related error is + # encountered during this process and raises if a second error or a + # non socket-related error occurs. + # 2. Removes stale connections from the connection pool. + # 3. Interrupts connections marked for interruption. # # Used by the pool populator background thread. # @@ -910,8 +956,10 @@ def next_available_connection(connection_global_id: nil) end def create_connection + r, _ = @generation_manager.pipe_fds(service_id: server.description.service_id) opts = options.merge( connection_pool: self, + pipe: r # Do not pass app metadata - this will be retrieved by the connection # based on the auth needs. ) @@ -921,7 +969,8 @@ def create_connection Connection.new(@server, opts) end - # Create a connection, connect it, and add it to the pool. + # Create a connection, connect it, and add it to the pool. Also + # check for stale and interruptable connections and deal with them. # # @return [ true | false ] True if a connection was created and # added to the pool, false otherwise @@ -930,18 +979,12 @@ def create_and_add_connection connection = nil @lock.synchronize do - if !closed? && unsynchronized_size < min_size + if !closed? && @ready && unsynchronized_size < min_size connection = create_connection @pending_connections << connection else - conn = @available_connections.detect do |conn| - connection_stale_unlocked?(conn) - end - if conn - conn.disconnect!(reason: :stale) - @available_connections.delete(conn) - return true - end + return true if remove_interrupted_connections + return true if remove_stale_connection return false end end @@ -966,6 +1009,48 @@ def create_and_add_connection true end + # Removes and disconnects all stale available connections. + def remove_stale_connection + if conn = @available_connections.detect(&method(:connection_stale_unlocked?)) + conn.disconnect!(reason: :stale) + @available_connections.delete(conn) + return true + end + end + + # Interrupt connections scheduled for interruption. + def remove_interrupted_connections + return false if @interrupt_connections.empty? + + gens = Set.new + while conn = @interrupt_connections.pop + if @checked_out_connections.include?(conn) + # If the connection has been checked out, mark it as interrupted and it will + # be disconnected on check in. + conn.interrupted! + do_check_in(conn) + elsif @pending_connections.include?(conn) + # If the connection is pending, disconnect with the interrupted flag. + conn.disconnect!(reason: :stale, interrupted: true) + @pending_connections.delete(conn) + end + gens << [ conn.generation, conn.service_id ] + end + + # Close the write side of the pipe. Pending connections might be + # hanging on the Kernel#select call, so in order to interrupt that, + # we also listen for the read side of the pipe in Kernel#select and + # close the write side of the pipe here, which will cause select to + # wake up and raise an IOError now that the socket is closed. + # The read side of the pipe will be scheduled for closing on the next + # generation bump. + gens.each do |gen, service_id| + @generation_manager.remove_pipe_fds(gen, service_id: service_id) + end + + true + end + # Checks whether a connection is stale. # # @param [ Mongo::Server::Connection ] connection The connection to check. @@ -987,6 +1072,25 @@ def raise_if_closed! end end + # If the connection was interrupted, raise a pool cleared error. If it + # wasn't interrupted raise the original error. + # + # @param [ Connection ] The connection. + # @param [ Mongo::Error ] The original error. + # + # @raise [ Mongo::Error | Mongo::Error::PoolClearedError ] A PoolClearedError + # if the connection was interrupted, the original error if not. + def maybe_raise_pool_cleared!(connection, e) + if connection&.interrupted? + err = Error::PoolClearedError.new(connection.server.address, connection.server.pool_internal).tap do |err| + e.labels.each { |l| err.add_label(l) } + end + raise err + else + raise e + end + end + # Attempts to connect (handshake and auth) the connection. If an error is # encountered, closes the connection and raises the error. def connect_connection(connection) @@ -1026,6 +1130,47 @@ def check_invariants end end end + + # Close the connections in the given list. + # + # @param [ Array ] connections A list of connections. + # @param [ Object ] service_id The service id. + def close_connections(connections, service_id) + if @server.load_balancer? && service_id + loop do + conn = connections.detect do |conn| + conn.service_id == service_id && + conn.generation < @generation_manager.generation(service_id: service_id) + end + if conn + connections.delete(conn) + conn.disconnect!(reason: :stale, interrupted: true) + @populate_semaphore.signal + else + break + end + end + else + connections.delete_if do |conn| + if conn.generation < @generation_manager.generation(service_id: service_id) + conn.disconnect!(reason: :stale, interrupted: true) + @populate_semaphore.signal + true + end + end + end + end + + # Schedule connections of previous generations for interruption. + # + # @param [ Array ] connections A list of connections. + # @param [ Object ] service_id The service id. + def schedule_for_interruption(connections, service_id) + @interrupt_connections += connections.select do |conn| + (!server.load_balancer? || conn.service_id == service_id) && + conn.generation < @generation_manager.generation(service_id: service_id) + end + end end end end diff --git a/lib/mongo/server/connection_pool/generation_manager.rb b/lib/mongo/server/connection_pool/generation_manager.rb index be4fbb0ef0..578e87b4c7 100644 --- a/lib/mongo/server/connection_pool/generation_manager.rb +++ b/lib/mongo/server/connection_pool/generation_manager.rb @@ -24,8 +24,10 @@ class GenerationManager def initialize(server:) @map = Hash.new { |hash, key| hash[key] = 1 } + @pipe_fds = Hash.new { |hash, key| hash[key] = { 1 => IO.pipe } } @server = server @lock = Mutex.new + @scheduled_for_close = [] end attr_reader :server @@ -44,10 +46,30 @@ def generation_unlocked(service_id: nil) @map[service_id] end + def pipe_fds(service_id: nil) + @pipe_fds[service_id][@map[service_id]] + end + + def remove_pipe_fds(generation, service_id: nil) + validate_service_id!(service_id) + + r, w = @pipe_fds[service_id].delete(generation) + w.close + # Schedule the read end of the pipe to be closed. We cannot close it + # immediately since we need to wait for any Kernel#select calls to + # notice that part of the pipe is closed, and check the socket. This + # all happens when attempting to read from the socket and waiting for + # it to become ready again. + @scheduled_for_close << r + end + def bump(service_id: nil) @lock.synchronize do + close_all_scheduled if service_id - @map[service_id] += 1 + gen = @map[service_id] += 1 + @pipe_fds[service_id] ||= {} + @pipe_fds[service_id][gen] = IO.pipe else # When service id is not supplied, one of two things may be # happening; @@ -59,7 +81,9 @@ def bump(service_id: nil) # # Incrementing everything in the map accomplishes both tasks. @map.each do |k, v| - @map[k] += 1 + gen = @map[k] += 1 + @pipe_fds[service_id] ||= {} + @pipe_fds[service_id][gen] = IO.pipe end end end @@ -78,6 +102,13 @@ def validate_service_id!(service_id) end end end + + # Close all fds scheduled for closing. + def close_all_scheduled + while pipe = @scheduled_for_close.pop + pipe.close + end + end end end end diff --git a/lib/mongo/server/monitor.rb b/lib/mongo/server/monitor.rb index 4b06bfa459..f753eb7ff1 100644 --- a/lib/mongo/server/monitor.rb +++ b/lib/mongo/server/monitor.rb @@ -223,13 +223,17 @@ def scan! @mutex.synchronize do throttle_scan_frequency! - result = do_scan - - run_sdam_flow(result) + begin + result = do_scan + rescue => e + run_sdam_flow({}, scan_error: e) + else + run_sdam_flow(result) + end end end - def run_sdam_flow(result, awaited: false) + def run_sdam_flow(result, awaited: false, scan_error: nil) @sdam_mutex.synchronize do old_description = server.description @@ -237,7 +241,7 @@ def run_sdam_flow(result, awaited: false) average_round_trip_time: server.round_trip_time_averager.average_round_trip_time ) - server.cluster.run_sdam_flow(server.description, new_description, awaited: awaited) + server.cluster.run_sdam_flow(server.description, new_description, awaited: awaited, scan_error: scan_error) server.description.tap do |new_description| unless awaited @@ -290,7 +294,7 @@ def do_scan log_prefix: options[:log_prefix], bg_error_backtrace: options[:bg_error_backtrace], ) - {} + raise exc end end diff --git a/lib/mongo/socket.rb b/lib/mongo/socket.rb index 60feb79100..acca1a0e00 100644 --- a/lib/mongo/socket.rb +++ b/lib/mongo/socket.rb @@ -334,12 +334,36 @@ def read_from_socket(length, timeout: nil) raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data" end end + pipe = options[:pipe] if exc.is_a?(IO::WaitReadable) - select_args = [[@socket], nil, [@socket], select_timeout] + if pipe + select_args = [[@socket, pipe], nil, [@socket, pipe], select_timeout] + else + select_args = [[@socket], nil, [@socket], select_timeout] + end else select_args = [nil, [@socket], [@socket], select_timeout] end + rv = Kernel.select(*select_args) + if Lint.enabled? + if pipe && rv&.include?(pipe) + # If the return value of select is the read end of the pipe, and + # an IOError is not raised, then that means the socket is still + # open. Select is interrupted be closing the write end of the + # pipe, which either returns the pipe if the socket is open, or + # raises an IOError if it isn't. Select is interrupted after all + # of the pending and checked out connections have been interrupted + # and closed, and this only happens once the pool is cleared with + # interrupt_in_use connections flag. This means that in order for + # the socket to still be open when the select is interrupted, and + # that socket is being read from, that means after clear was + # called, a connection from the previous generation was checked + # out of the pool, for reading on its socket. This should be impossible. + raise Mongo::LintError, "Select interrupted for live socket. This should be impossible." + end + end + if BSON::Environment.jruby? # Ignore the return value of Kernel.select. # On JRuby, select appears to return nil prior to timeout expiration diff --git a/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb b/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb index d186002bf1..1fc1f95989 100644 --- a/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb @@ -28,7 +28,8 @@ # Spawn mongocryptd on non-default port for sharded cluster tests extra_options: extra_options, }, - database: 'auto_encryption' + database: 'auto_encryption', + populator_io: false } ) ) @@ -227,7 +228,7 @@ let(:key_vault_client_option) do new_local_client( SpecConfig.instance.addresses, - SpecConfig.instance.test_options + SpecConfig.instance.test_options.merge(populator_io: false) ) end diff --git a/spec/integration/client_update_spec.rb b/spec/integration/client_update_spec.rb index 460bc8844c..87fc4bc5c8 100644 --- a/spec/integration/client_update_spec.rb +++ b/spec/integration/client_update_spec.rb @@ -52,6 +52,10 @@ # actually require a clean slate. https://jira.mongodb.org/browse/RUBY-2138 clean_slate + before do + authorized_client.reconnect if authorized_client.closed? + end + it 'raises an exception' do expect do new_client diff --git a/spec/integration/connection_pool_populator_spec.rb b/spec/integration/connection_pool_populator_spec.rb index 342031e947..49cd254e97 100644 --- a/spec/integration/connection_pool_populator_spec.rb +++ b/spec/integration/connection_pool_populator_spec.rb @@ -77,12 +77,12 @@ context 'when min size is zero' do - it 'does not start the background thread' do + it 'does start the background thread' do pool sleep 2 expect(pool.size).to eq(0) - expect(pool.instance_variable_get('@populator').running?).to be false + expect(pool.instance_variable_get('@populator')).to be_running end end end @@ -97,6 +97,7 @@ it 'repopulates the pool periodically only up to min size' do pool.ready + expect(pool.instance_variable_get('@populator')).to be_running sleep 2 expect(pool.size).to eq(1) @@ -104,7 +105,18 @@ first_connection = pool.check_out pool.check_in(first_connection) - pool.clear + RSpec::Mocks.with_temporary_scope do + allow(pool.server).to receive(:unknown?).and_return(true) + if server.load_balancer? + pool.clear(service_id: first_connection.service_id) + else + pool.clear + end + end + + ::Utils.wait_for_condition(3) do + pool.size == 0 + end expect(pool.size).to eq(0) pool.ready @@ -262,7 +274,7 @@ receive(:create_and_add_connection).twice.and_raise(Mongo::Error::SocketError) pool sleep 2 - expect(pool.populator.running?).to be true + expect(pool.populator).to be_running end end @@ -272,7 +284,7 @@ receive(:create_and_add_connection).and_raise(Mongo::Error) pool sleep 2 - expect(pool.populator.running?).to be true + expect(pool.populator).to be_running end end end diff --git a/spec/integration/cursor_pinning_spec.rb b/spec/integration/cursor_pinning_spec.rb index 43b726f952..44f1b9d37a 100644 --- a/spec/integration/cursor_pinning_spec.rb +++ b/spec/integration/cursor_pinning_spec.rb @@ -4,7 +4,11 @@ require 'spec_helper' describe 'Cursor pinning' do - let(:client) { authorized_client } + let(:client) do + authorized_client.tap do |client| + client.reconnect if client.closed? + end + end let(:collection_name) { 'cursor_pinning' } let(:collection) { client[collection_name] } @@ -23,10 +27,6 @@ # When not in load-balanced topology, iterating a cursor creates # new connections as needed. - before do - client.reconnect - end - it 'creates new connections for iteration' do server.pool.size.should == 0 diff --git a/spec/integration/sdam_error_handling_spec.rb b/spec/integration/sdam_error_handling_spec.rb index 3c30120b60..c2efd7bd7a 100644 --- a/spec/integration/sdam_error_handling_spec.rb +++ b/spec/integration/sdam_error_handling_spec.rb @@ -25,6 +25,7 @@ SpecConfig.instance.all_test_options.merge( socket_timeout: 3, connect_timeout: 3, heartbeat_frequency: 100, + populator_io: false, # Uncomment to print all events to stdout: #sdam_proc: Utils.subscribe_all_sdam_proc(diagnostic_subscriber), **Utils.disable_retries_client_options) diff --git a/spec/integration/server_spec.rb b/spec/integration/server_spec.rb index 55e90ef5b6..6c0ef60c34 100644 --- a/spec/integration/server_spec.rb +++ b/spec/integration/server_spec.rb @@ -29,11 +29,14 @@ expect(server).not_to be_unknown end - it 'fails in connection pool' do + after do + server.close + end + + it 'can be used for reads' do # See also RUBY-3102. - lambda do - view.send(:send_initial_query, server) - end.should raise_error(Mongo::Error::PoolPausedError) + result = view.send(:send_initial_query, server) + expect(result).to be_a(Mongo::Operation::Find::Result) end end @@ -47,6 +50,10 @@ expect(server).to be_unknown end + after do + server.close + end + it 'is unusable' do # See also RUBY-3102. lambda do diff --git a/spec/integration/srv_monitoring_spec.rb b/spec/integration/srv_monitoring_spec.rb index edc72a0bb2..0303ecdebd 100644 --- a/spec/integration/srv_monitoring_spec.rb +++ b/spec/integration/srv_monitoring_spec.rb @@ -134,6 +134,7 @@ nameserver_port: [['127.0.0.1', 5300], ['127.0.0.1', 5300]], }, logger: logger, + populator_io: false, ), ) end diff --git a/spec/integration/transaction_pinning_spec.rb b/spec/integration/transaction_pinning_spec.rb index 3d62c20f9e..70683d95d1 100644 --- a/spec/integration/transaction_pinning_spec.rb +++ b/spec/integration/transaction_pinning_spec.rb @@ -32,6 +32,12 @@ end end + after do + if pool = server.pool_internal + pool.close + end + end + it 'works' do sessions = [] connections = [] @@ -82,7 +88,8 @@ session.pinned_connection_global_id.should_not be nil server.pool.size.should == 1 - server.pool.clear + service_id = server.pool.instance_variable_get(:@available_connections).first.service_id + server.pool.clear(service_id: service_id) server.pool.size.should == 0 lambda do diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index 6a59b1131c..a63a333708 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -10,6 +10,8 @@ describe '.new' do context 'with scan: false' do + fails_on_jruby + it 'does not perform i/o' do allow_any_instance_of(Mongo::Server::Monitor).to receive(:run!) expect_any_instance_of(Mongo::Server::Monitor).not_to receive(:scan!) @@ -2700,7 +2702,7 @@ new_local_client(['127.0.0.1:27017'], database: SpecConfig.instance.test_db, server_selection_timeout: 0.5, - socket_timeout: 0.1, connect_timeout: 0.1) + socket_timeout: 0.1, connect_timeout: 0.1, populator_io: false) end let(:new_client) do client.with(app_name: 'client_construction_spec').tap do |new_client| diff --git a/spec/mongo/cursor_spec.rb b/spec/mongo/cursor_spec.rb index 84547dbc79..24efb8cf9e 100644 --- a/spec/mongo/cursor_spec.rb +++ b/spec/mongo/cursor_spec.rb @@ -43,6 +43,14 @@ reset_pool(server) end end + + after do + authorized_client.cluster.servers.each do |server| + if pool = server.pool_internal + pool.close + end + end + end end context 'cursor exhausted by initial result' do @@ -756,6 +764,10 @@ reset_pool(server) end + after do + server.pool.close + end + it 'does not raise an error' do cursor server.with_connection do |conn| diff --git a/spec/mongo/server/connection_pool/populator_spec.rb b/spec/mongo/server/connection_pool/populator_spec.rb index c2abb27359..e9b30e5ffe 100644 --- a/spec/mongo/server/connection_pool/populator_spec.rb +++ b/spec/mongo/server/connection_pool/populator_spec.rb @@ -29,8 +29,8 @@ before do # We create our own populator to test; disable pool's background populator # and clear the pool, so ours can run - pool.stop_populator pool.disconnect! + pool.stop_populator end describe '#log_warn' do @@ -65,6 +65,7 @@ end it 'populates the pool up to min_size' do + pool.instance_variable_set(:@ready, true) populator.run! ::Utils.wait_for_condition(3) do pool.size >= 2 @@ -91,6 +92,17 @@ expect(populator.running?).to be true end end + + context "when clearing the pool" do + it "the populator is run one extra time" do + expect(pool).to receive(:populate).twice + populator.run! + sleep 0.5 + pool.disconnect! + sleep 0.5 + expect(populator.running?).to be true + end + end end describe '#stop' do diff --git a/spec/mongo/server/connection_pool_spec.rb b/spec/mongo/server/connection_pool_spec.rb index 6ed9df0e37..1d683f2406 100644 --- a/spec/mongo/server/connection_pool_spec.rb +++ b/spec/mongo/server/connection_pool_spec.rb @@ -62,6 +62,14 @@ end end + let(:populate_semaphore) do + pool.instance_variable_get(:@populate_semaphore) + end + + let(:populator) do + pool.instance_variable_get(:@populator) + end + describe '#initialize' do context 'when a min size is provided' do @@ -109,6 +117,10 @@ expect(pool.size).to eq(0) expect(pool.available_count).to eq(0) end + + it "starts the populator" do + expect(populator).to be_running + end end context 'sizes given as min_size and max_size' do @@ -297,6 +309,10 @@ it 'is true' do expect(pool.closed?).to be true end + + it "stops the populator" do + expect(populator).to_not be_running + end end end @@ -405,7 +421,6 @@ it_behaves_like 'adds connection to the pool' end - end context 'connection of later generation than pool' do @@ -428,6 +443,32 @@ it_behaves_like 'does not add connection to pool' end + context 'interrupted connection' do + let!(:connection) do + pool.check_out.tap do |connection| + expect(connection).to receive(:interrupted?).at_least(:once).and_return(true) + expect(connection).not_to receive(:record_checkin!) + end + end + + it_behaves_like 'does not add connection to pool' + end + + context 'closed and interrupted connection' do + let!(:connection) do + pool.check_out.tap do |connection| + expect(connection).to receive(:interrupted?).exactly(:once).and_return(true) + expect(connection).to receive(:closed?).exactly(:once).and_return(true) + expect(connection).not_to receive(:record_checkin!) + expect(connection).not_to receive(:disconnect!) + end + end + + it "returns immediately" do + expect(pool.check_in(connection)).to be_nil + end + end + context 'when pool is closed' do before do connection @@ -690,6 +731,298 @@ expect(checkout_failed_events.first.reason).to be(:connection_error) end end + + context "when the pool is paused" do + require_no_linting + + before do + pool.pause + end + + it "raises a PoolPausedError" do + expect do + pool.check_out + end.to raise_error(Mongo::Error::PoolPausedError) + end + end + end + + describe "#ready" do + require_no_linting + + let(:pool) do + register_pool(described_class.new(server, server_options)) + end + + context "when the pool is closed" do + before do + pool.close + end + + it "raises an error" do + expect do + pool.ready + end.to raise_error(Mongo::Error::PoolClosedError) + end + end + + context "when readying an initialized pool" do + before do + pool.ready + end + + it "starts the populator" do + expect(populator).to be_running + end + + it "readies the pool" do + expect(pool).to be_ready + end + end + + context "when readying a paused pool" do + before do + pool.ready + pool.pause + end + + it "readies the pool" do + pool.ready + expect(pool).to be_ready + end + + it "signals the populate semaphore" do + RSpec::Mocks.with_temporary_scope do + expect(populate_semaphore).to receive(:signal).and_wrap_original do |m, *args| + m.call(*args) + end + pool.ready + end + end + end + end + + describe "#ready?" do + require_no_linting + + let(:pool) do + register_pool(described_class.new(server, server_options)) + end + + shared_examples "pool is ready" do + it "is ready" do + expect(pool).to be_ready + end + end + + shared_examples "pool is not ready" do + it "is not ready" do + expect(pool).to_not be_ready + end + end + + context "before readying the pool" do + it_behaves_like "pool is not ready" + end + + context "after readying the pool" do + before do + pool.ready + end + + it_behaves_like "pool is ready" + end + + context "after readying and pausing the pool" do + before do + pool.ready + pool.pause + end + + it_behaves_like "pool is not ready" + end + + context "after readying, pausing, and readying the pool" do + before do + pool.ready + pool.pause + pool.ready + end + + it_behaves_like "pool is ready" + end + + context "after closing the pool" do + before do + pool.ready + pool.close + end + + it_behaves_like "pool is not ready" + end + end + + describe "#pause" do + require_no_linting + + let(:pool) do + register_pool(described_class.new(server, server_options)) + end + + context "when the pool is closed" do + before do + pool.close + end + + it "raises an error" do + expect do + pool.pause + end.to raise_error(Mongo::Error::PoolClosedError) + end + end + + context "when the pool is paused" do + before do + pool.ready + pool.pause + end + + it "is still paused" do + expect(pool).to be_paused + pool.pause + expect(pool).to be_paused + end + end + + context "when the pool is ready" do + before do + pool.ready + end + + it "is still paused" do + expect(pool).to be_ready + pool.pause + expect(pool).to be_paused + end + + it "does not stop the populator" do + expect(populator).to be_running + end + end + end + + describe "#paused?" do + require_no_linting + + let(:pool) do + register_pool(described_class.new(server, server_options)) + end + + shared_examples "pool is paused" do + it "is paused" do + expect(pool).to be_paused + end + end + + shared_examples "pool is not paused" do + it "is not paused" do + expect(pool).to_not be_paused + end + end + + context "before readying the pool" do + it_behaves_like "pool is paused" + end + + context "after readying the pool" do + before do + pool.ready + end + + it_behaves_like "pool is not paused" + end + + context "after readying and pausing the pool" do + before do + pool.ready + pool.pause + end + + it_behaves_like "pool is paused" + end + + context "after readying, pausing, and readying the pool" do + before do + pool.ready + pool.pause + pool.ready + end + + it_behaves_like "pool is not paused" + end + + context "after closing the pool" do + before do + pool.ready + pool.close + end + + it "raises an error" do + expect do + pool.paused? + end.to raise_error(Mongo::Error::PoolClosedError) + end + end + end + + describe "#closed?" do + require_no_linting + + let(:pool) do + register_pool(described_class.new(server, server_options)) + end + + shared_examples "pool is closed" do + it "is closed" do + expect(pool).to be_closed + end + end + + shared_examples "pool is not closed" do + it "is not closed" do + expect(pool).to_not be_closed + end + end + + context "before readying the pool" do + it_behaves_like "pool is not closed" + end + + context "after readying the pool" do + before do + pool.ready + end + + it_behaves_like "pool is not closed" + end + + context "after readying and pausing the pool" do + before do + pool.ready + pool.pause + end + + it_behaves_like "pool is not closed" + end + + context "after closing the pool" do + before do + pool.ready + pool.close + end + + it_behaves_like "pool is closed" + end end describe '#disconnect!' do @@ -708,6 +1041,11 @@ end describe '#clear' do + let(:checked_out_connections) { pool.instance_variable_get(:@checked_out_connections) } + let(:available_connections) { pool.instance_variable_get(:@available_connections) } + let(:pending_connections) { pool.instance_variable_get(:@pending_connections) } + let(:interrupt_connections) { pool.instance_variable_get(:@interrupt_connections) } + def create_pool(min_pool_size) opts = SpecConfig.instance.test_options.merge(max_pool_size: 3, min_pool_size: min_pool_size) described_class.new(server, opts).tap do |pool| @@ -745,10 +1083,14 @@ def create_pool(min_pool_size) expect(pool.size).to eq(2) expect(pool.available_count).to eq(2) - pool.disconnect! + RSpec::Mocks.with_temporary_scope do + allow(pool.server).to receive(:unknown?).and_return(true) + pool.disconnect! + end expect(pool.size).to eq(0) expect(pool.available_count).to eq(0) + expect(pool).to be_paused pool.ready @@ -785,6 +1127,40 @@ def create_pool(min_pool_size) end.to raise_error(Mongo::Error::PoolClosedError) end end + + context "when interrupting in use connections" do + context "when there's checked out connections" do + require_topology :single, :replica_set, :sharded + require_no_linting + + before do + 3.times { pool.check_out } + connection = pool.check_out + pool.check_in(connection) + expect(checked_out_connections.length).to eq(3) + expect(available_connections.length).to eq(1) + + pending_connections << pool.send(:create_connection) + end + + it "interrupts the connections" do + expect(pool).to receive(:populate).exactly(3).and_call_original + RSpec::Mocks.with_temporary_scope do + allow(pool.server).to receive(:unknown?).and_return(true) + pool.clear(lazy: true, interrupt_in_use_connections: true) + end + + ::Utils.wait_for_condition(3) do + pool.size == 0 + end + + expect(pool.size).to eq(0) + expect(available_connections).to be_empty + expect(checked_out_connections).to be_empty + expect(pending_connections).to be_empty + end + end + end end describe '#close' do diff --git a/spec/mongo/server/monitor_spec.rb b/spec/mongo/server/monitor_spec.rb index 77cafa24db..d81be08971 100644 --- a/spec/mongo/server/monitor_spec.rb +++ b/spec/mongo/server/monitor_spec.rb @@ -271,7 +271,6 @@ expect(monitor).to receive(:check).and_raise(IOError) # The retry is done on a new socket instance. #expect(socket).to receive(:write).and_call_original - monitor.send(:do_scan) end @@ -281,7 +280,9 @@ # of the message. expect(msg).to match(/#{server.address}/) end - expect(result).to be_a(Hash) + expect do + result + end.to raise_error(IOError) end end diff --git a/spec/mongo/server_spec.rb b/spec/mongo/server_spec.rb index b6b6a0fab0..69e1601c66 100644 --- a/spec/mongo/server_spec.rb +++ b/spec/mongo/server_spec.rb @@ -129,14 +129,13 @@ end it 'pauses and clears the connection pool' do - expect(server.pool_internal).to receive(:pause).once.and_call_original - expect(server.pool_internal).to receive(:clear).once.and_call_original + expect(server.pool_internal).to receive(:close).once.and_call_original RSpec::Mocks.with_temporary_scope do # you can't disconnect from a known server, since this pauses the # pool and we only want to pause the pools of unknown servers. server.unknown! allow(server).to receive(:unknown?).and_return(true) - server.disconnect! + server.close end end end diff --git a/spec/runners/cmap.rb b/spec/runners/cmap.rb index 8cd7f703f8..809de6b01e 100644 --- a/spec/runners/cmap.rb +++ b/spec/runners/cmap.rb @@ -49,6 +49,8 @@ def initialize(test_path) @expected_error = @test['error'] @expected_events = @test['events'] @ignore_events = @test['ignore'] || [] + @fail_point_command = @test['failPoint'] + @threads = Set.new process_run_on preprocess @@ -56,14 +58,17 @@ def initialize(test_path) attr_reader :pool - def setup(server, subscriber) + def setup(server, client, subscriber) @subscriber = subscriber + @client = client # The driver always creates pools for known servers. # There is a test which creates and destroys a pool and it only expects # those two events, not the ready event. # This situation cannot happen in normal driver operation, but to # support this test, create the pool manually here. @pool = Mongo::Server::ConnectionPool.new(server, server.options) + + configure_fail_point end def run @@ -76,6 +81,8 @@ def run if err result['error'] = err break + elsif op.name == 'start' + @threads << state[op.target] end end @@ -141,6 +148,7 @@ def run { 'type' => 'ConnectionPoolCleared', 'address' => event.address, + 'interruptInUseConnections' => event.options[:interrupt_in_use_connections] } when Mongo::Monitoring::Event::Cmap::PoolReady { @@ -154,6 +162,22 @@ def run events << event unless @ignore_events.include?(event.fetch('type')) end end + ensure + disable_fail_points + kill_remaining_threads + end + + def disable_fail_points + if @fail_point_command + @client.command( + configureFailPoint: @fail_point_command['configureFailPoint'], + mode: 'off' + ) + end + end + + def kill_remaining_threads + @threads.each(&:kill) end def satisfied? @@ -168,6 +192,9 @@ def satisfied? if @topologies ok &&= @topologies.include?(cc.topology) end + if @oses + ok &&= @oses.any? { |os| SpecConfig.instance.send("#{os.to_s}?")} + end ok end @@ -198,7 +225,7 @@ def normalize_options(options) # This method only handles options used by spec tests at the time when # this method was written. Other options are silently dropped. def process_options(options) - (options || {}).reduce({}) do |opts, kv| + (options || {}).each_with_object({}) do |kv, opts| case kv.first when 'maxIdleTimeMS' opts[:max_idle_time] = kv.last / 1000.0 @@ -218,8 +245,6 @@ def process_options(options) else raise "Unknown option #{kv.first}" end - - opts end end @@ -232,8 +257,8 @@ def process_run_on doc.keys.first == 'maxServerVersion' end&.values&.first - @topologies = if topologies = run_on.detect { |doc| doc.keys.first == 'topology' }['topology'] - topologies.map do |topology| + @topologies = if topologies = run_on.detect { |doc| doc.keys.first == 'topology' } + (topologies['topology'] || {}).map do |topology| { 'replicaset' => :replica_set, 'single' => :single, @@ -246,8 +271,20 @@ def process_run_on end end end - else - nil + end + + @oses = if oses = run_on.detect { |doc| doc.keys.first == 'requireOs' } + (oses['requireOs'] || {}).map do |os| + { + 'macos' => :macos, + 'linux' => :linux, + 'windows' => :windows, + }[os].tap do |v| + unless v + raise "Unknown os #{os}" + end + end + end end end end @@ -267,6 +304,10 @@ def preprocess end end end + + def configure_fail_point + @client.database.command(@fail_point_command) if @fail_point_command + end end # Represents an operation in the spec. Operations are sequential. @@ -292,6 +333,10 @@ class Operation # @return [ String | nil ] label The label for the returned connection. attr_reader :label + # @return [ true | false ] interrupt_in_use_connections Whether or not + # all connections should be closed on pool clear. + attr_reader :interrupt_in_use_connections + # @return [ String | nil ] The binding for the connection which should run the operation. attr_reader :connection @@ -313,6 +358,7 @@ def initialize(spec, operation) @connection = operation['connection'] @event = operation['event'] @count = operation['count'] + @interrupt_in_use_connections = !!operation['interruptInUseConnections'] end def run(pool, state, main_thread = true) @@ -337,8 +383,6 @@ def run(pool, state, main_thread = true) run_clear_op(state) when 'close' run_close_op(state) - when 'ready' - run_ready_op(state) else raise "invalid operation: #{name}" end @@ -368,9 +412,14 @@ def run(pool, state, main_thread = true) def run_start_op(state) state[target] = Thread.start do Thread.current[:name] = @target - thread_ops.each { |op| op.run(pool, state, false) } + thread_ops.each do |op| + op.run(pool, state, false) + end end + # Allow the thread to begin running. + sleep 0.1 + # Since we expect exceptions to occur in some cases, we disable the printing of error # messages from the thread if the Ruby version supports it. if state[target].respond_to?(:report_on_exception) @@ -378,10 +427,6 @@ def run_start_op(state) end end - # Ruby driver does not have the concept of pausing and readying the pool. - def run_ready_op(_state) - end - def run_wait_op(_state) sleep(ms / 1000.0) end @@ -429,7 +474,7 @@ def run_clear_op(state) RSpec::Mocks.with_temporary_scope do allow(pool.server).to receive(:unknown?).and_return(true) - pool.clear(lazy: true) + pool.clear(lazy: true, interrupt_in_use_connections: interrupt_in_use_connections) end end diff --git a/spec/runners/unified/assertions.rb b/spec/runners/unified/assertions.rb index 79c55517d9..699fcec98f 100644 --- a/spec/runners/unified/assertions.rb +++ b/spec/runners/unified/assertions.rb @@ -209,6 +209,9 @@ def assert_event_matches(actual, expected) if reply = spec.use('reply') assert_matches(actual.reply, reply, 'Command reply does not match expectation') end + if interrupt_in_use_connections = spec.use('interruptInUseConnections') + assert_matches(actual.options[:interrupt_in_use_connections], interrupt_in_use_connections, 'Command interrupt_in_use_connections does not match expectation') + end unless spec.empty? raise NotImplementedError, "Unhandled keys: #{spec}" end diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index 31215dae0f..2a11eb69e9 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -289,7 +289,7 @@ def generate_entities(es) if thread_context.stop? break else - sleep 1 + sleep 0.1 end end end diff --git a/spec/spec_tests/cmap_spec.rb b/spec/spec_tests/cmap_spec.rb index d2501c2c73..c594b626a9 100644 --- a/spec/spec_tests/cmap_spec.rb +++ b/spec/spec_tests/cmap_spec.rb @@ -27,7 +27,7 @@ let(:options) do Mongo::Utils.shallow_symbolize_keys(Mongo::Client.canonicalize_ruby_options( SpecConfig.instance.all_test_options, - )).update(monitoring_io: false).tap do |options| + )).update(monitoring_io: false, populator_io: true).tap do |options| # We have a wait queue timeout set in the test suite options, but having # this option set interferes with assertions in the cmap spec tests. options.delete(:wait_queue_timeout) @@ -61,28 +61,21 @@ allow(server).to receive(:description).and_return(ClusterConfig.instance.primary_description) end ) - spec.setup(@server, subscriber) + + @client = ClusterTools.instance.direct_client(ClusterConfig.instance.primary_address, + database: 'admin') + spec.setup(@server, @client, subscriber) end after do - if @server && (pool = @server.instance_variable_get('@pool')) - begin - pool.disconnect! - rescue Mongo::Error::PoolClosedError - end + if pool = @server&.pool_internal + pool.disconnect! end spec.pool&.close end let!(:result) do - socket = double('fake socket') - allow(socket).to receive(:close) - # When linting, connection pool ensures the connection returned - # is connected. - allow(socket).to receive(:alive?).and_return(true) - - allow_any_instance_of(Mongo::Server::Connection).to receive(:do_connect).and_return(socket) if @server.load_balancer? allow_any_instance_of(Mongo::Server::Connection).to receive(:service_id).and_return('very fake') end diff --git a/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml b/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml new file mode 100644 index 0000000000..afbb880df0 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml @@ -0,0 +1,49 @@ +version: 1 +style: unit +description: Connections MUST be interrupted as soon as possible (interruptInUseConnections=true) +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - topology: [ "single", "replicaset", "sharded" ] +poolOptions: + # ensure it's not involved by default + backgroundThreadIntervalMS: 10000 +operations: + - name: ready + - name: checkOut + - name: checkOut + label: conn + - name: clear + interruptInUseConnections: true + - name: waitForEvent + event: ConnectionPoolCleared + count: 1 + timeout: 1000 + - name: waitForEvent + event: ConnectionClosed + count: 2 + timeout: 1000 + - name: close +events: + - type: ConnectionCheckedOut + connectionId: 1 + address: 42 + - type: ConnectionCheckedOut + connectionId: 2 + address: 42 + - type: ConnectionPoolCleared + interruptInUseConnections: true + - type: ConnectionClosed + reason: stale + address: 42 + - type: ConnectionClosed + reason: stale + address: 42 + - type: ConnectionPoolClosed + address: 42 +ignore: + - ConnectionCreated + - ConnectionPoolReady + - ConnectionReady + - ConnectionCheckOutStarted + - ConnectionPoolCreated + - ConnectionCheckedIn diff --git a/spec/spec_tests/data/cmap/pool-clear-interrupting-pending-connections.yml b/spec/spec_tests/data/cmap/pool-clear-interrupting-pending-connections.yml new file mode 100644 index 0000000000..bebf45341a --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-clear-interrupting-pending-connections.yml @@ -0,0 +1,43 @@ +version: 1 +style: integration +description: clear with interruptInUseConnections = true closes pending connections +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - minServerVersion: "4.9.0" + - topology: [ "single", "replicaset", "sharded" ] +failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 1000 +poolOptions: + minPoolSize: 0 +operations: + - name: ready + - name: start + target: thread1 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCreated + count: 1 + - name: clear + interruptInUseConnections: true + - name: waitForEvent + event: ConnectionCheckOutFailed + count: 1 +events: + - type: ConnectionCheckOutStarted + - type: ConnectionCreated + - type: ConnectionPoolCleared + interruptInUseConnections: true + - type: ConnectionClosed + - type: ConnectionCheckOutFailed +ignore: + - ConnectionCheckedIn + - ConnectionCheckedOut + - ConnectionPoolCreated + - ConnectionPoolReady diff --git a/spec/spec_tests/data/cmap/pool-clear-schedule-run-interruptInUseConnections-false.yml b/spec/spec_tests/data/cmap/pool-clear-schedule-run-interruptInUseConnections-false.yml new file mode 100644 index 0000000000..dcaafec8b5 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-clear-schedule-run-interruptInUseConnections-false.yml @@ -0,0 +1,48 @@ +version: 1 +style: unit +description: Pool clear SHOULD schedule the next background thread run immediately (interruptInUseConnections = false) +poolOptions: + # ensure it's not involved by default + backgroundThreadIntervalMS: 10000 +operations: + - name: ready + - name: checkOut + - name: checkOut + label: conn + - name: checkIn + connection: conn + - name: clear + interruptInUseConnections: false + - name: waitForEvent + event: ConnectionPoolCleared + count: 1 + timeout: 1000 + - name: waitForEvent + event: ConnectionClosed + count: 1 + timeout: 1000 + - name: close +events: + - type: ConnectionCheckedOut + connectionId: 1 + address: 42 + - type: ConnectionCheckedOut + connectionId: 2 + address: 42 + - type: ConnectionCheckedIn + connectionId: 2 + address: 42 + - type: ConnectionPoolCleared + interruptInUseConnections: false + - type: ConnectionClosed + connectionId: 2 + reason: stale + address: 42 + - type: ConnectionPoolClosed + address: 42 +ignore: + - ConnectionCreated + - ConnectionPoolReady + - ConnectionReady + - ConnectionCheckOutStarted + - ConnectionPoolCreated diff --git a/spec/spec_tests/data/sdam_unified/hello-timeout.yml b/spec/spec_tests/data/sdam_unified/hello-timeout.yml index efab836e65..bbb11d8a93 100644 --- a/spec/spec_tests/data/sdam_unified/hello-timeout.yml +++ b/spec/spec_tests/data/sdam_unified/hello-timeout.yml @@ -156,7 +156,7 @@ tests: failPoint: configureFailPoint: failCommand mode: - times: 4 + times: 2 data: failCommands: - hello diff --git a/spec/spec_tests/data/sdam_unified/interruptInUse-pool-clear.yml b/spec/spec_tests/data/sdam_unified/interruptInUse-pool-clear.yml new file mode 100644 index 0000000000..580ed23b7f --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/interruptInUse-pool-clear.yml @@ -0,0 +1,340 @@ +--- +description: interruptInUse + +schemaVersion: "1.11" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.9" + serverless: forbid + topologies: [ replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName interruptInUse + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Connection pool clear uses interruptInUseConnections=true after monitor timeout + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - poolClearedEvent + - connectionClosedEvent + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent + - connectionCheckedOutEvent + - connectionCheckedInEvent + uriOptions: + connectTimeoutMS: 500 + heartbeatFrequencyMS: 500 + appname: interruptInUse + retryReads: false + minPoolSize: 0 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - thread: + id: &thread1 thread1 + - name: insertOne + object: *collection + arguments: + document: { _id: 1 } + # simulate a long-running query + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: find + object: *collection + arguments: + filter: + $where : sleep(2000) || true + expectError: + isError: true + # Configure the monitor check to fail with a timeout. + # Use "times: 4" to increase the probability that the Monitor check triggers + # the failpoint, since the RTT hello may trigger this failpoint one or many + # times as well. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - hello + - isMaster + blockConnection: true + blockTimeMS: 1500 + appName: interruptInUse + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + - commandStartedEvent: + commandName: find + - commandFailedEvent: + commandName: find + - client: *client + eventType: cmap + events: + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + - connectionCheckedOutEvent: {} + - poolClearedEvent: + interruptInUseConnections: true + - connectionCheckedInEvent: {} + - connectionClosedEvent: {} + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + + - description: Error returned from connection pool clear with interruptInUseConnections=true is retryable + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - poolClearedEvent + - connectionClosedEvent + - commandStartedEvent + - commandFailedEvent + - commandSucceededEvent + - connectionCheckedOutEvent + - connectionCheckedInEvent + uriOptions: + connectTimeoutMS: 500 + heartbeatFrequencyMS: 500 + appname: interruptInUseRetryable + retryReads: true + minPoolSize: 0 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - thread: + id: &thread1 thread1 + - name: insertOne + object: *collection + arguments: + document: { _id: 1 } + # simulate a long-running query + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: find + object: *collection + arguments: + filter: + $where : sleep(2000) || true + # Configure the monitor check to fail with a timeout. + # Use "times: 4" to increase the probability that the Monitor check triggers + # the failpoint, since the RTT hello may trigger this failpoint one or many + # times as well. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - hello + - isMaster + blockConnection: true + blockTimeMS: 1500 + appName: interruptInUseRetryable + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + - commandStartedEvent: + commandName: find + - commandFailedEvent: + commandName: find + - commandStartedEvent: + commandName: find + - commandSucceededEvent: + commandName: find + - client: *client + eventType: cmap + events: + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + - connectionCheckedOutEvent: {} + - poolClearedEvent: + interruptInUseConnections: true + - connectionCheckedInEvent: {} + - connectionClosedEvent: {} + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - description: Error returned from connection pool clear with interruptInUseConnections=true is retryable for write + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - poolClearedEvent + - connectionClosedEvent + - commandStartedEvent + - commandFailedEvent + - commandSucceededEvent + - connectionCheckedOutEvent + - connectionCheckedInEvent + uriOptions: + connectTimeoutMS: 500 + heartbeatFrequencyMS: 500 + appname: interruptInUseRetryableWrite + retryWrites: true + minPoolSize: 0 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - thread: + id: &thread1 thread1 + # ensure the primary is discovered + - name: insertOne + object: *collection + arguments: + document: { _id: 1 } + # simulate a long-running query + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: updateOne + object: *collection + arguments: + filter: + $where: sleep(2000) || true + update: + "$set": { "a": "bar" } + # Configure the monitor check to fail with a timeout. + # Use "times: 4" to increase the probability that the Monitor check triggers + # the failpoint, since the RTT hello may trigger this failpoint one or many + # times as well. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - hello + - isMaster + blockConnection: true + blockTimeMS: 1500 + appName: interruptInUseRetryableWrite + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + - commandStartedEvent: + commandName: update + - commandFailedEvent: + commandName: update + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update + - client: *client + eventType: cmap + events: + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + - connectionCheckedOutEvent: {} + - poolClearedEvent: + interruptInUseConnections: true + - connectionCheckedInEvent: {} + - connectionClosedEvent: {} + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, a : bar } diff --git a/spec/spec_tests/data/sdam_unified/rediscover-quickly-after-step-down.yml b/spec/spec_tests/data/sdam_unified/rediscover-quickly-after-step-down.yml index d0ed6e41d2..e5b49de35d 100644 --- a/spec/spec_tests/data/sdam_unified/rediscover-quickly-after-step-down.yml +++ b/spec/spec_tests/data/sdam_unified/rediscover-quickly-after-step-down.yml @@ -78,7 +78,7 @@ tests: command: replSetFreeze: 0 readPreference: - mode: Secondary + mode: secondary commandName: replSetFreeze # Run replSetStepDown on the meta client. - name: runCommand diff --git a/spec/support/common_shortcuts.rb b/spec/support/common_shortcuts.rb index e82a4e28c7..33a5b6c7f0 100644 --- a/spec/support/common_shortcuts.rb +++ b/spec/support/common_shortcuts.rb @@ -325,6 +325,8 @@ def stop_monitoring(*clients) client.cluster.servers_list.each do |server| if pool = server.pool pool.instance_variable_set('@closed', false) + # Stop the populator so that we don't have leftover threads. + pool.instance_variable_get('@populator').stop! end end end @@ -405,6 +407,9 @@ def wait_for_snapshot(db: nil, collection: nil, client: nil) # Used for tests that e.g. mock network operations to avoid interference # from server monitoring. def reset_pool(server) + if pool = server.pool_internal + pool.close + end server.remove_instance_variable('@pool') server.pool.ready end From 6c587312e4efddbcea63662cd756245357450c09 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 10 Nov 2022 12:39:12 -0500 Subject: [PATCH 075/198] RUBY-3173 Allow client to be used after closing (#2659) * RUBY-3173 Allow client to be used after closing * RUBY-3173 use pool close * Revert "get rid of debug" This reverts commit e0359a513dc8f908fc4e430e366e078b4fdf4ec4. * RUBY-3173 attempt to fix live background threads * Apply suggestions from code review * fix lint errors --- lib/mongo/client.rb | 3 +- lib/mongo/server.rb | 8 +++- lib/mongo/server/connection_pool.rb | 38 ++++++++++++------- lib/mongo/server_selector/base.rb | 7 ++-- .../client_close_spec.rb | 20 ++++++---- spec/integration/client_spec.rb | 19 ++++------ 6 files changed, 57 insertions(+), 38 deletions(-) diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index d740425cf5..92bde662e6 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -1141,7 +1141,8 @@ def get_session(options = {}) # # @api private def with_session(options = {}, &block) - assert_not_closed + # TODO: Add this back in RUBY-3174. + # assert_not_closed session = get_session(options) diff --git a/lib/mongo/server.rb b/lib/mongo/server.rb index 9eec1de2c9..297ed4859d 100644 --- a/lib/mongo/server.rb +++ b/lib/mongo/server.rb @@ -303,7 +303,10 @@ def close _pool, @pool = @pool, nil end - _pool&.close + # TODO: change this to _pool.close in RUBY-3174. + # Clear the pool. If the server is not unknown then the + # pool will stay ready. Stop the background populator thread. + _pool&.close(stay_ready: true) nil end @@ -424,7 +427,8 @@ def pool end @pool_lock.synchronize do - @pool ||= ConnectionPool.new(self, options).tap do |pool| + opts = connected? ? options : options.merge(populator_io: false) + @pool ||= ConnectionPool.new(self, opts).tap do |pool| pool.ready end end diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index 645084a538..b62df7b953 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -703,11 +703,12 @@ def do_clear(options = nil) def ready raise_if_closed! - if Lint.enabled? - unless @server.connected? - raise Error::LintError, "Attempting to ready a pool for server #{@server.summary} which is disconnected" - end - end + # TODO: Add this back in RUBY-3174. + # if Lint.enabled? + # unless @server.connected? + # raise Error::LintError, "Attempting to ready a pool for server #{@server.summary} which is disconnected" + # end + # end @lock.synchronize do return if @ready @@ -744,6 +745,8 @@ def ready # # @option options [ true | false ] :force Also close all checked out # connections. + # @option options [ true | false ] :stay_ready For internal driver use + # only. Whether or not to mark the pool as closed. # # @return [ true ] Always true. # @@ -769,10 +772,12 @@ def close(options = nil) end end - # mark pool as closed before releasing lock so - # no connections can be created, checked in, or checked out - @closed = true - @ready = false + unless options && options[:stay_ready] + # mark pool as closed before releasing lock so + # no connections can be created, checked in, or checked out + @closed = true + @ready = false + end end publish_cmap_event( @@ -868,11 +873,7 @@ def stop_populator # connections waiting to be connected, connections which have not yet # been moved to available_connections, or connections moved to available_connections # but not deleted from pending_connections. These should be cleaned up. - until @pending_connections.empty? - connection = @pending_connections.take(1).first - connection.disconnect! - @pending_connections.delete(connection) - end + clear_pending_connections end end @@ -1171,6 +1172,15 @@ def schedule_for_interruption(connections, service_id) conn.generation < @generation_manager.generation(service_id: service_id) end end + + # Clear and disconnect the pending connections. + def clear_pending_connections + until @pending_connections.empty? + connection = @pending_connections.take(1).first + connection.disconnect! + @pending_connections.delete(connection) + end + end end end end diff --git a/lib/mongo/server_selector/base.rb b/lib/mongo/server_selector/base.rb index 88373187a4..ad8d581e82 100644 --- a/lib/mongo/server_selector/base.rb +++ b/lib/mongo/server_selector/base.rb @@ -256,9 +256,10 @@ def select_server(cluster, ping = nil, session = nil, write_aggregation: false) loop do if Lint.enabled? cluster.servers.each do |server| - if !server.unknown? && !server.connected? - raise Error::LintError, "Server #{server.summary} is known but is not connected" - end + # TODO: Add this back in RUBY-3174. + # if !server.unknown? && !server.connected? + # raise Error::LintError, "Server #{server.summary} is known but is not connected" + # end if !server.unknown? && !server.pool.ready? raise Error::LintError, "Server #{server.summary} is known but has non-ready pool" end diff --git a/spec/integration/client_side_encryption/client_close_spec.rb b/spec/integration/client_side_encryption/client_close_spec.rb index 252f3196b5..e69916dec7 100644 --- a/spec/integration/client_side_encryption/client_close_spec.rb +++ b/spec/integration/client_side_encryption/client_close_spec.rb @@ -28,11 +28,17 @@ ) end - shared_examples 'a non-functioning auto-encrypter' do - it 'raises a client closed error' do - expect do - client['users'].insert_one(ssn: '000-000-0000') - end.to raise_error(Mongo::Error::ClientClosed) + shared_examples 'a functioning auto-encrypter' do + it 'can still perform encryption' do + result = client['users'].insert_one(ssn: '000-000-0000') + expect(result).to be_ok + + encrypted_document = authorized_client + .use('auto_encryption')['users'] + .find(_id: result.inserted_ids.first) + .first + + expect(encrypted_document['ssn']).to be_ciphertext end end @@ -45,7 +51,7 @@ client.close end - it_behaves_like 'a non-functioning auto-encrypter' + it_behaves_like 'a functioning auto-encrypter' end context 'after performing operation without auto encryption' do @@ -54,7 +60,7 @@ client.close end - it_behaves_like 'a non-functioning auto-encrypter' + it_behaves_like 'a functioning auto-encrypter' end end end diff --git a/spec/integration/client_spec.rb b/spec/integration/client_spec.rb index 3e50d27547..2dc71caa80 100644 --- a/spec/integration/client_spec.rb +++ b/spec/integration/client_spec.rb @@ -6,7 +6,7 @@ describe 'Client' do # TODO after the client is closed, operations should fail with an exception # that communicates this state, instead of failing with server selection or - # pool errors. RUBY-3102 + # pool errors. RUBY-3102, RUBY-3174. context 'after client is disconnected' do let(:client) { authorized_client.with(server_selection_timeout: 1) } @@ -14,17 +14,14 @@ client.close end - it 'fails in connection pool' do - lambda do - client.database.command(ping: 1) - end.should raise_error(Mongo::Error::ClientClosed) + it 'is still usable for operations' do + resp = client.database.command(ping: 1) + expect(resp).to be_a(Mongo::Operation::Result) end context 'operation that can use sessions' do - it 'fails in connection pool' do - lambda do - client['collection'].insert_one(test: 1) - end.should raise_error(Mongo::Error::ClientClosed) + it 'is still usable for operations' do + client['collection'].insert_one(test: 1) end end @@ -41,7 +38,7 @@ it 'fails server selection' do expect do client.database.command(ping: 1) - end.to raise_error(Mongo::Error::ClientClosed) + end.to raise_error(Mongo::Error::NoServerAvailable) end end @@ -49,7 +46,7 @@ it 'fails server selection' do expect do client['collection'].insert_one(test: 1) - end.to raise_error(Mongo::Error::ClientClosed) + end.to raise_error(Mongo::Error::NoServerAvailable) end end end From 2760d21edfab2b6da8c828b4b878b5ea055560c4 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 11 Nov 2022 09:49:33 +0100 Subject: [PATCH 076/198] RUBY-3175 Remove use of activate_venv.sh (#2658) --- .evergreen/config-atlas.yml | 37 ------------------------------ .evergreen/config/common.yml.erb | 2 ++ .evergreen/run-tests-serverless.sh | 19 ++++++++------- .evergreen/venv-utils.sh | 1 + .mod/drivers-evergreen-tools | 2 +- 5 files changed, 15 insertions(+), 46 deletions(-) create mode 120000 .evergreen/venv-utils.sh diff --git a/.evergreen/config-atlas.yml b/.evergreen/config-atlas.yml index d2bf84e20c..ec21311701 100644 --- a/.evergreen/config-atlas.yml +++ b/.evergreen/config-atlas.yml @@ -440,43 +440,6 @@ post: - func: "upload test results to s3" - func: "delete serverless instance" -task_groups: - - name: testgcpkms_task_group - setup_group_can_fail_task: true - setup_group_timeout_secs: 1800 # 30 minutes - setup_group: - - func: fetch source - - func: "create expansions" - - command: shell.exec - params: - shell: "bash" - working_dir: "src" - script: | - ${PREPARE_SHELL} - echo '${testgcpkms_key_file}' > /tmp/testgcpkms_key_file.json - export GCPKMS_KEYFILE=/tmp/testgcpkms_key_file.json - export GCPKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS - export GCPKMS_SERVICEACCOUNT="${testgcpkms_service_account}" - export GCPKMS_MACHINETYPE="e2-standard-4" - .evergreen/csfle/gcpkms/create-and-setup-instance.sh - # Load the GCPKMS_GCLOUD, GCPKMS_INSTANCE, GCPKMS_REGION, and GCPKMS_ZONE expansions. - - command: expansions.update - params: - file: src/testgcpkms-expansions.yml - teardown_group: - - command: shell.exec - params: - shell: "bash" - working_dir: "src" - script: | - ${PREPARE_SHELL} - export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} - export GCPKMS_PROJECT=${GCPKMS_PROJECT} - export GCPKMS_ZONE=${GCPKMS_ZONE} - export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} - .evergreen/csfle/gcpkms/delete-instance.sh - tasks: - - testgcpkms-task tasks: diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index cc57262c06..8e81b5eaad 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -531,6 +531,7 @@ post: - func: "delete serverless instance" <% end %> +<% unless @atlas %> task_groups: - name: testgcpkms_task_group setup_group_can_fail_task: true @@ -568,6 +569,7 @@ task_groups: .evergreen/csfle/gcpkms/delete-instance.sh tasks: - testgcpkms-task +<% end %> tasks: diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index fc12699889..14a30f2d1b 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -26,14 +26,16 @@ else exit -1 fi -. .evergreen/csfle/activate_venv.sh -pip3 install boto3~=1.19 'cryptography<3.4' pykmip~=0.10.0 +cd .evergreen/csfle +. ./activate-kmstlsvenv.sh -python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/server.pem --port 7999 & -python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/expired.pem --port 8000 & -python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/wrong-host.pem --port 8001 & -python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/server.pem --port 8002 --require_client_cert & -python3 -u .evergreen/csfle/kms_kmip_server.py & +pip install boto3~=1.19 'cryptography<3.4' pykmip~=0.10.0 + +python -u ./kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 7999 & +python -u ./kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 8000 & +python -u ./kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 8001 & +python -u ./kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 8002 --require_client_cert & +python -u ./kms_kmip_server.py & echo "Waiting for mock KMS servers to start..." wait_for_kms_server() { @@ -54,7 +56,8 @@ wait_for_kms_server 5698 echo "Waiting for mock KMS servers to start... done." # Obtain temporary AWS credentials -PYTHON=python3 . .evergreen/csfle/set-temp-creds.sh +. ./set-temp-creds.sh +cd - echo "Running specs" diff --git a/.evergreen/venv-utils.sh b/.evergreen/venv-utils.sh new file mode 120000 index 0000000000..f544cf51bc --- /dev/null +++ b/.evergreen/venv-utils.sh @@ -0,0 +1 @@ +../.mod/drivers-evergreen-tools/.evergreen/venv-utils.sh \ No newline at end of file diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index 976d3c8020..f5d1c04994 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit 976d3c8020e95040e5d413b345f50c106b7bc3b7 +Subproject commit f5d1c049948718218b006c0732990e1c642cbb13 From da7472fc42fce0f348a6ac49a913616136624d60 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Fri, 11 Nov 2022 07:21:23 -0500 Subject: [PATCH 077/198] RUBY-2804 Clarify clearing behavior for load balanced pools (#2660) * RUBY-2804 Clarify clearing behavior for load balanced pools * fix lint error * Update spec/mongo/server/connection_pool_spec.rb --- spec/mongo/server/connection_pool_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/mongo/server/connection_pool_spec.rb b/spec/mongo/server/connection_pool_spec.rb index 1d683f2406..df37b7531d 100644 --- a/spec/mongo/server/connection_pool_spec.rb +++ b/spec/mongo/server/connection_pool_spec.rb @@ -1161,6 +1161,16 @@ def create_pool(min_pool_size) end end end + + context "when in load-balanced mode" do + require_topology :load_balanced + + it "does not pause the pool" do + allow(pool.server).to receive(:unknown?).and_return(true) + pool.clear + expect(pool).to_not be_paused + end + end end describe '#close' do From 496b4f250dbdf1d7b42fe97f20adb5184b38acbe Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 14 Nov 2022 11:24:20 -0500 Subject: [PATCH 078/198] RUBY-2598 Clear pool when connection establishment fails in populator (#2662) * working tests * RUBY-2598 Clear pool when connection establishment fails in populator * disable on load balancers * disable all lb cmap tests * Revert "disable all lb cmap tests" This reverts commit 4b3216252d67bb987fdc6219849ad02bd1423b7e. * fix timeout * disable lb tests * Update spec/runners/cmap.rb --- spec/runners/cmap.rb | 3 +- spec/spec_tests/cmap_spec.rb | 14 ++++++ spec/spec_tests/data/cmap/pool-checkin.yml | 3 ++ .../data/cmap/pool-clear-paused.yml | 18 ++++++++ .../spec_tests/data/cmap/pool-clear-ready.yml | 39 +++++++++++++++++ .../data/cmap/pool-create-min-size-error.yml | 43 +++++++++++++++++++ .../data/cmap/pool-create-min-size.yml | 3 ++ .../spec_tests/data/cmap/pool-ready-ready.yml | 22 ++++++++++ spec/spec_tests/data/cmap/pool-ready.yml | 30 +++++++++++++ .../data/cmap/wait-queue-timeout.yml | 3 ++ 10 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 spec/spec_tests/data/cmap/pool-clear-paused.yml create mode 100644 spec/spec_tests/data/cmap/pool-clear-ready.yml create mode 100644 spec/spec_tests/data/cmap/pool-create-min-size-error.yml create mode 100644 spec/spec_tests/data/cmap/pool-ready-ready.yml create mode 100644 spec/spec_tests/data/cmap/pool-ready.yml diff --git a/spec/runners/cmap.rb b/spec/runners/cmap.rb index 809de6b01e..516685c185 100644 --- a/spec/runners/cmap.rb +++ b/spec/runners/cmap.rb @@ -43,7 +43,7 @@ def initialize(test_path) @test = ::Utils.load_spec_yaml_file(test_path) @description = @test['description'] - @pool_options = ::Utils.snakeize_hash(process_options(@test['poolOptions'])) + @pool_options = process_options(@test['poolOptions']) @spec_ops = @test['operations'].map { |o| Operation.new(self, o) } @processed_ops = [] @expected_error = @test['error'] @@ -67,6 +67,7 @@ def setup(server, client, subscriber) # This situation cannot happen in normal driver operation, but to # support this test, create the pool manually here. @pool = Mongo::Server::ConnectionPool.new(server, server.options) + server.instance_variable_set(:@pool, @pool) configure_fail_point end diff --git a/spec/spec_tests/cmap_spec.rb b/spec/spec_tests/cmap_spec.rb index c594b626a9..bfd67cfbf9 100644 --- a/spec/spec_tests/cmap_spec.rb +++ b/spec/spec_tests/cmap_spec.rb @@ -59,9 +59,23 @@ options.merge(spec.pool_options) ).tap do |server| allow(server).to receive(:description).and_return(ClusterConfig.instance.primary_description) + # Since we use a mock for the cluster, run_sdam_flow does not clear + # the pool or mark the server unknown. Manually clear the pool and + # mock the server as unknown. + allow(server).to receive(:unknown!).and_wrap_original do |m, *args| + m.call(*args) + RSpec::Mocks.with_temporary_scope do + allow(server).to receive(:unknown?).and_return(true) + server.pool_internal&.clear(lazy: true) + end + end end ) + if app_name = spec.pool_options[:app_name] + allow(cluster).to receive(:app_metadata).and_return(Mongo::Server::AppMetadata.new({ app_name: app_name })) + end + @client = ClusterTools.instance.direct_client(ClusterConfig.instance.primary_address, database: 'admin') spec.setup(@server, @client, subscriber) diff --git a/spec/spec_tests/data/cmap/pool-checkin.yml b/spec/spec_tests/data/cmap/pool-checkin.yml index b6a4320376..5ff0cd7d8f 100644 --- a/spec/spec_tests/data/cmap/pool-checkin.yml +++ b/spec/spec_tests/data/cmap/pool-checkin.yml @@ -1,6 +1,9 @@ version: 1 style: unit description: must have a method of allowing the driver to check in a connection +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - topology: [ "single", "replicaset", "sharded" ] operations: - name: ready - name: checkOut diff --git a/spec/spec_tests/data/cmap/pool-clear-paused.yml b/spec/spec_tests/data/cmap/pool-clear-paused.yml new file mode 100644 index 0000000000..96a69c8904 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-clear-paused.yml @@ -0,0 +1,18 @@ +version: 1 +style: unit +description: clearing a paused pool emits no events +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - topology: [ "single", "replicaset", "sharded" ] +operations: + - name: clear + - name: ready + - name: clear + - name: clear +events: + - type: ConnectionPoolReady + address: 42 + - type: ConnectionPoolCleared + address: 42 +ignore: + - ConnectionPoolCreated diff --git a/spec/spec_tests/data/cmap/pool-clear-ready.yml b/spec/spec_tests/data/cmap/pool-clear-ready.yml new file mode 100644 index 0000000000..07dfb603cd --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-clear-ready.yml @@ -0,0 +1,39 @@ +version: 1 +style: unit +description: after clear, cannot check out connections until pool ready +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - topology: [ "single", "replicaset", "sharded" ] +operations: + - name: ready + - name: checkOut + - name: clear + - name: start + target: thread1 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCheckOutFailed + count: 1 + - name: ready + - name: checkOut +events: + - type: ConnectionPoolReady + address: 42 + - type: ConnectionCheckedOut + address: 42 + connectionId: 42 + - type: ConnectionPoolCleared + address: 42 + - type: ConnectionCheckOutFailed + address: 42 + reason: connectionError + - type: ConnectionPoolReady + address: 42 + - type: ConnectionCheckedOut + address: 42 +ignore: + - ConnectionPoolCreated + - ConnectionReady + - ConnectionCheckOutStarted + - ConnectionCreated diff --git a/spec/spec_tests/data/cmap/pool-create-min-size-error.yml b/spec/spec_tests/data/cmap/pool-create-min-size-error.yml new file mode 100644 index 0000000000..a9bced2114 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-create-min-size-error.yml @@ -0,0 +1,43 @@ +version: 1 +style: integration +description: error during minPoolSize population clears pool +runOn: + - + # required for appName in fail point + minServerVersion: "4.9.0" + # Remove the topology runOn requirement when cmap specs are adjusted for lbs + - topology: [ "single", "replicaset", "sharded" ] +failPoint: + configureFailPoint: failCommand + # high amount to ensure not interfered with by monitor checks. + mode: { times: 50 } + data: + failCommands: ["isMaster","hello"] + closeConnection: true + appName: "poolCreateMinSizeErrorTest" +poolOptions: + minPoolSize: 1 + backgroundThreadIntervalMS: 50 + appName: "poolCreateMinSizeErrorTest" +operations: + - name: ready + - name: waitForEvent + event: ConnectionPoolCleared + count: 1 + # ensure pool doesn't start making new connections + - name: wait + ms: 200 +events: + - type: ConnectionPoolReady + address: 42 + - type: ConnectionCreated + address: 42 + # The ruby driver clears the pool before closing the connection. + - type: ConnectionPoolCleared + address: 42 + - type: ConnectionClosed + address: 42 + connectionId: 42 + reason: error +ignore: + - ConnectionPoolCreated diff --git a/spec/spec_tests/data/cmap/pool-create-min-size.yml b/spec/spec_tests/data/cmap/pool-create-min-size.yml index a91e33f7a9..e308fb04c8 100644 --- a/spec/spec_tests/data/cmap/pool-create-min-size.yml +++ b/spec/spec_tests/data/cmap/pool-create-min-size.yml @@ -1,6 +1,9 @@ version: 1 style: unit description: must be able to start a pool with minPoolSize connections +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - topology: [ "single", "replicaset", "sharded" ] poolOptions: minPoolSize: 3 operations: diff --git a/spec/spec_tests/data/cmap/pool-ready-ready.yml b/spec/spec_tests/data/cmap/pool-ready-ready.yml new file mode 100644 index 0000000000..0519419ff2 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-ready-ready.yml @@ -0,0 +1,22 @@ +version: 1 +style: unit +description: readying a ready pool emits no events +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - topology: [ "single", "replicaset", "sharded" ] +operations: + - name: ready + - name: ready + - name: ready + # the first ready after this clear should emit an event + - name: clear + - name: ready +events: + - type: ConnectionPoolReady + address: 42 + - type: ConnectionPoolCleared + address: 42 + - type: ConnectionPoolReady + address: 42 +ignore: + - ConnectionPoolCreated diff --git a/spec/spec_tests/data/cmap/pool-ready.yml b/spec/spec_tests/data/cmap/pool-ready.yml new file mode 100644 index 0000000000..730d4d27b7 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-ready.yml @@ -0,0 +1,30 @@ +version: 1 +style: unit +description: pool starts as cleared and becomes ready +operations: + - name: start + target: thread1 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCheckOutFailed + count: 1 + - name: ready + - name: checkOut +events: + - type: ConnectionCheckOutStarted + address: 42 + - type: ConnectionCheckOutFailed + reason: connectionError + address: 42 + - type: ConnectionPoolReady + address: 42 + - type: ConnectionCheckOutStarted + address: 42 + - type: ConnectionCreated + address: 42 + - type: ConnectionCheckedOut + address: 42 +ignore: + - ConnectionPoolCreated + - ConnectionReady diff --git a/spec/spec_tests/data/cmap/wait-queue-timeout.yml b/spec/spec_tests/data/cmap/wait-queue-timeout.yml index 5433c14898..e0fd224810 100644 --- a/spec/spec_tests/data/cmap/wait-queue-timeout.yml +++ b/spec/spec_tests/data/cmap/wait-queue-timeout.yml @@ -1,6 +1,9 @@ version: 1 style: unit description: must aggressively timeout threads enqueued longer than waitQueueTimeoutMS +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - topology: [ "single", "replicaset", "sharded" ] poolOptions: maxPoolSize: 1 waitQueueTimeoutMS: 50 From 4fb852e548217114a242c4920c480d006f801a13 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 15 Nov 2022 09:15:19 +0100 Subject: [PATCH 079/198] RUBY-3179 Fix FLE spec (#2663) --- .../client_side_encryption/kms_tls_options_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/integration/client_side_encryption/kms_tls_options_spec.rb b/spec/integration/client_side_encryption/kms_tls_options_spec.rb index 45b137c1b5..5d1a87618d 100644 --- a/spec/integration/client_side_encryption/kms_tls_options_spec.rb +++ b/spec/integration/client_side_encryption/kms_tls_options_spec.rb @@ -216,7 +216,7 @@ master_key: master_key_template.merge({endpoint: "127.0.0.1:8002"}) } ) - end.to raise_error(Mongo::Error::KmsError, /(SocketError|ECONNRESET)/) + end.to raise_error(Mongo::Error::KmsError, /(certificate_required|SocketError|ECONNRESET)/) end end @@ -286,7 +286,7 @@ master_key: master_key } ) - end.to raise_error(Mongo::Error::KmsError, /(SocketError|ECONNRESET)/) + end.to raise_error(Mongo::Error::KmsError, /(certificate_required|SocketError|ECONNRESET)/) end end From 416a84a10d3133b8fdb57003775ae48b58ae48e9 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 16 Nov 2022 12:04:28 -0500 Subject: [PATCH 080/198] RUBY-2539 Provide explicit guidance on handling command errors that occur before the handshake completes during operation execution (#2664) * RUBY-2539 add minPoolSizeError tests * more working tests * all sdam tests working --- spec/runners/unified/test.rb | 9 +- .../auth-network-timeout-error.yml | 138 ++++++++++++++++++ .../find-network-timeout-error.yml | 119 +++++++++++++++ .../data/sdam_unified/minPoolSize-error.yml | 125 ++++++++++++++++ 4 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 spec/spec_tests/data/sdam_unified/auth-network-timeout-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/find-network-timeout-error.yml create mode 100644 spec/spec_tests/data/sdam_unified/minPoolSize-error.yml diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index 2a11eb69e9..d9c29181ba 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -100,6 +100,13 @@ def generate_entities(es) opts = {} end + # max_pool_size gets automatically set to 3 if not explicitly set by + # the test, therefore, if min_pool_size is set, make sure to set the + # max_pool_size as well to something greater. + if !opts.key?('max_pool_size') && min_pool_size = opts[:min_pool_size] + opts[:max_pool_size] = min_pool_size + 3 + end + if spec.use('useMultipleMongoses') if ClusterConfig.instance.topology == :sharded unless SpecConfig.instance.addresses.length > 1 @@ -121,8 +128,6 @@ def generate_entities(es) store_events.each do |spec| entity_name = spec['id'] event_names = spec['events'] - #event_name = event_name.gsub(/Event$/, '').gsub(/[A-Z]/) { |m| "_#{m}" }.upcase - #event_name = event_name.gsub(/Event$/, '').sub(/./) { |m| m.upcase } event_names.each do |event_name| store_event_names[event_name] = entity_name end diff --git a/spec/spec_tests/data/sdam_unified/auth-network-timeout-error.yml b/spec/spec_tests/data/sdam_unified/auth-network-timeout-error.yml new file mode 100644 index 0000000000..8b29a1e670 --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/auth-network-timeout-error.yml @@ -0,0 +1,138 @@ +--- +description: auth-network-timeout-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + auth: true + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName auth-network-timeout-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Reset server and pool after network timeout error during authentication + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslContinue + blockConnection: true + blockTimeMS: 500 + appName: authNetworkTimeoutErrorTest + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + appname: authNetworkTimeoutErrorTest + # Set a short connect/socket timeout to ensure the fail point causes the + # connection establishment to timeout. + connectTimeoutMS: 250 + socketTimeoutMS: 250 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # Perform another operation to ensure the node is rediscovered. + - name: insertMany + object: *collection + arguments: + documents: + - _id: 5 + - _id: 6 + # Assert the server was marked Unknown and pool was cleared exactly once. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + expectEvents: + # Note: The first insert command is never attempted because connection + # checkout fails. + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + insert: auth-network-timeout-error + documents: + - _id: 5 + - _id: 6 + commandName: insert + databaseName: *databaseName + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 5 + - _id: 6 diff --git a/spec/spec_tests/data/sdam_unified/find-network-timeout-error.yml b/spec/spec_tests/data/sdam_unified/find-network-timeout-error.yml new file mode 100644 index 0000000000..30c4809ccf --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/find-network-timeout-error.yml @@ -0,0 +1,119 @@ +--- +description: find-network-timeout-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName find-network-timeout-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Ignore network timeout error on find + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + blockConnection: true + blockTimeMS: 500 + appName: findNetworkTimeoutErrorTest + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - serverDescriptionChangedEvent + - poolClearedEvent + uriOptions: + retryWrites: false + retryReads: false + appname: findNetworkTimeoutErrorTest + # Set a short socket timeout to ensure the find command times out. + socketTimeoutMS: 250 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: find + object: *collection + arguments: + filter: + _id: 1 + expectError: + isError: true + # Perform another operation to ensure the node is still usable. + - name: insertOne + object: *collection + arguments: + document: + _id: 3 + # Assert the server was not marked Unknown and the pool was not cleared. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 0 + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 0 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + find: find-network-timeout-error + commandName: find + databaseName: *databaseName + - commandStartedEvent: + command: + insert: find-network-timeout-error + documents: + - _id: 3 + commandName: insert + databaseName: *databaseName + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - _id: 2 + - _id: 3 diff --git a/spec/spec_tests/data/sdam_unified/minPoolSize-error.yml b/spec/spec_tests/data/sdam_unified/minPoolSize-error.yml new file mode 100644 index 0000000000..cc72af578b --- /dev/null +++ b/spec/spec_tests/data/sdam_unified/minPoolSize-error.yml @@ -0,0 +1,125 @@ +--- +description: minPoolSize-error + +schemaVersion: "1.10" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.9" + serverless: forbid + topologies: + - single + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName sdam-minPoolSize-error + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Network error on minPoolSize background creation + operations: + # Configure the initial monitor handshake to succeed but the + # first or second background minPoolSize establishments to fail. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + skip: 3 + data: + failCommands: + - hello + - isMaster + appName: SDAMminPoolSizeError + closeConnection: true + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - serverDescriptionChangedEvent + - poolClearedEvent + - poolReadyEvent + uriOptions: + heartbeatFrequencyMS: 10000 + appname: SDAMminPoolSizeError + minPoolSize: 10 + serverSelectionTimeoutMS: 1000 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + # Wait for monitor to succeed handshake and mark the pool as ready. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolReadyEvent: {} + count: 1 + # Background connection establishment ensuring minPoolSize should fail, + # causing the pool to be cleared. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + # The server should be marked as Unknown as part of this. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + # Executing a command should fail server selection due to not being able + # to find the primary. + - name: runCommand + object: *database + arguments: + command: + ping: {} + commandName: ping + expectError: + isError: true + # Disable the failpoint, allowing the monitor to discover the primary again. + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: "off" + client: *setupClient + # Perform an operation to ensure the node is discovered. + - name: runCommand + object: *database + arguments: + command: + ping: 1 + commandName: ping + # Assert that the monitor discovered the primary and mark the pool as ready again. + - name: assertEventCount + object: testRunner + arguments: + client: *client + event: + poolReadyEvent: {} + count: 2 From aef3515d2019aa451cd8b975b4c0a31e1e6e42de Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 17 Nov 2022 09:36:12 +0100 Subject: [PATCH 081/198] RUBY-3168 Add tests for not spawn mongocryptd (#2665) --- .../mongocryptd_prose_spec.rb | 103 ++++++++++++++++++ .../omit_mongocryptd_client_prose_spec.rb | 73 ------------- 2 files changed, 103 insertions(+), 73 deletions(-) create mode 100644 spec/integration/client_side_encryption/mongocryptd_prose_spec.rb delete mode 100644 spec/integration/client_side_encryption/omit_mongocryptd_client_prose_spec.rb diff --git a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb new file mode 100644 index 0000000000..46cfbc21f8 --- /dev/null +++ b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'spec_helper' + +describe 'mongcryptd prose tests' do + require_libmongocrypt + require_enterprise + + include_context 'define shared FLE helpers' + include_context 'with local kms_providers' + + let(:mongocryptd_uri) do + "mongodb://localhost:27777" + end + + let(:encryption_client) do + new_local_client( + SpecConfig.instance.addresses, + SpecConfig.instance.test_options.merge( + auto_encryption_options: { + kms_providers: kms_providers, + kms_tls_options: kms_tls_options, + key_vault_namespace: key_vault_namespace, + schema_map: { "auto_encryption.users" => schema_map }, + extra_options: extra_options, + }, + database: 'auto_encryption', + ), + ) + end + + context 'when shared library is loaded' do + let(:extra_options) do + { + crypt_shared_lib_path: SpecConfig.instance.crypt_shared_lib_path, + mongocryptd_uri: mongocryptd_uri + } + end + + let!(:connect_attempt) do + Class.new do + def lock + @lock ||= Mutex.new + end + + def done? + lock.synchronize do + !!@done + end + end + + def done! + lock.synchronize do + @done = true + end + end + end.new + end + + let!(:listener) do + Thread.new do + TCPServer.new(27777).accept + connect_attempt.done! + end + end + + after do + listener.exit + end + + it 'does not try to connect to mongocryptd' do + skip 'This test requires crypt shared library' unless SpecConfig.instance.crypt_shared_lib_path + + encryption_client[:users].insert_one(ssn: ssn) + expect(connect_attempt.done?).to eq(false) + end + end + + context 'when shared library is required' do + let(:extra_options) do + { + crypt_shared_lib_path: SpecConfig.instance.crypt_shared_lib_path, + crypt_shared_lib_required: true, + mongocryptd_uri: mongocryptd_uri, + mongocryptd_spawn_args: [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27777"] + } + end + + it 'does not spawn mongocryptd' do + skip 'This test requires crypt shared library' unless SpecConfig.instance.crypt_shared_lib_path + + expect do + encryption_client[:users].insert_one(ssn: ssn) + end.not_to raise_error + + mongocryptd_client = new_local_client(mongocryptd_uri) + expect do + mongocryptd_client.database.command(hello: 1) + end.to raise_error(Mongo::Error::NoServerAvailable) + end + end +end diff --git a/spec/integration/client_side_encryption/omit_mongocryptd_client_prose_spec.rb b/spec/integration/client_side_encryption/omit_mongocryptd_client_prose_spec.rb deleted file mode 100644 index e8204d4a2b..0000000000 --- a/spec/integration/client_side_encryption/omit_mongocryptd_client_prose_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -require 'spec_helper' - -describe 'Do not connect to mongocryptd if shared library is loaded' do - require_libmongocrypt - require_enterprise - - include_context 'define shared FLE helpers' - include_context 'with local kms_providers' - - let(:extra_options) do - { - crypt_shared_lib_path: SpecConfig.instance.crypt_shared_lib_path, - mongocryptd_uri: "mongodb://localhost:27777" - } - end - - let(:encryption_client) do - new_local_client( - SpecConfig.instance.addresses, - SpecConfig.instance.test_options.merge( - auto_encryption_options: { - kms_providers: kms_providers, - kms_tls_options: kms_tls_options, - key_vault_namespace: key_vault_namespace, - schema_map: { "auto_encryption.users" => schema_map }, - extra_options: extra_options, - }, - database: 'auto_encryption', - ), - ) - end - - let!(:connect_attempt) do - Class.new do - def lock - @lock ||= Mutex.new - end - - def done? - lock.synchronize do - !!@done - end - end - - def done! - lock.synchronize do - @done = true - end - end - end.new - end - - let!(:listener) do - Thread.new do - TCPServer.new(27777).accept - connect_attempt.done! - end - end - - after do - listener.exit - end - - it 'does not try to connect to mongocryptd' do - skip 'This test requires crypt shared library' unless SpecConfig.instance.crypt_shared_lib_path - - encryption_client[:users].insert_one(ssn: ssn) - expect(connect_attempt.done?).to eq(false) - end -end From 8b9387e90b72e778185e735a02db037edec4ca6a Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 21 Nov 2022 12:25:04 -0500 Subject: [PATCH 082/198] RUBY-3171 Improve test coverage for retryable handshake errors (#2668) * RUBY-3171 finish retryable reads tests * add retryable writes tests --- spec/runners/unified/crud_operations.rb | 10 +- spec/runners/unified/ddl_operations.rb | 20 +- spec/runners/unified/support_operations.rb | 8 +- spec/runners/unified/test.rb | 2 +- .../unified/handshakeError.yml | 1319 ++++++++++++++++- .../unified/handshakeError.yml | 764 +++++++++- 6 files changed, 2007 insertions(+), 116 deletions(-) diff --git a/spec/runners/unified/crud_operations.rb b/spec/runners/unified/crud_operations.rb index 5544757617..e1cad3041d 100644 --- a/spec/runners/unified/crud_operations.rb +++ b/spec/runners/unified/crud_operations.rb @@ -6,6 +6,14 @@ module Unified module CrudOperations def find(op) + get_find_view(op).to_a + end + + def find_one(op) + get_find_view(op).first + end + + def get_find_view(op) collection = entities.get(:collection, op.use!('object')) use_arguments(op) do |args| opts = { @@ -37,7 +45,7 @@ def find(op) if projection = args.use('projection') req = req.projection(projection) end - result = req.to_a + req end end diff --git a/spec/runners/unified/ddl_operations.rb b/spec/runners/unified/ddl_operations.rb index 1efaedcbab..7424ffab93 100644 --- a/spec/runners/unified/ddl_operations.rb +++ b/spec/runners/unified/ddl_operations.rb @@ -6,13 +6,21 @@ module Unified module DdlOperations def list_databases(op) + list_dbs(op, name_only: false) + end + + def list_database_names(op) + list_dbs(op, name_only: false) + end + + def list_dbs(op, name_only: false) client = entities.get(:client, op.use!('object')) use_arguments(op) do |args| opts = {} if session = args.use('session') opts[:session] = entities.get(:session, session) end - client.list_databases({}, false, **opts) + client.list_databases(args.use('filter') || {}, name_only, **opts) end end @@ -47,6 +55,14 @@ def create_collection(op) end def list_collections(op) + list_colls(op, name_only: false) + end + + def list_collection_names(op) + list_colls(op, name_only: true) + end + + def list_colls(op, name_only: false) database = entities.get(:database, op.use!('object')) use_arguments(op) do |args| opts = {} @@ -56,7 +72,7 @@ def list_collections(op) if filter = args.use('filter') opts[:filter] = filter end - database.list_collections(**opts) + database.list_collections(**opts.merge(name_only: name_only)) end end diff --git a/spec/runners/unified/support_operations.rb b/spec/runners/unified/support_operations.rb index e52666fc85..c83d98448a 100644 --- a/spec/runners/unified/support_operations.rb +++ b/spec/runners/unified/support_operations.rb @@ -29,7 +29,13 @@ def fail_point(op) consume_test_runner(op) use_arguments(op) do |args| client = entities.get(:client, args.use!('client')) - client.command(args.use('failPoint')) + client.command(fp = args.use('failPoint')) + + $disable_fail_points ||= [] + $disable_fail_points << [ + fp, + ClusterConfig.instance.primary_address, + ] end end diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index d9c29181ba..c5bd6f453a 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -390,7 +390,7 @@ def execute_operation(op) method_name = "_#{name}" end - if ["modify_collection"].include?(name.to_s) + if ["modify_collection", "list_index_names"].include?(name.to_s) skip "Mongo Ruby Driver does not support #{name.to_s}" end diff --git a/spec/spec_tests/data/retryable_reads/unified/handshakeError.yml b/spec/spec_tests/data/retryable_reads/unified/handshakeError.yml index 90e6947dd1..f2b1ec982c 100644 --- a/spec/spec_tests/data/retryable_reads/unified/handshakeError.yml +++ b/spec/spec_tests/data/retryable_reads/unified/handshakeError.yml @@ -1,6 +1,9 @@ +# Tests in this file are generated from handshakeError.yml.template. + description: "retryable reads handshake failures" -schemaVersion: "1.3" +# 1.4 is required for "serverless: forbid". +schemaVersion: "1.4" runOnRequirements: - minServerVersion: "4.2" @@ -9,16 +12,20 @@ runOnRequirements: createEntities: - client: - id: &client0 client0 + id: &client client useMultipleMongoses: false - observeEvents: [commandStartedEvent, connectionCheckOutStartedEvent] + observeEvents: + - connectionCheckOutStartedEvent + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent - database: - id: &database0 database0 - client: *client0 - databaseName: &databaseName retryable-handshake-tests + id: &database database + client: *client + databaseName: &databaseName retryable-reads-handshake-tests - collection: - id: &collection0 collection0 - database: *database0 + id: &collection collection + database: *database collectionName: &collectionName coll initialData: @@ -30,100 +37,1306 @@ initialData: - { _id: 3, x: 33 } tests: - - description: "find succeeds after retryable handshake network error" + # Because setting a failPoint creates a connection in the connection pool, run + # a ping operation that fails immediately after the failPoint operation in + # order to discard the connection before running the actual operation to be + # tested. The saslContinue command is used to avoid SDAM errors. + # + # Description of events: + # - Failpoint operation. + # - Creates a connection in the connection pool that must be closed. + # - Ping operation. + # - Triggers failpoint (first time). + # - Closes the connection made by the fail point operation. + # - Test operation. + # - New connection is created. + # - Triggers failpoint (second time). + # - Tests whether operation successfully retries the handshake and succeeds. + + - description: "client.listDatabases succeeds after retryable handshake network error" operations: - - name: failPoint # fail the next connection establishment + - name: failPoint object: testRunner arguments: - client: *client0 + client: *client failPoint: configureFailPoint: failCommand mode: { times: 2 } data: - # use saslContinue here to avoid SDAM errors - # this failPoint itself will create a usable connection in the connection pool - # so we run a ping (that also fails) in order to discard the connection - # before testing our read operation "find" - failCommands: [saslContinue, ping] + failCommands: [ping, saslContinue] closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listDatabases + object: *client + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listDatabases + - commandSucceededEvent: + commandName: listDatabases + - description: "client.listDatabases succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true - name: runCommand - object: *database0 + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listDatabases + object: *client arguments: - commandName: ping - command: { ping: 1 } - expectError: - isError: true + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listDatabases + - commandSucceededEvent: + commandName: listDatabases - - name: find - object: *collection0 + - description: "client.listDatabaseNames succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner arguments: - filter: { _id: 2 } - expectResult: [{ _id: 2, x: 22 }] + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listDatabaseNames + object: *client + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listDatabases + - commandSucceededEvent: + commandName: listDatabases + - description: "client.listDatabaseNames succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listDatabaseNames + object: *client expectEvents: - - client: *client0 + - client: *client eventType: cmap events: - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - - client: *client0 + - client: *client events: - commandStartedEvent: - command: - ping: 1 + command: { ping: 1 } databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listDatabases + - commandSucceededEvent: + commandName: listDatabases + + - description: "client.createChangeStream succeeds after retryable handshake network error" + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: createChangeStream + object: *client + arguments: + pipeline: [] + saveResultAsEntity: changeStream + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: - commandStartedEvent: - command: - find: *collectionName - filter: { _id: 2 } + command: { ping: 1 } databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate - - description: "find succeeds after retryable handshake network error (ShutdownInProgress)" + - description: "client.createChangeStream succeeds after retryable handshake server error (ShutdownInProgress)" + runOnRequirements: + - serverless: forbid operations: - - name: failPoint # fail the next connection establishment + - name: failPoint object: testRunner arguments: - client: *client0 + client: *client failPoint: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: [saslContinue, ping] - errorCode: 91 # ShutdownInProgress + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: createChangeStream + object: *client + arguments: + pipeline: [] + saveResultAsEntity: changeStream + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + - description: "database.aggregate succeeds after retryable handshake network error" + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true - name: runCommand - object: *database0 + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: aggregate + object: *database arguments: - commandName: ping - command: { ping: 1 } - expectError: - isError: true + pipeline: [ { $listLocalSessions: {} }, { $limit: 1 } ] + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate - - name: find - object: *collection0 + - description: "database.aggregate succeeds after retryable handshake server error (ShutdownInProgress)" + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: aggregate + object: *database + arguments: + pipeline: [ { $listLocalSessions: {} }, { $limit: 1 } ] + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + + - description: "database.listCollections succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listCollections + object: *database + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listCollections + - commandSucceededEvent: + commandName: listCollections + + - description: "database.listCollections succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listCollections + object: *database + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listCollections + - commandSucceededEvent: + commandName: listCollections + + - description: "database.listCollectionNames succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listCollectionNames + object: *database + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listCollections + - commandSucceededEvent: + commandName: listCollections + + - description: "database.listCollectionNames succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner arguments: - filter: { _id: 2 } - expectResult: [{ _id: 2, x: 22 }] + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listCollectionNames + object: *database + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listCollections + - commandSucceededEvent: + commandName: listCollections + - description: "database.createChangeStream succeeds after retryable handshake network error" + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: createChangeStream + object: *database + arguments: + pipeline: [] + saveResultAsEntity: changeStream expectEvents: - - client: *client0 + - client: *client eventType: cmap events: - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - - client: *client0 + - client: *client events: - commandStartedEvent: - command: - ping: 1 + command: { ping: 1 } databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + + - description: "database.createChangeStream succeeds after retryable handshake server error (ShutdownInProgress)" + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: createChangeStream + object: *database + arguments: + pipeline: [] + saveResultAsEntity: changeStream + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: - commandStartedEvent: - command: - find: *collectionName - filter: { _id: 2 } + command: { ping: 1 } databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + + - description: "collection.aggregate succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: aggregate + object: *collection + arguments: + pipeline: [] + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + + - description: "collection.aggregate succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: aggregate + object: *collection + arguments: + pipeline: [] + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + + - description: "collection.countDocuments succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: countDocuments + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + + - description: "collection.countDocuments succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: countDocuments + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + + - description: "collection.estimatedDocumentCount succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: estimatedDocumentCount + object: *collection + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: count + - commandSucceededEvent: + commandName: count + + - description: "collection.estimatedDocumentCount succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: estimatedDocumentCount + object: *collection + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: count + - commandSucceededEvent: + commandName: count + + - description: "collection.distinct succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: distinct + object: *collection + arguments: + fieldName: x + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: distinct + - commandSucceededEvent: + commandName: distinct + + - description: "collection.distinct succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: distinct + object: *collection + arguments: + fieldName: x + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: distinct + - commandSucceededEvent: + commandName: distinct + + - description: "collection.find succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: find + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: find + - commandSucceededEvent: + commandName: find + + - description: "collection.find succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: find + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: find + - commandSucceededEvent: + commandName: find + + - description: "collection.findOne succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOne + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: find + - commandSucceededEvent: + commandName: find + + - description: "collection.findOne succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOne + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: find + - commandSucceededEvent: + commandName: find + + - description: "collection.listIndexes succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listIndexes + object: *collection + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listIndexes + - commandSucceededEvent: + commandName: listIndexes + + - description: "collection.listIndexes succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listIndexes + object: *collection + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listIndexes + - commandSucceededEvent: + commandName: listIndexes + + - description: "collection.listIndexNames succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listIndexNames + object: *collection + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listIndexes + - commandSucceededEvent: + commandName: listIndexes + + - description: "collection.listIndexNames succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: listIndexNames + object: *collection + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: listIndexes + - commandSucceededEvent: + commandName: listIndexes + + - description: "collection.createChangeStream succeeds after retryable handshake network error" + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: createChangeStream + object: *collection + arguments: + pipeline: [] + saveResultAsEntity: changeStream + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate + + - description: "collection.createChangeStream succeeds after retryable handshake server error (ShutdownInProgress)" + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: createChangeStream + object: *collection + arguments: + pipeline: [] + saveResultAsEntity: changeStream + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: aggregate + - commandSucceededEvent: + commandName: aggregate diff --git a/spec/spec_tests/data/retryable_writes/unified/handshakeError.yml b/spec/spec_tests/data/retryable_writes/unified/handshakeError.yml index bac3a7dba3..9b2774bc77 100644 --- a/spec/spec_tests/data/retryable_writes/unified/handshakeError.yml +++ b/spec/spec_tests/data/retryable_writes/unified/handshakeError.yml @@ -1,3 +1,5 @@ +# Tests in this file are generated from handshakeError.yml.template. + description: "retryable writes handshake failures" schemaVersion: "1.3" @@ -9,16 +11,20 @@ runOnRequirements: createEntities: - client: - id: &client0 client0 + id: &client client useMultipleMongoses: false - observeEvents: [commandStartedEvent, connectionCheckOutStartedEvent] + observeEvents: + - connectionCheckOutStartedEvent + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent - database: - id: &database0 database0 - client: *client0 - databaseName: &databaseName retryable-handshake-tests + id: &database database + client: *client + databaseName: &databaseName retryable-writes-handshake-tests - collection: - id: &collection0 collection0 - database: *database0 + id: &collection collection + database: *database collectionName: &collectionName coll initialData: @@ -28,110 +34,752 @@ initialData: - { _id: 1, x: 11 } tests: - - description: "InsertOne succeeds after retryable handshake error" + # Because setting a failPoint creates a connection in the connection pool, run + # a ping operation that fails immediately after the failPoint operation in + # order to discard the connection before running the actual operation to be + # tested. The saslContinue command is used to avoid SDAM errors. + # + # Description of events: + # - Failpoint operation. + # - Creates a connection in the connection pool that must be closed. + # - Ping operation. + # - Triggers failpoint (first time). + # - Closes the connection made by the fail point operation. + # - Test operation. + # - New connection is created. + # - Triggers failpoint (second time). + # - Tests whether operation successfully retries the handshake and succeeds. + + - description: "collection.insertOne succeeds after retryable handshake network error" operations: - - name: failPoint # fail the next connection establishment + - name: failPoint object: testRunner arguments: - client: *client0 + client: *client failPoint: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: [saslContinue, ping] + failCommands: [ping, saslContinue] closeConnection: true - - name: runCommand - object: *database0 + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: insertOne + object: *collection arguments: - commandName: ping - command: { ping: 1 } - expectError: - isError: true + document: { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + - description: "collection.insertOne succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } - name: insertOne - object: *collection0 + object: *collection arguments: document: { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + - description: "collection.insertMany succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: insertMany + object: *collection + arguments: + documents: + - { _id: 2, x: 22 } expectEvents: - - client: *client0 + - client: *client eventType: cmap events: - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - - client: *client0 + - client: *client events: - commandStartedEvent: - command: - ping: 1 + command: { ping: 1 } databaseName: *databaseName + - commandFailedEvent: + commandName: ping - commandStartedEvent: - command: - insert: *collectionName - documents: [{ _id: 2, x: 22 }] commandName: insert + - commandSucceededEvent: + commandName: insert + + - description: "collection.insertMany succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: insertMany + object: *collection + arguments: + documents: + - { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert - outcome: - - collectionName: *collectionName - databaseName: *databaseName - documents: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } # The write was still applied + - description: "collection.deleteOne succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: deleteOne + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: delete + - commandSucceededEvent: + commandName: delete - - description: "InsertOne succeeds after retryable handshake error ShutdownInProgress" + - description: "collection.deleteOne succeeds after retryable handshake server error (ShutdownInProgress)" operations: - - name: failPoint # fail the next connection establishment + - name: failPoint object: testRunner arguments: - client: *client0 + client: *client failPoint: configureFailPoint: failCommand mode: { times: 2 } data: - failCommands: [saslContinue, ping] - errorCode: 91 # ShutdownInProgress + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: deleteOne + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: delete + - commandSucceededEvent: + commandName: delete + - description: "collection.replaceOne succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true - name: runCommand - object: *database0 + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: replaceOne + object: *collection arguments: - commandName: ping - command: { ping: 1 } - expectError: - isError: true + filter: {} + replacement: { x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update - - name: insertOne - object: *collection0 + - description: "collection.replaceOne succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner arguments: - document: { _id: 2, x: 22 } + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: replaceOne + object: *collection + arguments: + filter: {} + replacement: { x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update + - description: "collection.updateOne succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: updateOne + object: *collection + arguments: + filter: {} + update: { $set: { x: 22 } } expectEvents: - - client: *client0 + - client: *client eventType: cmap events: - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - { connectionCheckOutStartedEvent: {} } - - client: *client0 + - client: *client events: - commandStartedEvent: - command: - ping: 1 + command: { ping: 1 } databaseName: *databaseName + - commandFailedEvent: + commandName: ping - commandStartedEvent: - command: - insert: *collectionName - documents: [{ _id: 2, x: 22 }] - commandName: insert + commandName: update + - commandSucceededEvent: + commandName: update + + - description: "collection.updateOne succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: updateOne + object: *collection + arguments: + filter: {} + update: { $set: { x: 22 } } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update + + - description: "collection.findOneAndDelete succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndDelete + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndDelete succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndDelete + object: *collection + arguments: + filter: {} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndReplace succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndReplace + object: *collection + arguments: + filter: {} + replacement: { x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndReplace succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndReplace + object: *collection + arguments: + filter: {} + replacement: { x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndUpdate succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndUpdate + object: *collection + arguments: + filter: {} + update: { $set: { x: 22 } } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.findOneAndUpdate succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: findOneAndUpdate + object: *collection + arguments: + filter: {} + update: { $set: { x: 22 } } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: findAndModify + - commandSucceededEvent: + commandName: findAndModify + + - description: "collection.bulkWrite succeeds after retryable handshake network error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: bulkWrite + object: *collection + arguments: + requests: + - insertOne: + document: { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert - outcome: - - collectionName: *collectionName - databaseName: *databaseName - documents: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } # The write was still applied + - description: "collection.bulkWrite succeeds after retryable handshake server error (ShutdownInProgress)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: bulkWrite + object: *collection + arguments: + requests: + - insertOne: + document: { _id: 2, x: 22 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert From d3c2d72a6540daa1220d8a8dee96e8c4be451a74 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 21 Nov 2022 12:25:16 -0500 Subject: [PATCH 083/198] RUBY-3058 sync causal consistency test (#2667) --- ...it-sessions-default-causal-consistency.yml | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 spec/spec_tests/data/sessions_unified/implicit-sessions-default-causal-consistency.yml diff --git a/spec/spec_tests/data/sessions_unified/implicit-sessions-default-causal-consistency.yml b/spec/spec_tests/data/sessions_unified/implicit-sessions-default-causal-consistency.yml new file mode 100644 index 0000000000..052c6dac20 --- /dev/null +++ b/spec/spec_tests/data/sessions_unified/implicit-sessions-default-causal-consistency.yml @@ -0,0 +1,119 @@ +description: "implicit sessions default causal consistency" + +schemaVersion: "1.3" + +runOnRequirements: + - minServerVersion: "4.2" + topologies: [replicaset, sharded, load-balanced] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [commandStartedEvent] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName implicit-cc-tests + - collection: + id: &collectionDefault collectionDefault + database: *database0 + collectionName: &collectionNameDefault coll-default + - collection: + id: &collectionSnapshot collectionSnapshot + database: *database0 + collectionName: &collectionNameSnapshot coll-snapshot + collectionOptions: + readConcern: { level: snapshot } + - collection: + id: &collectionlinearizable collectionlinearizable + database: *database0 + collectionName: &collectionNamelinearizable coll-linearizable + collectionOptions: + readConcern: { level: linearizable } + +initialData: + - collectionName: *collectionNameDefault + databaseName: *databaseName + documents: + - { _id: 1, x: default } + - collectionName: *collectionNameSnapshot + databaseName: *databaseName + documents: + - { _id: 1, x: snapshot } + - collectionName: *collectionNamelinearizable + databaseName: *databaseName + documents: + - { _id: 1, x: linearizable } + +tests: + - description: "readConcern is not sent on retried read in implicit session when readConcern level is not specified" + operations: + - &failPointCommand + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [find] + errorCode: 11600 #InterruptedAtShutdown + - name: find + object: *collectionDefault + arguments: + filter: {} + expectResult: [{ _id: 1, x: default }] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &commandStartedEventDefault + command: + find: *collectionNameDefault + filter: {} + readConcern: { $$exists: false } + databaseName: *databaseName + - commandStartedEvent: *commandStartedEventDefault + + - description: "afterClusterTime is not sent on retried read in implicit session when readConcern level is snapshot" + runOnRequirements: + - minServerVersion: "5.0" + operations: + - *failPointCommand + - name: find + object: *collectionSnapshot + arguments: + filter: {} + expectResult: [{ _id: 1, x: snapshot }] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &commandStartedEventSnapshot + command: + find: *collectionNameSnapshot + filter: {} + readConcern: + { level: snapshot, afterClusterTime: { $$exists: false } } + databaseName: *databaseName + - commandStartedEvent: *commandStartedEventSnapshot + + - description: "afterClusterTime is not sent on retried read in implicit session when readConcern level is linearizable" + operations: + - *failPointCommand + - name: find + object: *collectionlinearizable + arguments: + filter: {} + expectResult: [{ _id: 1, x: linearizable }] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &commandStartedEventLinearizable + command: + find: *collectionNamelinearizable + filter: {} + readConcern: + { level: linearizable, afterClusterTime: { $$exists: false } } + databaseName: *databaseName + - commandStartedEvent: *commandStartedEventLinearizable From 4022460accd57cf4b53b77a53534c5b1da937e8c Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 23 Nov 2022 09:43:08 +0100 Subject: [PATCH 084/198] RUBY-3070 Add atlas to main test suite (#2669) --- .evergreen/config-atlas.yml | 868 ---------------------------- .evergreen/config.yml | 44 +- .evergreen/config/atlas.yml.erb | 19 - .evergreen/config/common.yml.erb | 94 +-- .evergreen/config/standard.yml.erb | 18 + .evergreen/run-tests-atlas.sh | 2 +- .evergreen/run-tests-serverless.sh | 2 +- .evergreen/update-evergreen-configs | 12 +- spec/atlas/operations_spec.rb | 9 +- 9 files changed, 84 insertions(+), 984 deletions(-) delete mode 100644 .evergreen/config-atlas.yml delete mode 100644 .evergreen/config/atlas.yml.erb diff --git a/.evergreen/config-atlas.yml b/.evergreen/config-atlas.yml deleted file mode 100644 index ec21311701..0000000000 --- a/.evergreen/config-atlas.yml +++ /dev/null @@ -1,868 +0,0 @@ -# GENERATED FILE - DO NOT EDIT. -# Run ./.evergreen/update-evergreen-configs to regenerate this file. - -# When a task that used to pass starts to fail, go through all versions that -# may have been skipped to detect when the task started failing. -stepback: true - -# Fail builds when pre tasks fail. -pre_error_fails_task: true - -# Mark a failure as a system/bootstrap failure (purple box) rather then a task -# failure by default. -# Actual testing tasks are marked with `type: test` -command_type: system - -# Protect ourself against rogue test case, or curl gone wild, that runs forever. -exec_timeout_secs: 2700 - - -# What to do when evergreen hits the timeout (`post:` tasks are run automatically) -timeout: - - command: shell.exec - params: - script: | - true - -functions: - "fetch source": - # Executes git clone and applies the submitted patch, if any - - command: git.get_project - params: - directory: "src" - - command: shell.exec - params: - working_dir: "src" - script: | - set -ex - - git submodule update --init --recursive - - "fetch egos": - - command: shell.exec - params: - working_dir: "src" - script: | - set -e - - # Python version: - #curl -sfLo egos https://raw.githubusercontent.com/p-mongo/egos/master/egos - - # C version: - curl --retry 3 -sfLo egos.c https://raw.githubusercontent.com/p-mongo/egos/master/egos.c - gcc -O2 -g -oegos -lrt egos.c - - "create expansions": - # Make an evergreen expansion file with dynamic values - - command: shell.exec - params: - working_dir: "src" - script: | - # Get the current unique version of this checkout - if [ "${is_patch}" = "true" ]; then - CURRENT_VERSION=$(git describe)-patch-${version_id} - else - CURRENT_VERSION=latest - fi - - export DRIVERS_TOOLS="$(pwd)/.mod/drivers-evergreen-tools" - - # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory - if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin - export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS) - fi - - export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration" - export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" - export UPLOAD_BUCKET="${project}" - export PROJECT_DIRECTORY="$(pwd)" - - cat < expansion.yml - CURRENT_VERSION: "$CURRENT_VERSION" - DRIVERS_TOOLS: "$DRIVERS_TOOLS" - MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME" - MONGODB_BINARIES: "$MONGODB_BINARIES" - UPLOAD_BUCKET: "$UPLOAD_BUCKET" - PROJECT_DIRECTORY: "$PROJECT_DIRECTORY" - PREPARE_SHELL: | - set -o errexit - set -o xtrace - export DRIVERS_TOOLS="$DRIVERS_TOOLS" - export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME" - export MONGODB_BINARIES="$MONGODB_BINARIES" - export UPLOAD_BUCKET="$UPLOAD_BUCKET" - export PROJECT_DIRECTORY="$PROJECT_DIRECTORY" - - # TMPDIR cannot be too long, see - # https://github.com/broadinstitute/cromwell/issues/3647. - # Why is it even set at all? - #export TMPDIR="$MONGO_ORCHESTRATION_HOME/db" - export PATH="$MONGODB_BINARIES:$PATH" - export PROJECT="${project}" - export ATLAS_REPLICA_SET_URI="${atlas_replica_set_uri}" - export ATLAS_SHARDED_URI="${atlas_sharded_uri}" - export ATLAS_FREE_TIER_URI="${atlas_free_tier_uri}" - export ATLAS_TLS11_URI="${atlas_tls11_uri}" - export ATLAS_TLS12_URI="${atlas_tls12_uri}" - export ATLAS_SERVERLESS_URI="${atlas_serverless_uri}" - export ATLAS_SERVERLESS_LB_URI="${atlas_serverless_lb_uri}" - export RVM_RUBY="${RVM_RUBY}" - export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" - export SERVERLESS_API_PUBLIC_KEY="${SERVERLESS_API_PUBLIC_KEY}" - export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" - export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" - export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" - export AWS_ACCESS_KEY_ID="${fle_aws_key}" - export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" - export AWS_DEFAULT_REGION="${fle_aws_region}" - EOT - # See what we've done - cat expansion.yml - - # Load the expansion file to make an evergreen variable with the current - # unique version - - command: expansions.update - params: - file: src/expansion.yml - - "delete serverless instance": - - command: shell.exec - params: - working_dir: "src" - shell: bash - script: | - ${PREPARE_SHELL} - if [[ -n "${SERVERLESS_INSTANCE_NAME}" ]]; then - SERVERLESS_INSTANCE_NAME=${SERVERLESS_INSTANCE_NAME} .evergreen/serverless/delete-instance.sh - fi - - "export AWS auth credentials": - - command: shell.exec - type: test - params: - silent: true - working_dir: "src" - script: | - cat < .env.private - IAM_AUTH_ASSUME_AWS_ACCOUNT="${iam_auth_assume_aws_account}" - IAM_AUTH_ASSUME_AWS_SECRET_ACCESS_KEY="${iam_auth_assume_aws_secret_access_key}" - IAM_AUTH_ASSUME_ROLE_NAME="${iam_auth_assume_role_name}" - IAM_AUTH_EC2_INSTANCE_ACCOUNT="${iam_auth_ec2_instance_account}" - IAM_AUTH_EC2_INSTANCE_PROFILE="${iam_auth_ec2_instance_profile}" - IAM_AUTH_EC2_INSTANCE_SECRET_ACCESS_KEY="${iam_auth_ec2_instance_secret_access_key}" - IAM_AUTH_ECS_ACCOUNT="${iam_auth_ecs_account}" - IAM_AUTH_ECS_ACCOUNT_ARN="${iam_auth_ecs_account_arn}" - IAM_AUTH_ECS_CLUSTER="${iam_auth_ecs_cluster}" - IAM_AUTH_ECS_SECRET_ACCESS_KEY="${iam_auth_ecs_secret_access_key}" - IAM_AUTH_ECS_SECURITY_GROUP="${iam_auth_ecs_security_group}" - IAM_AUTH_ECS_SUBNET_A="${iam_auth_ecs_subnet_a}" - IAM_AUTH_ECS_SUBNET_B="${iam_auth_ecs_subnet_b}" - IAM_AUTH_ECS_TASK_DEFINITION="${iam_auth_ecs_task_definition}" - EOT - - "export FLE credentials": - - command: shell.exec - type: test - params: - silent: true - working_dir: "src" - script: | - cat < .env.private - MONGO_RUBY_DRIVER_AWS_KEY="${fle_aws_key}" - MONGO_RUBY_DRIVER_AWS_SECRET="${fle_aws_secret}" - MONGO_RUBY_DRIVER_AWS_REGION="${fle_aws_region}" - MONGO_RUBY_DRIVER_AWS_ARN="${fle_aws_arn}" - - MONGO_RUBY_DRIVER_AZURE_TENANT_ID="${fle_azure_tenant_id}" - MONGO_RUBY_DRIVER_AZURE_CLIENT_ID="${fle_azure_client_id}" - MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET="${fle_azure_client_secret}" - MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT="${fle_azure_identity_platform_endpoint}" - MONGO_RUBY_DRIVER_AZURE_KEY_VAULT_ENDPOINT="${fle_azure_key_vault_endpoint}" - MONGO_RUBY_DRIVER_AZURE_KEY_NAME="${fle_azure_key_name}" - - MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}" - MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY="${fle_gcp_private_key}" - MONGO_RUBY_DRIVER_GCP_PROJECT_ID="${fle_gcp_project_id}" - MONGO_RUBY_DRIVER_GCP_LOCATION="${fle_gcp_location}" - MONGO_RUBY_DRIVER_GCP_KEY_RING="${fle_gcp_key_ring}" - MONGO_RUBY_DRIVER_GCP_KEY_NAME="${fle_gcp_key_name}" - MONGO_RUBY_DRIVER_MONGOCRYPTD_PORT="${fle_mongocryptd_port}" - EOT - - "export Kerberos credentials": - - command: shell.exec - type: test - params: - silent: true - working_dir: "src" - script: | - cat < .env.private - SASL_HOST=${sasl_host} - SASL_PORT=${sasl_port} - SASL_USER=${sasl_user} - SASL_PASS=${sasl_pass} - SASL_DB=${sasl_db} - PRINCIPAL=${principal} - KERBEROS_DB=${kerberos_db} - KEYTAB_BASE64=${keytab_base64} - EOT - - "exec script" : - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - sh ${PROJECT_DIRECTORY}/${file} - - "upload mo artifacts": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - find $MONGO_ORCHESTRATION_HOME -name \*.log\* | xargs tar czf mongodb-logs.tar.gz - - command: s3.put - params: - aws_key: ${aws_key} - aws_secret: ${aws_secret} - local_file: mongodb-logs.tar.gz - remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz - bucket: mciuploads - permissions: public-read - content_type: ${content_type|application/x-gzip} - display_name: "mongodb-logs.tar.gz" - - "upload working dir": - - command: archive.targz_pack - params: - target: "working-dir.tar.gz" - source_dir: ${PROJECT_DIRECTORY}/ - include: - - "./**" - - command: s3.put - params: - aws_key: ${aws_key} - aws_secret: ${aws_secret} - local_file: working-dir.tar.gz - remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-working-dir.tar.gz - bucket: mciuploads - permissions: public-read - content_type: ${content_type|application/x-gzip} - display_name: "working-dir.tar.gz" - - command: archive.targz_pack - params: - target: "drivers-dir.tar.gz" - source_dir: ${DRIVERS_TOOLS} - include: - - "./**" - - command: s3.put - params: - aws_key: ${aws_key} - aws_secret: ${aws_secret} - local_file: drivers-dir.tar.gz - remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-drivers-dir.tar.gz - bucket: mciuploads - permissions: public-read - content_type: ${content_type|application/x-gzip} - display_name: "drivers-dir.tar.gz" - - "upload test results to s3": - - command: s3.put - params: - aws_key: ${aws_key} - aws_secret: ${aws_secret} - # src is the relative path to repo checkout, - # This is specified in this yaml file earlier. - local_file: ./src/tmp/rspec.json - display_name: rspec.json - remote_file: ${UPLOAD_BUCKET}/${version_id}/${build_id}/artifacts/${build_variant}/rspec.json - content_type: application/json - permissions: public-read - bucket: mciuploads - # AWS does not appear to support on-the-fly gzip encoding; compress - # the results manually and upload a compressed file. - # Typical size reduction: 50 MB -> 800 KB - - command: shell.exec - params: - script: | - gzip src/tmp/rspec.json.gz - - command: s3.put - params: - aws_key: ${aws_key} - aws_secret: ${aws_secret} - # src is the relative path to repo checkout, - # This is specified in this yaml file earlier. - local_file: ./src/tmp/rspec.json.gz - display_name: rspec.json.gz - remote_file: ${UPLOAD_BUCKET}/${version_id}/${build_id}/artifacts/${build_variant}/rspec.json.gz - content_type: application/gzip - permissions: public-read - bucket: mciuploads - - command: shell.exec - params: - script: | - xz -9 src/tmp/rspec.json.xz - - command: s3.put - params: - aws_key: ${aws_key} - aws_secret: ${aws_secret} - # src is the relative path to repo checkout, - # This is specified in this yaml file earlier. - local_file: ./src/tmp/rspec.json.xz - display_name: rspec.json.xz - remote_file: ${UPLOAD_BUCKET}/${version_id}/${build_id}/artifacts/${build_variant}/rspec.json.xz - content_type: application/x-xz - permissions: public-read - bucket: mciuploads - - "upload test results": - - command: attach.xunit_results - params: - file: ./src/tmp/rspec.xml - - "delete private environment": - - command: shell.exec - type: test - params: - silent: true - working_dir: "src" - script: | - rm -f .env.private - - "build and test docker image": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - set -x - ./egos stdbuf -i0 -o0 -e0 .evergreen/test-on-docker -d ${os} MONGODB_VERSION=${mongodb-version} TOPOLOGY=${topology} RVM_RUBY=${ruby} -s .evergreen/run-tests.sh TEST_CMD=true ${PRELOAD_ARG} - - "run tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests.sh - - "run tests via docker": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-docker.sh - - "run AWS auth tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-aws-auth.sh - - "run Kerberos unit tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-kerberos-unit.sh - - "run Kerberos integration tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-kerberos-integration.sh - - "run Atlas tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - AUTH=${AUTH} SSL=${SSL} TOPOLOGY=${TOPOLOGY} RVM_RUBY="${RVM_RUBY}" ATLAS_REPLICA_SET_URI=${atlas_replica_set_uri} ATLAS_SHARDED_URI=${atlas_sharded_uri} ATLAS_FREE_TIER_URI=${atlas_free_tier_uri} ATLAS_TLS11_URI=${atlas_tls11_uri} ATLAS_TLS12_URI=${atlas_tls12_uri} ATLAS_SERVERLESS_URI=${atlas_serverless_uri} ATLAS_SERVERLESS_LB_URI=${atlas_serverless_lb_uri} .evergreen/run-tests-atlas.sh - - "run serverless tests": - - command: shell.exec - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - # This hack with PROJECT env is necessary while serverless - # rejects creating instances with too long names. - PROJECT="mongo-ruby-driver" LOADBALANCED="ON" .evergreen/serverless/create-instance.sh - cat serverless-expansion.yml - - - command: expansions.update - params: - file: src/serverless-expansion.yml - - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh - -pre: - - func: "fetch source" - - func: "fetch egos" - - func: "create expansions" - -post: - - func: "delete private environment" - # Removed, causing timeouts - # - func: "upload working dir" - - func: "upload mo artifacts" - #- func: "upload test results" - - func: "upload test results to s3" - - func: "delete serverless instance" - - -tasks: - - - name: "test-atlas" - commands: - - func: "run Atlas tests" - - name: "test-serverless" - commands: - - func: "export FLE credentials" - - func: "run serverless tests" -axes: - - - id: preload - display_name: Preload server - values: - - id: nopreload - display_name: Do not preload - - id: preload - display_name: Preload - variables: - PRELOAD_ARG: -p - - - id: "mongodb-version" - display_name: MongoDB Version - values: - - id: "latest" - display_name: "latest" - variables: - MONGODB_VERSION: "latest" - - id: "6.0" - display_name: "6.0" - variables: - MONGODB_VERSION: "6.0" - - id: "5.3" - display_name: "5.3" - variables: - MONGODB_VERSION: "5.3" - - id: "5.0" - display_name: "5.0" - variables: - MONGODB_VERSION: "5.0" - - id: "4.4" - display_name: "4.4" - variables: - MONGODB_VERSION: "4.4" - - id: "4.2" - display_name: "4.2" - variables: - MONGODB_VERSION: "4.2" - - id: "4.0" - display_name: "4.0" - variables: - MONGODB_VERSION: "4.0" - - id: "3.6" - display_name: "3.6" - variables: - MONGODB_VERSION: "3.6" - - - id: fcv - display_name: FCV - values: - - id: '3.4' - display_name: '3.4' - variables: - FCV: '3.4' - - - id: "topology" - display_name: Topology - values: - - id: "standalone" - display_name: Standalone - variables: - TOPOLOGY: standalone - - id: "replica-set" - display_name: Replica Set - variables: - TOPOLOGY: replica-set - - id: "sharded-cluster" - display_name: Sharded - variables: - TOPOLOGY: sharded-cluster - - id: "load-balanced" - display_name: Load Balanced - variables: - TOPOLOGY: load-balanced - - - id: "single-mongos" - display_name: Single Mongos - values: - - id: "single-mongos" - display_name: Single Mongos - variables: - SINGLE_MONGOS: 'true' - - - id: "auth-and-ssl" - display_name: Authentication and SSL - values: - - id: "auth-and-ssl" - display_name: Auth SSL - variables: - AUTH: "auth" - SSL: "ssl" - - id: "auth-and-nossl" - display_name: Auth NoSSL - variables: - AUTH: "auth" - - id: "noauth-and-ssl" - display_name: NoAuth SSL - variables: - SSL: "ssl" - - id: "noauth-and-nossl" - display_name: NoAuth NoSSL - - id: "x509" - display_name: X.509 - variables: - AUTH: "x509" - SSL: "ssl" - - id: kerberos - display_name: Kerberos - variables: - AUTH: kerberos - - id: aws-regular - display_name: AWS Auth Regular Credentials - variables: - AUTH: aws-regular - - id: aws-assume-role - display_name: AWS Auth Assume Role - variables: - AUTH: aws-assume-role - - id: aws-ec2 - display_name: AWS Auth EC2 Role - variables: - AUTH: aws-ec2 - - id: aws-ecs - display_name: AWS Auth ECS Task - variables: - AUTH: aws-ecs - - - id: "ruby" - display_name: Ruby Version - values: - - id: "ruby-3.1" - display_name: ruby-3.1 - variables: - RVM_RUBY: "ruby-3.1" - - id: "ruby-3.0" - display_name: ruby-3.0 - variables: - RVM_RUBY: "ruby-3.0" - - id: "ruby-2.7" - display_name: ruby-2.7 - variables: - RVM_RUBY: "ruby-2.7" - - id: "ruby-2.6" - display_name: ruby-2.6 - variables: - RVM_RUBY: "ruby-2.6" - - id: "ruby-2.5" - display_name: ruby-2.5 - variables: - RVM_RUBY: "ruby-2.5" - - id: "ruby-head" - display_name: ruby-head - variables: - RVM_RUBY: "ruby-head" - - id: "jruby-9.2" - display_name: jruby-9.2 - variables: - RVM_RUBY: "jruby-9.2" - - id: "jruby-9.3" - display_name: jruby-9.3 - variables: - RVM_RUBY: "jruby-9.3" - - - id: "os" - display_name: OS - values: - - id: debian11 - display_name: "Debian 11" - run_on: debian11-small - - id: ubuntu2004 - display_name: "Ubuntu 20.04" - run_on: ubuntu2004-small - - id: ubuntu1804 - display_name: "Ubuntu 18.04" - run_on: ubuntu1804-small - - id: ubuntu1604 - display_name: "Ubuntu 16.04" - run_on: ubuntu1604-small - - id: rhel80 - display_name: "RHEL 8" - run_on: rhel80-small - - id: rhel70 - display_name: "RHEL 7" - run_on: rhel70-small - - id: rhel62 - display_name: "RHEL 6" - run_on: rhel62-small - - - id: docker-distro - display_name: Docker Distro - values: - - id: debian92 - display_name: debian92 - variables: - DOCKER_DISTRO: debian92 - - id: debian10 - display_name: debian10 - variables: - DOCKER_DISTRO: debian10 - - id: ubuntu1804 - display_name: ubuntu1804 - variables: - DOCKER_DISTRO: ubuntu1804 - - - id: "compressor" - display_name: Compressor - values: - - id: "zlib" - display_name: Zlib - variables: - COMPRESSOR: "zlib" - - id: "snappy" - display_name: Snappy - variables: - COMPRESSOR: "snappy" - - id: "zstd" - display_name: Zstd - variables: - COMPRESSOR: "zstd" - - - id: retry-reads - display_name: Retry Reads - values: - - id: no-retry-reads - display_name: No Retry Reads - variables: - RETRY_READS: 'false' - - - id: retry-writes - display_name: Retry Writes - values: - - id: no-retry-writes - display_name: No Retry Writes - variables: - RETRY_WRITES: 'false' - - - id: lint - display_name: Lint - values: - - id: on - display_name: On - variables: - LINT: '1' - - - id: stress - display_name: Stress - values: - - id: on - display_name: On - variables: - STRESS: '1' - - - id: fork - display_name: Fork - values: - - id: on - display_name: On - variables: - FORK: '1' - - - id: solo - display_name: Solo - values: - - id: on - display_name: On - variables: - SOLO: '1' - - - id: "as" - display_name: ActiveSupport - values: - - id: "as" - display_name: AS - variables: - WITH_ACTIVE_SUPPORT: true - - - id: bson - display_name: BSON - values: - - id: master - display_name: master - variables: - BSON: master - - id: 4-stable - display_name: 4-stable - variables: - BSON: 4-stable - - id: min - display_name: min - variables: - BSON: min - - - id: storage-engine - display_name: Storage Engine - values: - - id: mmapv1 - display_name: MMAPv1 - run_on: ubuntu1604-small - variables: - MMAPV1: 'true' - - - id: "fle" - display_name: FLE - values: - - id: "helper" - display_name: via LMC helper - variables: - FLE: helper - - id: "path" - display_name: via LMC path - variables: - FLE: path - - - id: ocsp-algorithm - display_name: OCSP Algorithm - values: - - id: rsa - display_name: RSA - variables: - OCSP_ALGORITHM: rsa - - id: ecdsa - display_name: ECDSA - variables: - OCSP_ALGORITHM: ecdsa - - - id: ocsp-status - display_name: OCSP Status - values: - - id: valid - display_name: Valid - - id: revoked - display_name: Revoked - variables: - OCSP_STATUS: revoked - - id: unknown - display_name: Unknown - variables: - OCSP_STATUS: unknown - - - id: ocsp-delegate - display_name: OCSP Delegate - values: - - id: on - display_name: on - variables: - OCSP_DELEGATE: 1 - - - id: ocsp-must-staple - display_name: OCSP Must Staple - values: - - id: on - display_name: on - variables: - OCSP_MUST_STAPLE: 1 - - - id: ocsp-verifier - display_name: OCSP Verifier - values: - - id: true - display_name: true - variables: - OCSP_VERIFIER: 1 - - - id: ocsp-connectivity - display_name: OCSP Connectivity - values: - - id: pass - display_name: pass - variables: - OCSP_CONNECTIVITY: pass - - id: fail - display_name: fail - variables: - OCSP_CONNECTIVITY: fail - - - id: extra-uri-options - display_name: extra URI options - values: - - id: none - display_name: None - - id: "tlsInsecure=true" - variables: - EXTRA_URI_OPTIONS: "tlsInsecure=true" - - id: "tlsAllowInvalidCertificates=true" - variables: - EXTRA_URI_OPTIONS: "tlsAllowInvalidCertificates=true" - - - id: api-version-required - display_name: API version required - values: - - id: yes - display_name: Yes - variables: - API_VERSION_REQUIRED: 1 - - id: no - display_name: No -buildvariants: - - matrix_name: "all" - matrix_spec: - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] - display_name: "Atlas tests ${ruby}" - run_on: - - ubuntu1804-small - tasks: - - name: test-atlas - - - matrix_name: "serverless" - matrix_spec: - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] - fle: helper - display_name: "Atlas serverless ${ruby} single mongos" - run_on: - - ubuntu1804-small - tasks: - - name: test-serverless diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 576c89153e..c889dd7300 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -129,6 +129,23 @@ functions: export OCSP_CONNECTIVITY="${OCSP_CONNECTIVITY}" export OCSP_VERIFIER="${OCSP_VERIFIER}" + export ATLAS_REPLICA_SET_URI="${atlas_replica_set_uri}" + export ATLAS_SHARDED_URI="${atlas_sharded_uri}" + export ATLAS_FREE_TIER_URI="${atlas_free_tier_uri}" + export ATLAS_TLS11_URI="${atlas_tls11_uri}" + export ATLAS_TLS12_URI="${atlas_tls12_uri}" + export ATLAS_SERVERLESS_URI="${atlas_serverless_uri}" + export ATLAS_SERVERLESS_LB_URI="${atlas_serverless_lb_uri}" + export RVM_RUBY="${RVM_RUBY}" + export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" + export SERVERLESS_API_PUBLIC_KEY="${SERVERLESS_API_PUBLIC_KEY}" + export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" + export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" + export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + # Needed for generating temporary aws credentials. if [ -n "${FLE}" ]; then @@ -460,6 +477,7 @@ post: - func: "upload mo artifacts" #- func: "upload test results" - func: "upload test results to s3" + - func: "delete serverless instance" task_groups: - name: testgcpkms_task_group @@ -500,7 +518,13 @@ task_groups: - testgcpkms-task tasks: - + - name: "test-atlas" + commands: + - func: "run Atlas tests" + - name: "test-serverless" + commands: + - func: "export FLE credentials" + - func: "run serverless tests" - name: "test-docker" commands: - func: "build and test docker image" @@ -1731,3 +1755,21 @@ buildvariants: - name: testgcpkms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - matrix_name: "atlas" + matrix_spec: + ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + display_name: "Atlas tests ${ruby}" + run_on: + - ubuntu1804-small + tasks: + - name: test-atlas + + - matrix_name: "serverless" + matrix_spec: + ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + fle: helper + display_name: "Atlas serverless ${ruby} single mongos" + run_on: + - ubuntu1804-small + tasks: + - name: test-serverless diff --git a/.evergreen/config/atlas.yml.erb b/.evergreen/config/atlas.yml.erb deleted file mode 100644 index 1f602b945c..0000000000 --- a/.evergreen/config/atlas.yml.erb +++ /dev/null @@ -1,19 +0,0 @@ -buildvariants: - - matrix_name: "all" - matrix_spec: - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] - display_name: "Atlas tests ${ruby}" - run_on: - - ubuntu1804-small - tasks: - - name: test-atlas - - - matrix_name: "serverless" - matrix_spec: - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] - fle: helper - display_name: "Atlas serverless ${ruby} single mongos" - run_on: - - ubuntu1804-small - tasks: - - name: test-serverless diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 8e81b5eaad..d377073b60 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -50,74 +50,6 @@ functions: curl --retry 3 -sfLo egos.c https://raw.githubusercontent.com/p-mongo/egos/master/egos.c gcc -O2 -g -oegos -lrt egos.c - <% if @atlas %> - "create expansions": - # Make an evergreen expansion file with dynamic values - - command: shell.exec - params: - working_dir: "src" - script: | - # Get the current unique version of this checkout - if [ "${is_patch}" = "true" ]; then - CURRENT_VERSION=$(git describe)-patch-${version_id} - else - CURRENT_VERSION=latest - fi - - export DRIVERS_TOOLS="$(pwd)/.mod/drivers-evergreen-tools" - - # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory - if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin - export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS) - fi - - export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration" - export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" - export UPLOAD_BUCKET="${project}" - export PROJECT_DIRECTORY="$(pwd)" - - cat < expansion.yml - CURRENT_VERSION: "$CURRENT_VERSION" - DRIVERS_TOOLS: "$DRIVERS_TOOLS" - MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME" - MONGODB_BINARIES: "$MONGODB_BINARIES" - UPLOAD_BUCKET: "$UPLOAD_BUCKET" - PROJECT_DIRECTORY: "$PROJECT_DIRECTORY" - PREPARE_SHELL: | - set -o errexit - set -o xtrace - export DRIVERS_TOOLS="$DRIVERS_TOOLS" - export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME" - export MONGODB_BINARIES="$MONGODB_BINARIES" - export UPLOAD_BUCKET="$UPLOAD_BUCKET" - export PROJECT_DIRECTORY="$PROJECT_DIRECTORY" - - # TMPDIR cannot be too long, see - # https://github.com/broadinstitute/cromwell/issues/3647. - # Why is it even set at all? - #export TMPDIR="$MONGO_ORCHESTRATION_HOME/db" - export PATH="$MONGODB_BINARIES:$PATH" - export PROJECT="${project}" - export ATLAS_REPLICA_SET_URI="${atlas_replica_set_uri}" - export ATLAS_SHARDED_URI="${atlas_sharded_uri}" - export ATLAS_FREE_TIER_URI="${atlas_free_tier_uri}" - export ATLAS_TLS11_URI="${atlas_tls11_uri}" - export ATLAS_TLS12_URI="${atlas_tls12_uri}" - export ATLAS_SERVERLESS_URI="${atlas_serverless_uri}" - export ATLAS_SERVERLESS_LB_URI="${atlas_serverless_lb_uri}" - export RVM_RUBY="${RVM_RUBY}" - export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" - export SERVERLESS_API_PUBLIC_KEY="${SERVERLESS_API_PUBLIC_KEY}" - export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" - export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" - export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" - export AWS_ACCESS_KEY_ID="${fle_aws_key}" - export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" - export AWS_DEFAULT_REGION="${fle_aws_region}" - EOT - # See what we've done - cat expansion.yml - <% else %> "create expansions": # Make an evergreen expansion file with dynamic values - command: shell.exec @@ -195,6 +127,23 @@ functions: export OCSP_CONNECTIVITY="${OCSP_CONNECTIVITY}" export OCSP_VERIFIER="${OCSP_VERIFIER}" + export ATLAS_REPLICA_SET_URI="${atlas_replica_set_uri}" + export ATLAS_SHARDED_URI="${atlas_sharded_uri}" + export ATLAS_FREE_TIER_URI="${atlas_free_tier_uri}" + export ATLAS_TLS11_URI="${atlas_tls11_uri}" + export ATLAS_TLS12_URI="${atlas_tls12_uri}" + export ATLAS_SERVERLESS_URI="${atlas_serverless_uri}" + export ATLAS_SERVERLESS_LB_URI="${atlas_serverless_lb_uri}" + export RVM_RUBY="${RVM_RUBY}" + export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" + export SERVERLESS_API_PUBLIC_KEY="${SERVERLESS_API_PUBLIC_KEY}" + export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" + export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" + export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + # Needed for generating temporary aws credentials. if [ -n "${FLE}" ]; then @@ -206,7 +155,6 @@ functions: # See what we've done cat expansion.yml - <% end %> # Load the expansion file to make an evergreen variable with the current # unique version @@ -527,11 +475,8 @@ post: - func: "upload mo artifacts" #- func: "upload test results" - func: "upload test results to s3" - <% if @atlas %> - func: "delete serverless instance" - <% end %> -<% unless @atlas %> task_groups: - name: testgcpkms_task_group setup_group_can_fail_task: true @@ -569,11 +514,8 @@ task_groups: .evergreen/csfle/gcpkms/delete-instance.sh tasks: - testgcpkms-task -<% end %> tasks: - - <% if @atlas %> - name: "test-atlas" commands: - func: "run Atlas tests" @@ -581,7 +523,6 @@ tasks: commands: - func: "export FLE credentials" - func: "run serverless tests" - <% else %> - name: "test-docker" commands: - func: "build and test docker image" @@ -642,4 +583,3 @@ tasks: export GCPKMS_ZONE=${GCPKMS_ZONE} export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} GCPKMS_CMD="TEST_FLE_GCP_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}" MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY='${fle_gcp_private_key}' MONGO_RUBY_DRIVER_GCP_PROJECT_ID='${fle_gcp_project_id}' MONGO_RUBY_DRIVER_GCP_LOCATION='${fle_gcp_location}' MONGO_RUBY_DRIVER_GCP_KEY_RING='${fle_gcp_key_ring}' MONGO_RUBY_DRIVER_GCP_KEY_NAME='${fle_gcp_key_name}' ./.evergreen/run-tests-gcp.sh" .evergreen/csfle/gcpkms/run-command.sh - <% end %> diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index a7b8f662e2..c5dbcd71bb 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -655,3 +655,21 @@ buildvariants: - name: testgcpkms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - matrix_name: "atlas" + matrix_spec: + ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + display_name: "Atlas tests ${ruby}" + run_on: + - ubuntu1804-small + tasks: + - name: test-atlas + + - matrix_name: "serverless" + matrix_spec: + ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + fle: helper + display_name: "Atlas serverless ${ruby} single mongos" + run_on: + - ubuntu1804-small + tasks: + - name: test-serverless diff --git a/.evergreen/run-tests-atlas.sh b/.evergreen/run-tests-atlas.sh index fd49553c43..bd1732ce27 100755 --- a/.evergreen/run-tests-atlas.sh +++ b/.evergreen/run-tests-atlas.sh @@ -21,7 +21,7 @@ do # ${!foo} syntax is bash specific: # https://stackoverflow.com/questions/14049057/bash-expand-variable-in-a-variable export ATLAS_URI="${!uri}" - + if test -z "$ATLAS_URI"; then echo "The \$$uri environment variable was not set" 1>&2 test_status=1 diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index 14a30f2d1b..df13341466 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -81,6 +81,6 @@ bundle exec rspec \ kill_jruby # Terminate all kmip servers... and whatever else happens to be running # that is a python script. -pkill python3 +pkill python exit ${test_status} diff --git a/.evergreen/update-evergreen-configs b/.evergreen/update-evergreen-configs index 8a070e8e5f..a57b4e5d16 100755 --- a/.evergreen/update-evergreen-configs +++ b/.evergreen/update-evergreen-configs @@ -8,9 +8,6 @@ autoload :YAML, 'yaml' class Runner def run transform('config.yml') - - @atlas = true - transform('config-atlas.yml') end def transform(output_file_name) @@ -27,13 +24,8 @@ EOT template_path = File.join(File.dirname(__FILE__), 'config/axes.yml.erb') contents << Tilt.new(template_path, engine_class: Erubi::CaptureEndEngine).render(self) - if @atlas - template_path = File.join(File.dirname(__FILE__), 'config/atlas.yml.erb') - contents << Tilt.new(template_path, engine_class: Erubi::CaptureEndEngine).render(self) - else - template_path = File.join(File.dirname(__FILE__), 'config/standard.yml.erb') - contents << Tilt.new(template_path, engine_class: Erubi::CaptureEndEngine).render(self) - end + template_path = File.join(File.dirname(__FILE__), 'config/standard.yml.erb') + contents << Tilt.new(template_path, engine_class: Erubi::CaptureEndEngine).render(self) output_path = File.join(File.dirname(__FILE__), output_file_name) File.open(output_path, 'w') do |f| diff --git a/spec/atlas/operations_spec.rb b/spec/atlas/operations_spec.rb index 7a00d1ecd1..080d75dfd7 100644 --- a/spec/atlas/operations_spec.rb +++ b/spec/atlas/operations_spec.rb @@ -13,15 +13,10 @@ end end - describe 'list_collections' do - # Atlas free tier proxy enforces restrictions on list_collections - # arguments. This tests verifies that list_collections works on Atlas - + describe 'ping' do it 'works' do - # We are not allowed to mutate the database, therefore the list of - # collections would generally be empty. expect do - client.database.list_collections + client.database.command(ping: 1) end.not_to raise_error end end From 932b06b7564a5e5ae8d4ad08fe8d6ceee629e4eb Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 23 Nov 2022 09:43:29 +0100 Subject: [PATCH 085/198] RUBY-3184 Rename crypt_shared_lib_required (#2670) --- docs/reference/client-side-encryption.txt | 2 +- lib/mongo/client.rb | 2 +- lib/mongo/crypt/auto_encrypter.rb | 8 ++++---- lib/mongo/crypt/handle.rb | 6 +++--- .../client_side_encryption/mongocryptd_prose_spec.rb | 2 +- spec/mongo/crypt/handle_spec.rb | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/reference/client-side-encryption.txt b/docs/reference/client-side-encryption.txt index 1c02a956e6..bacce213af 100644 --- a/docs/reference/client-side-encryption.txt +++ b/docs/reference/client-side-encryption.txt @@ -111,7 +111,7 @@ If the library can be loaded, then the driver will not try to spawn mongocryptd The daemon will be still spawned if the shared library cannot be found. It is also possible to require using the shared library by passing -``crypt_shared_lib_required: true`` option when creating a client. In this case, +``crypt_shared_required: true`` option when creating a client. In this case, an error will be raised if the shared library cannot be loaded. .. note:: diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index 92bde662e6..b0654e83a1 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -466,7 +466,7 @@ def hash # - :crypt_shared_lib_path => [ String | nil ] Path that should # be the used to load the crypt shared library. Providing this option # overrides default crypt shared library load paths for libmongocrypt. - # - :crypt_shared_lib_required => [ Boolean | nil ] Whether + # - :crypt_shared_required => [ Boolean | nil ] Whether # crypt shared library is required. If 'true', an error will be raised # if a crypt_shared library cannot be loaded by libmongocrypt. # diff --git a/lib/mongo/crypt/auto_encrypter.rb b/lib/mongo/crypt/auto_encrypter.rb index 1ac24430fd..ad34486a77 100644 --- a/lib/mongo/crypt/auto_encrypter.rb +++ b/lib/mongo/crypt/auto_encrypter.rb @@ -84,7 +84,7 @@ class AutoEncrypter # @option options [ String | nil ] :crypt_shared_lib_path Path that should # be the used to load the crypt shared library. Providing this option # overrides default crypt shared library load paths for libmongocrypt. - # @option options [ Boolean | nil ] :crypt_shared_lib_required Whether + # @option options [ Boolean | nil ] :crypt_shared_required Whether # crypt shared library is required. If 'true', an error will be raised # if a crypt_shared library cannot be loaded by libmongocrypt. # @@ -103,7 +103,7 @@ def initialize(options) encrypted_fields_map: @options[:encrypted_fields_map], bypass_query_analysis: @options[:bypass_query_analysis], crypt_shared_lib_path: @options[:extra_options][:crypt_shared_lib_path], - crypt_shared_lib_required: @options[:extra_options][:crypt_shared_lib_required], + crypt_shared_required: @options[:extra_options][:crypt_shared_required], ) @mongocryptd_options = @options[:extra_options].slice( @@ -115,9 +115,9 @@ def initialize(options) @mongocryptd_options[:mongocryptd_bypass_spawn] = @options[:bypass_auto_encryption] || @options[:extra_options][:mongocryptd_bypass_spawn] || @crypt_handle.crypt_shared_lib_available? || - @options[:extra_options][:crypt_shared_lib_required] + @options[:extra_options][:crypt_shared_required] - unless @options[:extra_options][:crypt_shared_lib_required] || @crypt_handle.crypt_shared_lib_available? + unless @options[:extra_options][:crypt_shared_required] || @crypt_handle.crypt_shared_lib_available? # Set server selection timeout to 1 to prevent the client waiting for a # long timeout before spawning mongocryptd @mongocryptd_client = Client.new( diff --git a/lib/mongo/crypt/handle.rb b/lib/mongo/crypt/handle.rb index 512cf10a6e..eb469fedbb 100644 --- a/lib/mongo/crypt/handle.rb +++ b/lib/mongo/crypt/handle.rb @@ -56,7 +56,7 @@ class Handle # @option options [ String | nil ] :crypt_shared_lib_path Path that should # be the used to load the crypt shared library. Providing this option # overrides default crypt shared library load paths for libmongocrypt. - # @option options [ Boolean | nil ] :crypt_shared_lib_required Whether + # @option options [ Boolean | nil ] :crypt_shared_required Whether # crypt_shared library is required. If 'true', an error will be raised # if a crypt_shared library cannot be loaded by libmongocrypt. # @option options [ Boolean | nil ] :explicit_encryption_only Whether this @@ -104,8 +104,8 @@ def initialize(kms_providers, kms_tls_options, options={}) initialize_mongocrypt - @crypt_shared_lib_required = !!options[:crypt_shared_lib_required] - if @crypt_shared_lib_required && crypt_shared_lib_version == 0 + @crypt_shared_required = !!options[:crypt_shared_required] + if @crypt_shared_required && crypt_shared_lib_version == 0 raise Mongo::Error::CryptError.new( "Crypt shared library is required, but cannot be loaded according to libmongocrypt" ) diff --git a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb index 46cfbc21f8..9976896c24 100644 --- a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb +++ b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb @@ -81,7 +81,7 @@ def done! let(:extra_options) do { crypt_shared_lib_path: SpecConfig.instance.crypt_shared_lib_path, - crypt_shared_lib_required: true, + crypt_shared_required: true, mongocryptd_uri: mongocryptd_uri, mongocryptd_spawn_args: [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27777"] } diff --git a/spec/mongo/crypt/handle_spec.rb b/spec/mongo/crypt/handle_spec.rb index be5331e858..7cfba8ccbe 100644 --- a/spec/mongo/crypt/handle_spec.rb +++ b/spec/mongo/crypt/handle_spec.rb @@ -20,7 +20,7 @@ schema_map_path: schema_map_path, bypass_query_analysis: bypass_query_analysis, crypt_shared_lib_path: crypt_shared_lib_path, - crypt_shared_lib_required: crypt_shared_lib_required, + crypt_shared_required: crypt_shared_required, explicit_encryption_only: explicit_encryption_only, ) end @@ -41,7 +41,7 @@ nil end - let(:crypt_shared_lib_required) do + let(:crypt_shared_required) do nil end @@ -118,11 +118,11 @@ end end - context 'with crypt_shared_lib_required' do + context 'with crypt_shared_required' do min_server_version '6.0.0' context 'set to true' do - let(:crypt_shared_lib_required) do + let(:crypt_shared_required) do true end From c369355ee546320b581a37c83376b613aac7f8d0 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Mon, 28 Nov 2022 11:46:33 -0500 Subject: [PATCH 086/198] RUBY-2495 rework the check_out method (#2671) * modularize connection pool * further modularize the pool * create condition variable * RUBY-2495 add size condition variable * fix test and add comment * RUBY-2495 fix tests and add test * fix condition variable * attempts to fix signaling * fix neg timeout * fix condition variable, add tests, modify waiting * update driver-evergreen-tools * use ubuntu-20.04 for gh actions * update download-mongodb.sh and put back ubuntu-latest * put back ubuntu-20.04 * put back det * fix condition variable tests * remove failing tests * Update lib/mongo/server/connection_pool.rb * address comments * verify lock is acquired --- .github/workflows/test.yml | 4 +- .mod/drivers-evergreen-tools | 2 +- lib/mongo.rb | 1 + lib/mongo/condition_variable.rb | 58 +++ lib/mongo/distinguishing_semaphore.rb | 2 +- lib/mongo/semaphore.rb | 2 +- lib/mongo/server/connection_pool.rb | 466 ++++++++++++++-------- spec/mongo/condition_variable_spec.rb | 104 +++++ spec/mongo/server/connection_pool_spec.rb | 21 + 9 files changed, 483 insertions(+), 177 deletions(-) create mode 100644 lib/mongo/condition_variable.rb create mode 100644 spec/mongo/condition_variable_spec.rb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28c7dd342f..d4ee5e4168 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,12 +10,12 @@ jobs: env: CI: true TESTOPTS: "-v" - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 continue-on-error: true strategy: fail-fast: false matrix: - os: [ ubuntu-latest ] + os: [ ubuntu-20.04 ] ruby: ["2.5", "2.6", "2.7", "3.0", "3.1"] mongodb: ["3.6", "4.4", "5.0"] topology: [replica_set, sharded_cluster] diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index f5d1c04994..a3a17f1185 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit f5d1c049948718218b006c0732990e1c642cbb13 +Subproject commit a3a17f1185643135a6b24f318bf0ce1e58fdea94 diff --git a/lib/mongo.rb b/lib/mongo.rb index b32f4553d2..b9121a7230 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -38,6 +38,7 @@ require 'mongo/bson' require 'mongo/semaphore' require 'mongo/distinguishing_semaphore' +require 'mongo/condition_variable' require 'mongo/options' require 'mongo/loggable' require 'mongo/cluster_time' diff --git a/lib/mongo/condition_variable.rb b/lib/mongo/condition_variable.rb new file mode 100644 index 0000000000..cbf77ccc9b --- /dev/null +++ b/lib/mongo/condition_variable.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2020 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + # This is an implementation of a condition variable. + # + # @api private + class ConditionVariable + extend Forwardable + + def initialize(lock = Mutex.new) + @lock = lock + @cv = ::ConditionVariable.new + end + + # Waits for the condition variable to be signaled up to timeout seconds. + # If condition variable is not signaled, returns after timeout seconds. + def wait(timeout = nil) + raise_unless_locked! + return false if timeout && timeout < 0 + @cv.wait(@lock, timeout) + end + + def broadcast + raise_unless_locked! + @cv.broadcast + end + + def signal + raise_unless_locked! + @cv.signal + end + + def_delegators :@lock, :synchronize + + private + + def raise_unless_locked! + unless @lock.owned? + raise ArgumentError, "the lock must be owned when calling this method" + end + end + end +end diff --git a/lib/mongo/distinguishing_semaphore.rb b/lib/mongo/distinguishing_semaphore.rb index 23e9cc0787..71a436b528 100644 --- a/lib/mongo/distinguishing_semaphore.rb +++ b/lib/mongo/distinguishing_semaphore.rb @@ -23,7 +23,7 @@ module Mongo class DistinguishingSemaphore def initialize @lock = Mutex.new - @cv = ConditionVariable.new + @cv = ::ConditionVariable.new @queue = [] end diff --git a/lib/mongo/semaphore.rb b/lib/mongo/semaphore.rb index 94372e3d52..e3e99f0f75 100644 --- a/lib/mongo/semaphore.rb +++ b/lib/mongo/semaphore.rb @@ -23,7 +23,7 @@ module Mongo class Semaphore def initialize @lock = Mutex.new - @cv = ConditionVariable.new + @cv = ::ConditionVariable.new end # Waits for the semaphore to be signaled up to timeout seconds. diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index b62df7b953..2e27f52915 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -148,6 +148,16 @@ def initialize(server, options = {}) @populator = Populator.new(self, options) @populate_semaphore = Semaphore.new + # Condition variable to enforce the first check in check_out: max_pool_size. + # This condition variable should be signaled when the number of + # unavailable connections decreases (pending + pending_connections + + # available_connections). + @size_cv = Mongo::ConditionVariable.new(@lock) + # This represents the number of threads that have made it past the size_cv + # gate but have not acquired a connection to add to the pending_connections + # set. + @pending = 0 + ObjectSpace.define_finalizer(self, self.class.finalize(@available_connections, @pending_connections, @populator)) publish_cmap_event( @@ -247,6 +257,14 @@ def unsynchronized_size end private :unsynchronized_size + # @return [ Integer ] The number of unavailable connections in the pool. + # Used to calculate whether we have hit max_pool_size. + # + # @api private + def unavailable_connections + @checked_out_connections.length + @pending_connections.length + @pending + end + # Number of available connections in the pool. # # @return [ Integer ] Number of available connections. @@ -327,172 +345,10 @@ def check_out(connection_global_id: nil) Monitoring::Event::Cmap::ConnectionCheckOutStarted.new(@server.address) ) - if closed? - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( - @server.address, - Monitoring::Event::Cmap::ConnectionCheckOutFailed::POOL_CLOSED - ), - ) - raise Error::PoolClosedError.new(@server.address, self) - end - - if paused? - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( - @server.address, - # CMAP spec decided to conflate pool paused with all the other - # possible non-timeout errors. - Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR, - ), - ) - raise Error::PoolPausedError.new(@server.address, self) - end - - deadline = Utils.monotonic_time + wait_timeout - pid = Process.pid - connection = nil - # It seems that synchronize sets up its own loop, thus a simple break - # is insufficient to break the outer loop - catch(:done) do - loop do - # Lock must be taken on each iteration, rather for the method - # overall, otherwise other threads will not be able to check in - # a connection while this thread is waiting for one. - @lock.synchronize do - until @available_connections.empty? - connection = next_available_connection( - connection_global_id: connection_global_id - ) - - if connection.nil? - if connection_global_id - # A particular connection is requested, but it is not available. - # If it is nether available not checked out, we should stop here. - @checked_out_connections.detect do |conn| - conn.connection_global_id == connection_global_id - end.tap do |conn| - if conn.nil? - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( - @server.address, - Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR - ), - ) - raise Error::MissingConnection.new - end - end - else - break - end - end - - if connection.pid != pid - log_warn("Detected PID change - Mongo client should have been reconnected (old pid #{connection.pid}, new pid #{pid}") - connection.disconnect!(reason: :stale) - @populate_semaphore.signal - next - end - - if !connection.pinned? - # If connection is marked as pinned, it is used by a transaction - # or a series of cursor operations in a load balanced setup. - # In this case connection should not be disconnected until - # unpinned. - if connection.generation != generation( - service_id: connection.service_id - ) - # Stale connections should be disconnected in the clear - # method, but if any don't, check again here - connection.disconnect!(reason: :stale) - @populate_semaphore.signal - next - end - - if max_idle_time && connection.last_checkin && - Time.now - connection.last_checkin > max_idle_time - then - connection.disconnect!(reason: :idle) - @populate_semaphore.signal - next - end - end - - @pending_connections << connection - throw(:done) - end - - if @server.load_balancer? && connection_global_id - unless @checked_out_connections.detect { |c| c.global_id == connection_global_id } - raise Error::ConnectionUnavailable.new - end - # We need a particular connection, and if it is not available - # we can wait for an in-progress operation to return - # such a connection to the pool. - else - # If we are below pool capacity, create a new connection. - # - # Ruby does not allow a thread to lock a mutex which it already - # holds. - if max_size == 0 || unsynchronized_size < max_size - connection = create_connection - @pending_connections << connection - throw(:done) - end - end - end - - wait = deadline - Utils.monotonic_time - if wait <= 0 - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( - @server.address, - Monitoring::Event::Cmap::ConnectionCheckOutFailed::TIMEOUT, - ), - ) - - msg = @lock.synchronize do - connection_global_id_msg = if connection_global_id - " for connection #{connection_global_id}" - else - '' - end + raise_if_pool_closed! + raise_if_pool_paused_locked! - "Timed out attempting to check out a connection " + - "from pool for #{@server.address}#{connection_global_id_msg} after #{wait_timeout} sec. " + - "Connections in pool: #{@available_connections.length} available, " + - "#{@checked_out_connections.length} checked out, " + - "#{@pending_connections.length} pending " + - "(max size: #{max_size})" - end - raise Error::ConnectionCheckOutTimeout.new(msg, address: @server.address) - end - @available_semaphore.wait(wait) - end - end - - begin - connect_connection(connection) - rescue Exception - # Handshake or authentication failed - @lock.synchronize do - @pending_connections.delete(connection) - end - @populate_semaphore.signal - - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( - @server.address, - Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR - ), - ) - raise - end - - @lock.synchronize do - @checked_out_connections << connection - @pending_connections.delete(connection) - end + connection = retrieve_and_connect_connection(connection_global_id) publish_cmap_event( Monitoring::Event::Cmap::ConnectionCheckedOut.new(@server.address, connection.id, self), @@ -551,6 +407,8 @@ def do_check_in(connection) # the exception) should be fine. @checked_out_connections.delete(connection) + @size_cv.signal + publish_cmap_event( Monitoring::Event::Cmap::ConnectionCheckedIn.new(@server.address, connection.id, self) ) @@ -669,7 +527,7 @@ def do_clear(options = nil) @generation_manager.bump(service_id: service_id) unless options && options[:lazy] - close_connections(@available_connections, service_id) + close_available_connections(service_id) end if options && options[:interrupt_in_use_connections] @@ -689,6 +547,10 @@ def do_clear(options = nil) # otherwise, allow the retry to be attempted with a ready pool. do_pause if !@server.load_balancer? && @server.unknown? end + + # Broadcast here to cause all of the threads waiting on the pool size + # to decrease to break out of the wait loop and error. + @size_cv.broadcast end # "Schedule the background thread" after clearing. This is responsible @@ -778,6 +640,8 @@ def close(options = nil) @closed = true @ready = false end + + @size_cv.broadcast end publish_cmap_event( @@ -943,6 +807,8 @@ def self.finalize(available_connections, pending_connections, populator) # global id. If no suitable connections are available, # returns nil. def next_available_connection(connection_global_id: nil) + raise_unless_locked! + if @server.load_balancer? && connection_global_id conn = @available_connections.detect do |conn| conn.global_id == connection_global_id @@ -980,7 +846,7 @@ def create_and_add_connection connection = nil @lock.synchronize do - if !closed? && @ready && unsynchronized_size < min_size + if !closed? && @ready && (unsynchronized_size + @pending) < min_size connection = create_connection @pending_connections << connection else @@ -995,6 +861,7 @@ def create_and_add_connection rescue Exception @lock.synchronize do @pending_connections.delete(connection) + @size_cv.signal end raise end @@ -1005,6 +872,7 @@ def create_and_add_connection # wake up one thread waiting for connections, since one was created @available_semaphore.signal + @size_cv.signal end true @@ -1132,19 +1000,19 @@ def check_invariants end end - # Close the connections in the given list. + # Close the available connections. # # @param [ Array ] connections A list of connections. # @param [ Object ] service_id The service id. - def close_connections(connections, service_id) + def close_available_connections(service_id) if @server.load_balancer? && service_id loop do - conn = connections.detect do |conn| + conn = @available_connections.detect do |conn| conn.service_id == service_id && conn.generation < @generation_manager.generation(service_id: service_id) end if conn - connections.delete(conn) + @available_connections.delete(conn) conn.disconnect!(reason: :stale, interrupted: true) @populate_semaphore.signal else @@ -1152,7 +1020,7 @@ def close_connections(connections, service_id) end end else - connections.delete_if do |conn| + @available_connections.delete_if do |conn| if conn.generation < @generation_manager.generation(service_id: service_id) conn.disconnect!(reason: :stale, interrupted: true) @populate_semaphore.signal @@ -1181,6 +1049,260 @@ def clear_pending_connections @pending_connections.delete(connection) end end + + # The lock should be acquired when calling this method. + def raise_check_out_timeout!(connection_global_id) + raise_unless_locked! + + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( + @server.address, + Monitoring::Event::Cmap::ConnectionCheckOutFailed::TIMEOUT, + ), + ) + + connection_global_id_msg = if connection_global_id + " for connection #{connection_global_id}" + else + '' + end + + msg = "Timed out attempting to check out a connection " + + "from pool for #{@server.address}#{connection_global_id_msg} after #{wait_timeout} sec. " + + "Connections in pool: #{@available_connections.length} available, " + + "#{@checked_out_connections.length} checked out, " + + "#{@pending_connections.length + @pending} pending " + + "(max size: #{max_size})" + raise Error::ConnectionCheckOutTimeout.new(msg, address: @server.address) + end + + def raise_check_out_timeout_locked!(connection_global_id) + @lock.synchronize do + raise_check_out_timeout!(connection_global_id) + end + end + + def raise_if_pool_closed! + if closed? + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( + @server.address, + Monitoring::Event::Cmap::ConnectionCheckOutFailed::POOL_CLOSED + ), + ) + raise Error::PoolClosedError.new(@server.address, self) + end + end + + def raise_if_pool_paused! + raise_unless_locked! + + if !@ready + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( + @server.address, + # CMAP spec decided to conflate pool paused with all the other + # possible non-timeout errors. + Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR, + ), + ) + raise Error::PoolPausedError.new(@server.address, self) + end + end + + def raise_if_pool_paused_locked! + @lock.synchronize do + raise_if_pool_paused! + end + end + + # The lock should be acquired when calling this method. + def raise_if_not_ready! + raise_unless_locked! + raise_if_pool_closed! + raise_if_pool_paused! + end + + def raise_unless_locked! + unless @lock.owned? + raise ArgumentError, "the lock must be owned when calling this method" + end + end + + def valid_available_connection?(connection, pid, connection_global_id) + if connection.nil? + if connection_global_id + # A particular connection is requested, but it is not available. + # If it is nether available not checked out, we should stop here. + @checked_out_connections.detect do |conn| + conn.connection_global_id == connection_global_id + end.tap do |conn| + if conn.nil? + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( + @server.address, + Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR + ), + ) + raise Error::MissingConnection.new + end + end + else + return false + end + end + + if connection.pid != pid + log_warn("Detected PID change - Mongo client should have been reconnected (old pid #{connection.pid}, new pid #{pid}") + connection.disconnect!(reason: :stale) + @populate_semaphore.signal + return false + end + + if !connection.pinned? + # If connection is marked as pinned, it is used by a transaction + # or a series of cursor operations in a load balanced setup. + # In this case connection should not be disconnected until + # unpinned. + if connection.generation != generation( + service_id: connection.service_id + ) + # Stale connections should be disconnected in the clear + # method, but if any don't, check again here + connection.disconnect!(reason: :stale) + @populate_semaphore.signal + return false + end + + if max_idle_time && connection.last_checkin && + Time.now - connection.last_checkin > max_idle_time + then + connection.disconnect!(reason: :idle) + @populate_semaphore.signal + return false + end + end + true + end + + # Retrieves a connection if one is available, otherwise we create a new + # one. If no connection exists and the pool is at max size, wait until + # a connection is checked back into the pool. + # + # @param [ Float ] deadline The deadline to get the connection. + # @param [ Integer ] pid The current process id. + # @param [ Integer ] connection_global_id The global id for the + # connection to check out. + # + # @return [ Mongo::Server::Connection ] The checked out connection. + # + # @raise [ Error::PoolClosedError ] If the pool has been closed. + # @raise [ Timeout::Error ] If the connection pool is at maximum size + # and remains so for longer than the wait timeout. + def get_connection(deadline, pid, connection_global_id) + # The first gate to checking out a connection. Make sure the number of + # unavailable connections is less than the max pool size. + @size_cv.synchronize do + until max_size == 0 || unavailable_connections < max_size + wait = deadline - Utils.monotonic_time + raise_check_out_timeout!(connection_global_id) if wait <= 0 + @size_cv.wait(wait) + raise_if_not_ready! + end + @pending += 1 + end + + loop do + # Lock must be taken on each iteration, rather for the method + # overall, otherwise other threads will not be able to check in + # a connection while this thread is waiting for one. + + @lock.synchronize do + until @available_connections.empty? + connection = next_available_connection( + connection_global_id: connection_global_id + ) + + unless valid_available_connection?(connection, pid, connection_global_id) + # If there's no available connection and we're not looking + # for a specific connection, don't attempt to look at the + # available connections again. + if connection.nil? && !connection_global_id + break + else + next + end + end + + @pending -= 1 + @pending_connections << connection + return connection + end + + if @server.load_balancer? && connection_global_id + unless @checked_out_connections.detect { |c| c.global_id == connection_global_id } + raise Error::ConnectionUnavailable.new + end + # We need a particular connection, and if it is not available + # we can wait for an in-progress operation to return + # such a connection to the pool. + else + connection = create_connection + @pending -= 1 + @pending_connections << connection + return connection + end + end + + wait = deadline - Utils.monotonic_time + raise_check_out_timeout_locked!(connection_global_id) if wait <= 0 + @available_semaphore.wait(wait) + end + end + + # Retrieves a connection and connects it. + # + # @param [ Integer ] connection_global_id The global id for the + # connection to check out. + # + # @return [ Mongo::Server::Connection ] The checked out connection. + # + # @raise [ Error::PoolClosedError ] If the pool has been closed. + # @raise [ Timeout::Error ] If the connection pool is at maximum size + # and remains so for longer than the wait timeout. + def retrieve_and_connect_connection(connection_global_id) + deadline = Utils.monotonic_time + wait_timeout + + connection = get_connection(deadline, Process.pid, connection_global_id) + + begin + connect_connection(connection) + rescue Exception + # Handshake or authentication failed + @lock.synchronize do + @pending_connections.delete(connection) + @size_cv.signal + end + @populate_semaphore.signal + + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( + @server.address, + Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR + ), + ) + raise + end + + @lock.synchronize do + @checked_out_connections << connection + @pending_connections.delete(connection) + # no need to signal size_cv here since the number of unavailable + # connections is unchanged. + end + + connection + end end end end diff --git a/spec/mongo/condition_variable_spec.rb b/spec/mongo/condition_variable_spec.rb new file mode 100644 index 0000000000..54ebb2590b --- /dev/null +++ b/spec/mongo/condition_variable_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'lite_spec_helper' + +describe Mongo::ConditionVariable do + let(:lock) { Mutex.new } + let(:condition_variable) do + described_class.new(lock) + end + + it 'waits until signaled' do + result = nil + + consumer = Thread.new do + lock.synchronize do + result = condition_variable.wait(3) + end + end + + # Context switch to start the thread + sleep 0.1 + + start_time = Mongo::Utils.monotonic_time + lock.synchronize do + condition_variable.signal + end + consumer.join + + (Mongo::Utils.monotonic_time - start_time).should < 1 + end + + it 'waits until broadcast' do + result = nil + + consumer = Thread.new do + lock.synchronize do + result = condition_variable.wait(3) + end + end + + # Context switch to start the thread + sleep 0.1 + + start_time = Mongo::Utils.monotonic_time + lock.synchronize do + condition_variable.broadcast + end + consumer.join + + (Mongo::Utils.monotonic_time - start_time).should < 1 + end + + it 'times out' do + result = nil + + consumer = Thread.new do + lock.synchronize do + result = condition_variable.wait(2) + end + end + + # Context switch to start the thread + sleep 0.1 + + start_time = Mongo::Utils.monotonic_time + consumer.join + + (Mongo::Utils.monotonic_time - start_time).should > 1 + end + + context "when acquiring the lock and waiting" do + + it "releases the lock while waiting" do + + lock_acquired = false + Timeout::timeout(1) do + thread = Thread.new do + until lock_acquired + sleep 0.1 + end + lock.synchronize do + condition_variable.signal + end + end + lock.synchronize do + lock_acquired = true + condition_variable.wait(10) + end + end + end + end + + context "when waiting but not signaling" do + it "waits until timeout" do + lock.synchronize do + start = Mongo::Utils.monotonic_time + condition_variable.wait(1) + duration = Mongo::Utils.monotonic_time - start + expect(duration).to be > 1 + end + end + end +end diff --git a/spec/mongo/server/connection_pool_spec.rb b/spec/mongo/server/connection_pool_spec.rb index df37b7531d..2aaaf4b346 100644 --- a/spec/mongo/server/connection_pool_spec.rb +++ b/spec/mongo/server/connection_pool_spec.rb @@ -730,6 +730,27 @@ expect(checkout_failed_events.size).to eq(1) expect(checkout_failed_events.first.reason).to be(:connection_error) end + + context "when the error is caused by close" do + let(:pool) { server.pool } + let(:options) { { max_size: 1 } } + + it "raises an error and returns immediately" do + expect(pool.max_size).to eq(1) + Timeout::timeout(1) do + c1 = pool.check_out + thread = Thread.new do + c2 = pool.check_out + end + + sleep 0.1 + expect do + pool.close + thread.join + end.to raise_error(Mongo::Error::PoolClosedError) + end + end + end end context "when the pool is paused" do From d5bf24de3c59f49029ec78ec4549c2edbe7b014d Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 1 Dec 2022 14:55:38 -0500 Subject: [PATCH 087/198] RUBY-3189 Fix URI Options table (#2673) --- docs/reference/create-client.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index b410430648..24b416a47e 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -1026,11 +1026,11 @@ URI options are explained in detail in the :manual:`Connection URI reference invalid values (e.g. negative and non-numeric values), invalid values provided via this URI option are ignored with a warning. - * - srvMaxHosts=Integer - - ``:srv_max_hosts => Integer`` + * - srvMaxHosts=Integer + - ``:srv_max_hosts => Integer`` - * - srvServiceName=String - - ``:srv_service_name => String`` + * - srvServiceName=String + - ``:srv_service_name => String`` * - ssl=Boolean - ``:ssl => true|false`` From 5bcfbe9863ac8e6eaad49c3a191ddf47c2c22bf9 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 1 Dec 2022 15:20:55 -0500 Subject: [PATCH 088/198] RUBY-3160 fix all parse errors except bson-tutorials (#2672) --- docs/contribute.txt | 4 +- docs/reference/client-side-encryption.txt | 45 +++++++++++-------- .../connection-and-configuration.txt | 8 ++-- docs/reference/create-client.txt | 3 +- docs/reference/crud-operations.txt | 2 +- docs/reference/gridfs.txt | 2 +- docs/reference/monitoring.txt | 12 ++--- docs/reference/schema-operations.txt | 8 ++-- docs/reference/working-with-data.txt | 26 +++++------ docs/release-notes.txt | 2 +- docs/tutorials/common-errors.txt | 6 ++- 11 files changed, 65 insertions(+), 53 deletions(-) diff --git a/docs/contribute.txt b/docs/contribute.txt index d25dda4e8d..a541b15fad 100644 --- a/docs/contribute.txt +++ b/docs/contribute.txt @@ -58,7 +58,7 @@ Contribute Code The MongoDB Ruby driver source is located `at GitHub `_. -The list of known issues in the driver is available +The list of known issues in the driver is available `in JIRA `_. We recommend creating a JIRA ticket before starting work on a bug fix or @@ -73,7 +73,7 @@ the changes to the stable branches, if needed. A MongoDB deployment is required to run the tests. Setup procedures and recommendations for various deployments, as well as how to configure the driver's test suite for the deployments, are covered in the `spec -readme `. +readme `__. The driver is tested on `Evergreen `_, MongoDB's in-house continuous integration platform. After a pull request diff --git a/docs/reference/client-side-encryption.txt b/docs/reference/client-side-encryption.txt index bacce213af..8f31ceca44 100644 --- a/docs/reference/client-side-encryption.txt +++ b/docs/reference/client-side-encryption.txt @@ -128,12 +128,12 @@ enterprise-only feature. If you only intend to use explicit encryption, you may skip this step. Mongocryptd comes pre-packaged with enterprise builds of the MongoDB server -(versions 4.2 and newer). For installation instructions, see -`the MongoDB manual `_. +(versions 4.2 and newer). For installation instructions, see the +`MongoDB manual `_. In order to configure mongocryptd (for example, which port it listens on or the path used to spawn the daemon), it is necessary to pass different options to the -``Mongo::Client`` performing automatic encryption. See the `:extra_options`_ +``Mongo::Client`` performing automatic encryption. See the :ref:`:extra_options ` section of this tutorial for more information. Automatic Encryption @@ -274,8 +274,8 @@ The example above demonstrates using automatic encryption with a local master ke For more information about using other key management services to create a master key and create data keys, see the following sections of this tutorial: -- `Creating A Master Key`_ -- `Creating A Data Key`_ +- :ref:`Creating A Master Key ` +- :ref:`Creating A Data Key ` Explicit Encryption =================== @@ -372,8 +372,8 @@ The example above demonstrates using explicit encryption with a local master key For more information about using other key management services to create a master key and create data keys, see the following sections of this tutorial: -- `Creating A Master Key`_, -- `Creating A Data Key`_, +- :ref:`Creating A Master Key ` +- :ref:`Creating A Data Key ` Queryable Encryption ==================== @@ -485,8 +485,8 @@ The example above demonstrates using automatic encryption with a local master ke For more information about using other key management services to create a master key and create data keys, see the following sections of this tutorial: -- `Creating A Master Key`_ -- `Creating A Data Key`_ +- :ref:`Creating A Master Key ` +- :ref:`Creating A Data Key ` Below is an example of explicit queryable encryption. @@ -598,6 +598,7 @@ Below is an example of explicit queryable encryption. find_result = client['encryption_coll'].find(encrypted_field: find_payload).first['encrypted_field'] # => 'sensitive data' +.. _creating-a-master-key: Creating a Master Key ===================== @@ -608,6 +609,8 @@ local key, or by creating a key in a key management service. Currently Ruby driver supports AWS Key Management Service (KMS), Azure Key Vault, and Google Cloud Key Management (GCP KMS). +.. _local-master-key: + Local Master Key ~~~~~~~~~~~~~~~~ @@ -626,17 +629,21 @@ Run the following code to generate a local master key using Ruby: local_master_key = SecureRandom.random_bytes(96) # => "\xB2\xBE\x8EN\xD4\x14\xC2\x13\xC3..." (a binary blob) +.. _remote-master-key: + Remote Master Key ~~~~~~~~~~~~~~~~~ It is recommended that you use a remote Key Management Service to create and store your master key. To do so, follow steps of the -:drivers:`"Set up a Remote Master Key" section` +`"Set up a Remote Master Key" `_ in the MongoDB Client-Side Encryption documentation. For more information about creating a master key, see the -:drivers:`Create a Master Key ` +`Create a Master Key `_ section of the MongoDB manual. +.. _creating-a-data-key: + Creating a Data Key =================== @@ -679,8 +686,8 @@ key with the following code snippet: data_key_id = client_encryption.create_data_key('local') # => -See the `Local Master Key`_ section for more information about generating a new -local master key. +See the :ref:`Local Master Key ` section for more information +about generating a new local master key. Create a Data Key Using a Remote Master Key ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -788,12 +795,12 @@ certificate and private key to authenticate to KMIP server. ) # => -See the `Remote Master Key`_ section of this tutorial for more information about -generating a new remote master key and finding the information you need to -create data keys. +See the :ref:`Remote Master Key ` section of this tutorial +for more information about generating a new remote master key and finding the +information you need to create data keys. For more information about creating a data key, see the -:drivers:`Create a Data Encryption Key ` +`Create a Data Encryption Key `_ section of the MongoDB manual. For a list of possible KMS TLS options @@ -1030,7 +1037,7 @@ When you intend to use your schema map, convert it to a Ruby ``Hash`` using the .. seealso:: - :drivers:`Specify Encrypted Fields Using JSON Schema`, + `Specify Encrypted Fields Using JSON Schema `_, :manual:`Automatic Encryption Rules` .. _schema-map-path: @@ -1068,6 +1075,8 @@ decryption of any previously-encrypted data. } ) +.. _cse-extra-options: + ``:extra_options`` ~~~~~~~~~~~~~~~~~~ diff --git a/docs/reference/connection-and-configuration.txt b/docs/reference/connection-and-configuration.txt index acc28b5250..7532e73f98 100644 --- a/docs/reference/connection-and-configuration.txt +++ b/docs/reference/connection-and-configuration.txt @@ -12,7 +12,7 @@ options the driver provides, including authentication. .. toctree:: :titlesonly: - create-client - authentication - monitoring - user-management + /reference/create-client + /reference/authentication + /reference/monitoring + /reference/user-management diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index 24b416a47e..ba8525677a 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -896,6 +896,7 @@ URI options are explained in detail in the :manual:`Connection URI reference `. .. note:: + Options that are set in **milliseconds** in the URI are represented as a ``float`` in Ruby and the units are **seconds**. @@ -1341,7 +1342,7 @@ It is also possible to remove hooks from ``Mongo.tls_context_hooks`` by storing a reference to the Procs somewhere else in the application, and then using ``Array#delete_if`` to remove the desired hooks. -..warning :: +.. warning:: TLS context hooks are global and will affect every instance of ``Mongo::Client``. Any library that allows applications to enable these hooks should expose methods to diff --git a/docs/reference/crud-operations.txt b/docs/reference/crud-operations.txt index 73262aa70b..b8e5d5634b 100644 --- a/docs/reference/crud-operations.txt +++ b/docs/reference/crud-operations.txt @@ -988,7 +988,7 @@ for more information on working with these types of fields. A Note about the BSON Symbol type ================================= -Because the BSON specification deprecated the BSON symbol type, the `bson` gem +Because the BSON specification deprecated the BSON symbol type, the ``bson`` gem will serialize Ruby symbols into BSON strings when used on its own. However, in order to maintain backwards compatibility with older datasets, the Ruby driver overrides this behavior to serialize Ruby symbols as BSON symbols. This is diff --git a/docs/reference/gridfs.txt b/docs/reference/gridfs.txt index 267c4b1552..756750b18c 100644 --- a/docs/reference/gridfs.txt +++ b/docs/reference/gridfs.txt @@ -252,7 +252,7 @@ by default into the ``fs.chunks`` collection and file metadata is inserted into client.database.fs.insert_one(file) -To insert into collections with a name prefix other than `fs`, access the +To insert into collections with a name prefix other than ``fs``, access the filesystem with a ``:fs_name`` option. .. code-block:: ruby diff --git a/docs/reference/monitoring.txt b/docs/reference/monitoring.txt index ab3b2ccee1..06328456a4 100644 --- a/docs/reference/monitoring.txt +++ b/docs/reference/monitoring.txt @@ -290,9 +290,9 @@ Server Heartbeats The application can be notified of each server heartbeat by subscribing to SERVER_HEARTBEAT topic. A server heartbeat listener must implement -three methods: `started`, `succeeded` and `failed`. Each heartbeat invokes -the `started` method on the listener, and then either `succeeded` or `failed` -method depending on the outcome of the heartbeat. +three methods: ``started``, ``succeeded`` and ``failed``. Each heartbeat +invokes the ``started`` method on the listener, and then either ``succeeded`` +or ``failed`` method depending on the outcome of the heartbeat. All heartbeat events contain the address of the server that the heartbeat was sent to. Succeeded and failed events contain the round trip time for @@ -460,9 +460,9 @@ command monitoring mechanism: - ``copydb`` 2. If the command is a handshake command, either ``ismaster`` or ``hello``, on a non-monitoring connection, no event is published at all. -3. Commands sent over monitoring connections (such as ismaster and hello) do - not publish command monitoring events. Instead, every time a server is - checked a server heartbeat event is published. The server heartbeat events +3. Commands sent over monitoring connections (such as ismaster and hello) do + not publish command monitoring events. Instead, every time a server is + checked a server heartbeat event is published. The server heartbeat events do not include command or reply payloads. 4. If the command is a handshake command, and the ``speculativeAuthenticate`` options is ``true``, the command will be redacted, and an empty payload will diff --git a/docs/reference/schema-operations.txt b/docs/reference/schema-operations.txt index 713db0afc4..6d3617302a 100644 --- a/docs/reference/schema-operations.txt +++ b/docs/reference/schema-operations.txt @@ -12,7 +12,7 @@ including managing databases, collections, indexes and users. .. toctree:: :titlesonly: - database-tasks - collection-tasks - indexing - collations + /reference/database-tasks + /reference/collection-tasks + /reference/indexing + /reference/collations diff --git a/docs/reference/working-with-data.txt b/docs/reference/working-with-data.txt index d6d76f82ed..3382428f8e 100644 --- a/docs/reference/working-with-data.txt +++ b/docs/reference/working-with-data.txt @@ -12,16 +12,16 @@ implements for inserting, updating and retrieving data from MongoDB. .. toctree:: :titlesonly: - crud-operations - bulk-operations - projection - aggregation - map-reduce - text-search - geospatial-search - query-cache - gridfs - change-streams - sessions - transactions - client-side-encryption + /reference/crud-operations + /reference/bulk-operations + /reference/projection + /reference/aggregation + /reference/map-reduce + /reference/text-search + /reference/geospatial-search + /reference/query-cache + /reference/gridfs + /reference/change-streams + /reference/sessions + /reference/transactions + /reference/client-side-encryption diff --git a/docs/release-notes.txt b/docs/release-notes.txt index 5e9f8841f8..e4b853da2b 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -64,7 +64,7 @@ This release includes the following new features: specify an arbitrary comment to help trace the operation through the database profiler, currentOp and logs. - The ``estimated_document_count`` method is now using the ``count`` server - command instead of `$collStats`` aggregation pipeline stage, to support + command instead of ``$collStats`` aggregation pipeline stage, to support operation on views. Applications using the Stable API should upgrade to server versions 5.0.8 (if using MongoDB 5.0) or 5.3.2 (if using MongoDB 5.1/5.2/5.3) or newer to use the ``count`` command when API strict is enabled, diff --git a/docs/tutorials/common-errors.txt b/docs/tutorials/common-errors.txt index 6b393cc2df..0651442cb7 100644 --- a/docs/tutorials/common-errors.txt +++ b/docs/tutorials/common-errors.txt @@ -91,8 +91,10 @@ This error was last reported on Ruby Driver version 2.5.1, so updating the drive can potentially solve this issue. A user has reported that they solved this issue as follows: +.. blockquote:: + DNS servers on EC2 are generated in /etc/resolv.conf by default. Following - the answer `here `_ + the `answer `_ and setting the nameservers to Google NS I was able to fix this issue. You can find more information about this issue here: @@ -116,5 +118,5 @@ authorization mechanism using the ``auth_mech`` option in your ``mongoid.yml`` file. See more on this issue here: -`RUBY-1281 `_. +`RUBY-1684 `_. From e10a3eedf9b763549452e723e0903f3c08203d1f Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 7 Dec 2022 11:30:17 -0500 Subject: [PATCH 089/198] RUBY-2495/RUBY-2840/RUBY-2808 Rate limit new connection creations (maxConnecting) (#2666) * modularize connection pool * RUBY-2495 fix all max connecting tests * pend lb tests * fix jruby counting semaphore * RUBY-2495 add debug logging * fix lb pend * more lb pends * pend more lb tests * even more pending lb * more logging * sleep all threads * RUBY-2495 run all cmap operations from the main thread * remove debugging * return nil from wait_for_thread * attempt to fix race condition * remove counting semaphore * revert changes to loggable * first attempt at adding max connecting * remove connectionunavailable error * remove connection from the loop * signal on check in * remove debugging * add checkout timeout * fix connection return * remove obsolete method * fix connection_global_id * RUBY-2495 changes * fix cursor pinning spec * add debug loggin * add debug logging * add back raise on unowned lock * change error * more debug logging * add more debug logging * fix debug logging * RUBY-2495 attempt to fix tests * add more debug logging * remove caller * more debug logging * lock when signaling in clear * more debug logging * add back rescue * remove the rescue * remove connection pool debug logging * put the lock around the full loop * fix syntax * move checkout under the same lock * debug logging on socket timeout * fix session transaction spec * remove socket timeout logging * attempt to fix race condition * clean up code * remove more code * more cleanup * more cleanup * make connection perished retryable * add spec test * restrict topology * Revert "remove connectionunavailable error" This reverts commit 864bfff7583b3b4ce8c48424e6d4b97efadbd4db. --- lib/mongo/collection/view/change_stream.rb | 2 +- lib/mongo/error/connection_perished.rb | 2 + lib/mongo/error/missing_connection.rb | 4 +- lib/mongo/retryable.rb | 16 +- lib/mongo/server/connection.rb | 29 ++- lib/mongo/server/connection_pool.rb | 197 +++++++++--------- spec/integration/transaction_pinning_spec.rb | 4 +- spec/mongo/session_transaction_spec.rb | 2 +- spec/runners/cmap.rb | 91 +++++--- ...ckout-custom-maxConnecting-is-enforced.yml | 50 +++++ ...ool-checkout-maxConnecting-is-enforced.yml | 81 +++++++ .../pool-checkout-maxConnecting-timeout.yml | 71 +++++++ ...t-minPoolSize-connection-maxConnecting.yml | 66 ++++++ ...kout-returned-connection-maxConnecting.yml | 88 ++++++++ 14 files changed, 549 insertions(+), 154 deletions(-) create mode 100644 spec/spec_tests/data/cmap/pool-checkout-custom-maxConnecting-is-enforced.yml create mode 100644 spec/spec_tests/data/cmap/pool-checkout-maxConnecting-is-enforced.yml create mode 100644 spec/spec_tests/data/cmap/pool-checkout-maxConnecting-timeout.yml create mode 100644 spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml create mode 100644 spec/spec_tests/data/cmap/pool-checkout-returned-connection-maxConnecting.yml diff --git a/lib/mongo/collection/view/change_stream.rb b/lib/mongo/collection/view/change_stream.rb index 073b811a25..1ed8db2851 100644 --- a/lib/mongo/collection/view/change_stream.rb +++ b/lib/mongo/collection/view/change_stream.rb @@ -238,7 +238,7 @@ def close unless closed? begin @cursor.close - rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError, Error::ConnectionUnavailable + rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError, Error::MissingConnection # ignore end @cursor = nil diff --git a/lib/mongo/error/connection_perished.rb b/lib/mongo/error/connection_perished.rb index 6ca8172944..a99ba26d37 100644 --- a/lib/mongo/error/connection_perished.rb +++ b/lib/mongo/error/connection_perished.rb @@ -21,6 +21,8 @@ class Error # Exception raised when trying to perform operations on a connection that # experienced a network error. class ConnectionPerished < Error + include WriteRetryable + include ChangeStreamResumable end end end diff --git a/lib/mongo/error/missing_connection.rb b/lib/mongo/error/missing_connection.rb index 40c45b81f7..2056d7be08 100644 --- a/lib/mongo/error/missing_connection.rb +++ b/lib/mongo/error/missing_connection.rb @@ -18,7 +18,9 @@ module Mongo class Error - # Exception raised when trying to check out connection that does not exist. + # Exception raised when trying to check out a connection with a specific + # global id, and the connection for that global id no longer exists in the + # pool. class MissingConnection < Error end end diff --git a/lib/mongo/retryable.rb b/lib/mongo/retryable.rb index 5a75d1a896..38d3dc729d 100644 --- a/lib/mongo/retryable.rb +++ b/lib/mongo/retryable.rb @@ -131,7 +131,7 @@ def read_with_retry(session = nil, server_selector = nil, &block) server = select_server(cluster, server_selector, session) begin yield server - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError, Error::OperationFailure => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError, Error::OperationFailure => e e.add_note('retries disabled') raise e end @@ -163,7 +163,7 @@ def read_with_retry(session = nil, server_selector = nil, &block) # @since 2.2.6 def read_with_one_retry(options = nil) yield - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError => e if e.write_retryable? retry_message = options && options[:retry_message] log_retry(e, message: retry_message) @@ -247,7 +247,7 @@ def write_with_retry(write_concern, ending_transaction: false, context:, &block) # it later for the retry as well. yield(connection, txn_num, context.dup) end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError, Auth::Unauthorized => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError, Auth::Unauthorized => e e.add_note('modern retry') e.add_note("attempt 1") if !e.label?('RetryableWriteError') @@ -308,7 +308,7 @@ def nro_write_with_retry(write_concern, context:, &block) server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield connection, nil, context end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError, Error::OperationFailure => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError, Error::OperationFailure => e e.add_note('retries disabled') raise e end @@ -371,7 +371,7 @@ def modern_read_with_retry(session, server_selector, &block) server = select_server(cluster, server_selector, session) begin yield server - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ServerNotUsable => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished => e e.add_note('modern retry') e.add_note("attempt 1") if session.in_transaction? @@ -394,7 +394,7 @@ def legacy_read_with_retry(session, server_selector) begin attempt += 1 yield server - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished => e e.add_note('legacy retry') e.add_note("attempt #{attempt}") if attempt > client.max_read_retries || (session && session.in_transaction?) @@ -449,7 +449,7 @@ def retry_read(original_error, server_selector, session, &block) begin yield server, true - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished => e e.add_note('modern retry') e.add_note("attempt 2") raise e @@ -496,7 +496,7 @@ def retry_write(original_error, txn_num, context:, &block) server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield(connection, txn_num, context) end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::PoolError => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError => e if e.write_retryable? e.add_note('modern retry') e.add_note('attempt 2') diff --git a/lib/mongo/server/connection.rb b/lib/mongo/server/connection.rb index d5344eba2c..b8159b3263 100644 --- a/lib/mongo/server/connection.rb +++ b/lib/mongo/server/connection.rb @@ -147,6 +147,14 @@ def connection_pool options[:connection_pool] end + # Whether the connection was connected and was not interrupted, closed, + # or had an error raised. + # + # @return [ true | false ] if the connection was connected. + def connected? + !closed? && !error? && !interrupted? && !!@socket + end + # Whether the connection was closed. # # Closed connections should no longer be used. Instead obtain a new @@ -219,17 +227,9 @@ def unpin # # @since 2.0.0 def connect! - if error? - raise Error::ConnectionPerished, "Connection #{generation}:#{id} for #{address.seed} is perished. Reconnecting closed or errored connections is no longer supported" - end - - if closed? - raise Error::ConnectionPerished, "Connection #{generation}:#{id} for #{address.seed} is closed. Reconnecting closed or errored connections is no longer supported" - end + raise_if_closed! unless @socket - # When @socket is assigned, the socket should have handshaken and - # authenticated and be usable. @socket = create_socket @description, @compressor = do_connect @@ -269,6 +269,7 @@ def connect! # description instance from the hello response of the returned socket # and the compressor to use. private def do_connect + raise_if_closed! begin pending_connection = PendingConnection.new( socket, @server, monitoring, options.merge(id: id)) @@ -406,6 +407,16 @@ def handle_errors raise end end + + def raise_if_closed! + if error? + raise Error::ConnectionPerished, "Connection #{generation}:#{id} for #{address.seed} is perished. Reconnecting closed or errored connections is no longer supported" + end + + if closed? + raise Error::ConnectionPerished, "Connection #{generation}:#{id} for #{address.seed} is closed. Reconnecting closed or errored connections is no longer supported" + end + end end end end diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index 2e27f52915..2ac9756524 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -138,11 +138,6 @@ def initialize(server, options = {}) # must do so under this lock. @lock = Mutex.new - # Condition variable broadcast when a connection is added to - # @available_connections, to wake up any threads waiting for an - # available connection when pool is at max size - @available_semaphore = Semaphore.new - # Background thread reponsible for maintaining the size of # the pool to at least min_size @populator = Populator.new(self, options) @@ -156,7 +151,13 @@ def initialize(server, options = {}) # This represents the number of threads that have made it past the size_cv # gate but have not acquired a connection to add to the pending_connections # set. - @pending = 0 + @connection_requests = 0 + + # Condition variable to enforce the second check in check_out: max_connecting. + # Thei condition variable should be signaled when the number of pending + # connections decreases. + @max_connecting_cv = Mongo::ConditionVariable.new(@lock) + @max_connecting = options.fetch(:max_connecting, 2) ObjectSpace.define_finalizer(self, self.class.finalize(@available_connections, @pending_connections, @populator)) @@ -262,7 +263,7 @@ def unsynchronized_size # # @api private def unavailable_connections - @checked_out_connections.length + @pending_connections.length + @pending + @checked_out_connections.length + @pending_connections.length + @connection_requests end # Number of available connections in the pool. @@ -320,6 +321,9 @@ def summary # @api private attr_reader :populator + # @api private + attr_reader :max_connecting + # Checks a connection out of the pool. # # If there are active connections in the pool, the most recently used @@ -443,9 +447,7 @@ def do_check_in(connection) connection.record_checkin! @available_connections << connection - # Wake up only one thread waiting for an available connection, - # since only one connection was checked in. - @available_semaphore.signal + @max_connecting_cv.signal end end @@ -548,6 +550,9 @@ def do_clear(options = nil) do_pause if !@server.load_balancer? && @server.unknown? end + # Broadcast here to cause all of the threads waiting on the max + # connecting to decrease to break out of the wait loop and error. + @max_connecting_cv.broadcast # Broadcast here to cause all of the threads waiting on the pool size # to decrease to break out of the wait loop and error. @size_cv.broadcast @@ -641,6 +646,7 @@ def close(options = nil) @ready = false end + @max_connecting_cv.broadcast @size_cv.broadcast end @@ -687,7 +693,7 @@ def with_connection(connection_global_id: nil) connection = check_out(connection_global_id: connection_global_id) yield(connection) - rescue Error::SocketError, Error::SocketTimeoutError => e + rescue Error::SocketError, Error::SocketTimeoutError, Error::ConnectionPerished => e maybe_raise_pool_cleared!(connection, e) ensure if connection @@ -769,11 +775,6 @@ def populate end return create_and_add_connection - rescue Error::AuthError, Error - # wake up one thread waiting for connections, since one could not - # be created here, and can instead be created in flow - @available_semaphore.signal - raise end # Finalize the connection pool for garbage collection. @@ -806,7 +807,7 @@ def self.finalize(available_connections, pending_connections, populator) # Returns the next available connection, optionally with given # global id. If no suitable connections are available, # returns nil. - def next_available_connection(connection_global_id: nil) + def next_available_connection(connection_global_id) raise_unless_locked! if @server.load_balancer? && connection_global_id @@ -846,7 +847,10 @@ def create_and_add_connection connection = nil @lock.synchronize do - if !closed? && @ready && (unsynchronized_size + @pending) < min_size + if !closed? && @ready && + (unsynchronized_size + @connection_requests) < min_size && + @pending_connections.length < @max_connecting + then connection = create_connection @pending_connections << connection else @@ -861,6 +865,7 @@ def create_and_add_connection rescue Exception @lock.synchronize do @pending_connections.delete(connection) + @max_connecting_cv.signal @size_cv.signal end raise @@ -869,9 +874,7 @@ def create_and_add_connection @lock.synchronize do @available_connections << connection @pending_connections.delete(connection) - - # wake up one thread waiting for connections, since one was created - @available_semaphore.signal + @max_connecting_cv.signal @size_cv.signal end @@ -1071,7 +1074,7 @@ def raise_check_out_timeout!(connection_global_id) "from pool for #{@server.address}#{connection_global_id_msg} after #{wait_timeout} sec. " + "Connections in pool: #{@available_connections.length} available, " + "#{@checked_out_connections.length} checked out, " + - "#{@pending_connections.length + @pending} pending " + + "#{@pending_connections.length + @connection_requests} pending " + "(max size: #{max_size})" raise Error::ConnectionCheckOutTimeout.new(msg, address: @server.address) end @@ -1130,28 +1133,6 @@ def raise_unless_locked! end def valid_available_connection?(connection, pid, connection_global_id) - if connection.nil? - if connection_global_id - # A particular connection is requested, but it is not available. - # If it is nether available not checked out, we should stop here. - @checked_out_connections.detect do |conn| - conn.connection_global_id == connection_global_id - end.tap do |conn| - if conn.nil? - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( - @server.address, - Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR - ), - ) - raise Error::MissingConnection.new - end - end - else - return false - end - end - if connection.pid != pid log_warn("Detected PID change - Mongo client should have been reconnected (old pid #{connection.pid}, new pid #{pid}") connection.disconnect!(reason: :stale) @@ -1200,63 +1181,44 @@ def valid_available_connection?(connection, pid, connection_global_id) # @raise [ Timeout::Error ] If the connection pool is at maximum size # and remains so for longer than the wait timeout. def get_connection(deadline, pid, connection_global_id) - # The first gate to checking out a connection. Make sure the number of - # unavailable connections is less than the max pool size. - @size_cv.synchronize do - until max_size == 0 || unavailable_connections < max_size - wait = deadline - Utils.monotonic_time - raise_check_out_timeout!(connection_global_id) if wait <= 0 - @size_cv.wait(wait) - raise_if_not_ready! + if connection = next_available_connection(connection_global_id) + unless valid_available_connection?(connection, pid, connection_global_id) + return nil end - @pending += 1 - end - - loop do - # Lock must be taken on each iteration, rather for the method - # overall, otherwise other threads will not be able to check in - # a connection while this thread is waiting for one. - @lock.synchronize do - until @available_connections.empty? - connection = next_available_connection( - connection_global_id: connection_global_id + # If the connection is connected, it's not considered a + # "pending connection". The pending_connections list represents + # the set of connections that are awaiting connection. + unless connection.connected? + @connection_requests -= 1 + @pending_connections << connection + end + return connection + elsif connection_global_id && @server.load_balancer? + # A particular connection is requested, but it is not available. + # If it is nether available not checked out, we should stop here. + @checked_out_connections.detect do |conn| + conn.global_id == connection_global_id + end.tap do |conn| + if conn.nil? + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( + @server.address, + Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR + ), ) - - unless valid_available_connection?(connection, pid, connection_global_id) - # If there's no available connection and we're not looking - # for a specific connection, don't attempt to look at the - # available connections again. - if connection.nil? && !connection_global_id - break - else - next - end - end - - @pending -= 1 - @pending_connections << connection - return connection - end - - if @server.load_balancer? && connection_global_id - unless @checked_out_connections.detect { |c| c.global_id == connection_global_id } - raise Error::ConnectionUnavailable.new - end - # We need a particular connection, and if it is not available - # we can wait for an in-progress operation to return - # such a connection to the pool. - else - connection = create_connection - @pending -= 1 - @pending_connections << connection - return connection + raise Error::MissingConnection.new end end - - wait = deadline - Utils.monotonic_time - raise_check_out_timeout_locked!(connection_global_id) if wait <= 0 - @available_semaphore.wait(wait) + # We need a particular connection, and if it is not available + # we can wait for an in-progress operation to return + # such a connection to the pool. + nil + else + connection = create_connection + @connection_requests -= 1 + @pending_connections << connection + return connection end end @@ -1272,15 +1234,47 @@ def get_connection(deadline, pid, connection_global_id) # and remains so for longer than the wait timeout. def retrieve_and_connect_connection(connection_global_id) deadline = Utils.monotonic_time + wait_timeout + connection = nil + + @lock.synchronize do + + # The first gate to checking out a connection. Make sure the number of + # unavailable connections is less than the max pool size. + until max_size == 0 || unavailable_connections < max_size + wait = deadline - Utils.monotonic_time + raise_check_out_timeout!(connection_global_id) if wait <= 0 + @size_cv.wait(wait) + raise_if_not_ready! + end + @connection_requests += 1 + + while connection.nil? + # The second gate to checking out a connection. Make sure 1) there + # exists an available connection and 2) we are under max_connecting. + until @available_connections.any? || @pending_connections.length < @max_connecting + wait = deadline - Utils.monotonic_time + raise_check_out_timeout!(connection_global_id) if wait <= 0 + @max_connecting_cv.wait(wait) + raise_if_not_ready! + end - connection = get_connection(deadline, Process.pid, connection_global_id) + connection = get_connection(deadline, Process.pid, connection_global_id) + wait = deadline - Utils.monotonic_time + raise_check_out_timeout!(connection_global_id) if connection.nil? && wait <= 0 + end + end begin connect_connection(connection) rescue Exception # Handshake or authentication failed @lock.synchronize do - @pending_connections.delete(connection) + if @pending_connections.include?(connection) + @pending_connections.delete(connection) + else + @connection_requests -= 1 + end + @max_connecting_cv.signal @size_cv.signal end @populate_semaphore.signal @@ -1296,7 +1290,12 @@ def retrieve_and_connect_connection(connection_global_id) @lock.synchronize do @checked_out_connections << connection - @pending_connections.delete(connection) + if @pending_connections.include?(connection) + @pending_connections.delete(connection) + else + @connection_requests -= 1 + end + @max_connecting_cv.signal # no need to signal size_cv here since the number of unavailable # connections is unchanged. end diff --git a/spec/integration/transaction_pinning_spec.rb b/spec/integration/transaction_pinning_spec.rb index 70683d95d1..87645c0af6 100644 --- a/spec/integration/transaction_pinning_spec.rb +++ b/spec/integration/transaction_pinning_spec.rb @@ -80,7 +80,7 @@ client["tx_pin"].create end - it 'raises ConnectionUnavailable' do + it 'raises MissingConnection' do session = client.start_session session.start_transaction client["tx_pin"].insert_one({test: 1}, session: session) @@ -94,7 +94,7 @@ lambda do client["tx_pin"].insert_one({test: 2}, session: session) - end.should raise_error(Mongo::Error::ConnectionUnavailable) + end.should raise_error(Mongo::Error::MissingConnection) end end diff --git a/spec/mongo/session_transaction_spec.rb b/spec/mongo/session_transaction_spec.rb index 55a7f4b5a3..ee95f400c2 100644 --- a/spec/mongo/session_transaction_spec.rb +++ b/spec/mongo/session_transaction_spec.rb @@ -152,7 +152,7 @@ class SessionTransactionSpecError < StandardError; end it 'times out' do start = Mongo::Utils.monotonic_time - 10.times do |i| + 11.times do |i| expect(Mongo::Utils).to receive(:monotonic_time).ordered.and_return(start + i) end expect(Mongo::Utils).to receive(:monotonic_time).ordered.and_return(start + 200) diff --git a/spec/runners/cmap.rb b/spec/runners/cmap.rb index 516685c185..644daea162 100644 --- a/spec/runners/cmap.rb +++ b/spec/runners/cmap.rb @@ -18,9 +18,6 @@ class Spec # @return [ Array ] spec_ops The spec operations. attr_reader :spec_ops - # @return [ Array ] processed_ops The processed operations. - attr_reader :processed_ops - # @return [ Error | nil ] error The expected error. attr_reader :expected_error @@ -45,7 +42,6 @@ def initialize(test_path) @description = @test['description'] @pool_options = process_options(@test['poolOptions']) @spec_ops = @test['operations'].map { |o| Operation.new(self, o) } - @processed_ops = [] @expected_error = @test['error'] @expected_events = @test['events'] @ignore_events = @test['ignore'] || [] @@ -53,7 +49,6 @@ def initialize(test_path) @threads = Set.new process_run_on - preprocess end attr_reader :pool @@ -76,7 +71,7 @@ def run state = {} {}.tap do |result| - processed_ops.each do |op| + spec_ops.each do |op| err = op.run(pool, state) if err @@ -240,7 +235,8 @@ def process_options(options) opts[:wait_queue_timeout] = kv.last / 1000.0 when 'backgroundThreadIntervalMS' # The populator busy loops, this option doesn't apply to our driver. - #opts[:pool_background_thread_interval] = kv.last / 1000.0 + when 'maxConnecting' + opts[:max_connecting] = kv.last when 'appName' opts[:app_name] = kv.last else @@ -290,22 +286,6 @@ def process_run_on end end - # Places operations run by the non-main thread in the `thread_ops` field of the corresponding - # `start` operation. - def preprocess - until spec_ops.empty? - processed_ops << spec_ops.shift - - if processed_ops.last.name == "start" - spec_ops.delete_if do |op| - if op.thread == processed_ops.last.target - processed_ops.last.thread_ops << op - end - end - end - end - end - def configure_fail_point @client.database.command(@fail_point_command) if @fail_point_command end @@ -325,9 +305,6 @@ class Operation # @return [ String | nil ] target The name of the started thread. attr_reader :target - # @return [ Array ] thread_ops The operations to run on the thread. - attr_reader :thread_ops - # @return [ Integer | nil ] ms The number of milliseconds to sleep. attr_reader :ms @@ -352,7 +329,6 @@ def initialize(spec, operation) @spec = spec @name = operation['name'] @thread = operation['thread'] - @thread_ops = [] @target = operation['target'] @ms = operation['ms'] @label = operation['label'] @@ -363,8 +339,9 @@ def initialize(spec, operation) end def run(pool, state, main_thread = true) - @pool = pool + return run_on_thread(state) if thread && main_thread + @pool = pool case name when 'start' run_start_op(state) @@ -411,12 +388,27 @@ def run(pool, state, main_thread = true) private def run_start_op(state) - state[target] = Thread.start do - Thread.current[:name] = @target - thread_ops.each do |op| - op.run(pool, state, false) + thread_context = ThreadContext.new + thread = Thread.start do + loop do + begin + op = thread_context.operations.pop(true) + op.run(pool, state, false) + rescue ThreadError + # Queue is empty + end + if thread_context.stop? + break + else + sleep 0.1 + end end end + class << thread + attr_accessor :context + end + thread.context = thread_context + state[target] = thread # Allow the thread to begin running. sleep 0.1 @@ -433,7 +425,13 @@ def run_wait_op(_state) end def run_wait_for_thread_op(state) - state[target].join + if thread = state[target] + thread.context.signal_stop + thread.join + else + raise "Expected thread for '#{thread}' but none exists." + end + nil end def run_wait_for_event_op(state) @@ -486,6 +484,33 @@ def run_close_op(state) def run_ready_op(state) pool.ready end + + def run_on_thread(state) + if thd = state[thread] + thd.context.operations << self + # Sleep to allow the other thread to execute the new command. + sleep 0.1 + else + raise "Expected thread for '#{thread}' but none exists." + end + nil + end + end + + class ThreadContext + def initialize + @operations = Queue.new + end + + def stop? + !!@stop + end + + def signal_stop + @stop = true + end + + attr_reader :operations end end end diff --git a/spec/spec_tests/data/cmap/pool-checkout-custom-maxConnecting-is-enforced.yml b/spec/spec_tests/data/cmap/pool-checkout-custom-maxConnecting-is-enforced.yml new file mode 100644 index 0000000000..84db10186a --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-checkout-custom-maxConnecting-is-enforced.yml @@ -0,0 +1,50 @@ +version: 1 +style: integration +description: custom maxConnecting is enforced +runOn: + - minServerVersion: "4.4.0" + - topology: [ "single", "replicaset", "sharded" ] +failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 500 +poolOptions: + maxConnecting: 1 + # gives opportunity for the checkout in thread2 to establish a new connection, which it must not do until thread1 establishes one + maxPoolSize: 2 + waitQueueTimeoutMS: 5000 +operations: + - name: ready + # thread1 exists to consume the single permit to open a connection, + # so that thread2 would be blocked acquiring a permit, which results in ordering its ConnectionCreated event after + # the ConnectionReady event from thread1. + - name: start + target: thread1 + - name: start + target: thread2 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCreated + count: 1 + - name: checkOut + thread: thread2 + - name: waitForEvent + event: ConnectionReady + count: 2 +events: + - type: ConnectionCreated + - type: ConnectionReady + - type: ConnectionCreated + - type: ConnectionReady +ignore: + - ConnectionCheckOutStarted + - ConnectionCheckedIn + - ConnectionCheckedOut + - ConnectionClosed + - ConnectionPoolCreated + - ConnectionPoolReady diff --git a/spec/spec_tests/data/cmap/pool-checkout-maxConnecting-is-enforced.yml b/spec/spec_tests/data/cmap/pool-checkout-maxConnecting-is-enforced.yml new file mode 100644 index 0000000000..bac6f0a2ed --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-checkout-maxConnecting-is-enforced.yml @@ -0,0 +1,81 @@ +version: 1 +style: integration +description: maxConnecting is enforced +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - + # required for blockConnection in fail point + minServerVersion: "4.4.0" + - topology: [ "single", "replicaset", "sharded" ] +failPoint: + configureFailPoint: failCommand + # high amount to ensure not interfered with by monitor checks. + mode: { times: 50 } + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 750 +poolOptions: + maxPoolSize: 10 + waitQueueTimeoutMS: 5000 +operations: + - name: ready + # start 3 threads + - name: start + target: thread1 + - name: start + target: thread2 + - name: start + target: thread3 + # start creating a Connection. This will take a while + # due to the fail point. + - name: checkOut + thread: thread1 + # wait for thread1 to actually start creating a Connection + - name: waitForEvent + event: ConnectionCreated + count: 1 + # wait some more time to ensure thread1 has begun establishing a Connection + - name: wait + ms: 100 + # start 2 check out requests. Only one thread should + # start creating a Connection and the other one should be + # waiting for pendingConnectionCount to be less than maxConnecting, + # only starting once thread1 finishes creating its Connection. + - name: checkOut + thread: thread2 + - name: checkOut + thread: thread3 + # wait until all Connections have been created. + - name: waitForEvent + event: ConnectionReady + count: 3 +events: + # thread1 creates its connection + - type: ConnectionCreated + address: 42 + connectionId: 1 + # either thread2 or thread3 creates its connection + # the other thread is stuck waiting for maxConnecting to come down + - type: ConnectionCreated + address: 42 + # thread1 finishes establishing its connection, freeing + # up the blocked thread to start establishing + - type: ConnectionReady + address: 42 + connectionId: 1 + - type: ConnectionCreated + address: 42 + # the remaining two Connections finish establishing + - type: ConnectionReady + address: 42 + - type: ConnectionReady + address: 42 +ignore: + - ConnectionCheckOutStarted + - ConnectionCheckedIn + - ConnectionCheckedOut + - ConnectionClosed + - ConnectionPoolCreated + - ConnectionPoolReady diff --git a/spec/spec_tests/data/cmap/pool-checkout-maxConnecting-timeout.yml b/spec/spec_tests/data/cmap/pool-checkout-maxConnecting-timeout.yml new file mode 100644 index 0000000000..d4e251fc65 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-checkout-maxConnecting-timeout.yml @@ -0,0 +1,71 @@ +version: 1 +style: integration +description: waiting on maxConnecting is limited by WaitQueueTimeoutMS +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - + # required for blockConnection in fail point + minServerVersion: "4.4.0" + - topology: [ "single", "replicaset", "sharded" ] +failPoint: + configureFailPoint: failCommand + # high amount to ensure not interfered with by monitor checks. + mode: { times: 50 } + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 750 +poolOptions: + maxPoolSize: 10 + # Drivers that limit connection establishment by waitQueueTimeoutMS may skip + # this test. While waitQueueTimeoutMS is technically not supposed to limit establishment time, + # it will soon be deprecated, so it is easier for those drivers to just skip this test. + waitQueueTimeoutMS: 50 +operations: + - name: ready + # start creating two connections simultaneously. + - name: start + target: thread1 + - name: checkOut + thread: thread1 + - name: start + target: thread2 + - name: checkOut + thread: thread2 + # wait for other two threads to start establishing + - name: waitForEvent + event: ConnectionCreated + count: 2 + # start a third thread that will be blocked waiting for + # one of the other two to finish + - name: start + target: thread3 + - name: checkOut + thread: thread3 + - name: waitForEvent + event: ConnectionCheckOutFailed + count: 1 + # rejoin thread3, should experience error + - name: waitForThread + target: thread3 +error: + type: WaitQueueTimeoutError + message: Timed out while checking out a connection from connection pool +events: + - type: ConnectionCheckOutStarted + address: 42 + - type: ConnectionCheckOutStarted + address: 42 + - type: ConnectionCheckOutStarted + address: 42 + - type: ConnectionCheckOutFailed + reason: timeout + address: 42 +ignore: + - ConnectionCreated + - ConnectionCheckedIn + - ConnectionCheckedOut + - ConnectionClosed + - ConnectionPoolCreated + - ConnectionPoolReady diff --git a/spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml b/spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml new file mode 100644 index 0000000000..9c7c4cd43b --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml @@ -0,0 +1,66 @@ +version: 1 +style: integration +description: threads blocked by maxConnecting check out minPoolSize connections +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - + # required for blockConnection in fail point + minServerVersion: "4.4.0" + - topology: [ "single", "replicaset", "sharded" ] +failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 500 +poolOptions: + # allows both thread1 and the background thread to start opening connections concurrently + minPoolSize: 2 + # gives opportunity for the checkout in thread2 to open a new connection, which it must not do nonetheless + maxPoolSize: 3 + waitQueueTimeoutMS: 5000 +operations: + - name: ready + # thread1 exists to hold on one of the two permits to open a connection (the other one is initially held by the background thread), + # so that thread2 would be blocked acquiring a permit, which opens an opportunity for it to grab the connection newly opened + # by the background thread instead of opening a third connection. + - name: start + target: thread1 + - name: start + target: thread2 + # Ideally, thread1 should be holding for its permit to open a connection till the end of the test, but we cannot express that. + # This delay emulates the above requirement: + # - it is long enough to make sure that the background thread opens a connection before thread1 releases its permit; + # - it is short enough to allow thread2 to become blocked acquiring a permit to open a connection, and then grab the connection + # opened by the background thread, before the background thread releases its permit. + # This wait is not necessary in ruby since the background thread runs immediately. + - name: wait + ms: 0 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCreated + count: 2 + - name: checkOut + thread: thread2 + - name: waitForEvent + event: ConnectionCheckedOut + count: 2 +events: + # exactly 2 connections must be created and checked out + - type: ConnectionCreated + address: 42 + - type: ConnectionCreated + address: 42 + - type: ConnectionCheckedOut + address: 42 + - type: ConnectionCheckedOut + address: 42 +ignore: + - ConnectionPoolReady + - ConnectionClosed + - ConnectionReady + - ConnectionPoolCreated + - ConnectionCheckOutStarted diff --git a/spec/spec_tests/data/cmap/pool-checkout-returned-connection-maxConnecting.yml b/spec/spec_tests/data/cmap/pool-checkout-returned-connection-maxConnecting.yml new file mode 100644 index 0000000000..0accf2eec1 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-checkout-returned-connection-maxConnecting.yml @@ -0,0 +1,88 @@ +version: 1 +style: integration +description: threads blocked by maxConnecting check out returned connections +# Remove the topology runOn requirement when cmap specs are adjusted for lbs +runOn: + - + # required for blockConnection in fail point + minServerVersion: "4.4.0" + - topology: [ "single", "replicaset", "sharded" ] +failPoint: + configureFailPoint: failCommand + # high amount to ensure not interfered with by monitor checks. + mode: { times: 50 } + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 750 +poolOptions: + maxPoolSize: 10 + waitQueueTimeoutMS: 5000 +operations: + - name: ready + # check out a connection and hold on to it. + - name: checkOut + label: conn0 + # then start three threads that all attempt to check out. Two threads + # will fill maxConnecting, and the other should be waiting either for + # the other two to finish or for the main thread to check its connection + # back in. + - name: start + target: thread1 + - name: checkOut + thread: thread1 + - name: start + target: thread2 + - name: checkOut + thread: thread2 + - name: start + target: thread3 + - name: checkOut + thread: thread3 + # wait for all three to start checking out and a little longer + # for the establishments to begin. + - name: waitForEvent + event: ConnectionCheckOutStarted + count: 4 + - name: wait + ms: 100 + # check original connection back in, so the thread that isn't + # currently establishing will become unblocked. Then wait for + # all threads to complete. + - name: checkIn + connection: conn0 + - name: waitForEvent + event: ConnectionCheckedOut + count: 4 +events: + # main thread checking out a Connection and holding it + - type: ConnectionCreated + address: 42 + connectionId: 1 + - type: ConnectionCheckedOut + address: 42 + # two threads creating their Connections + - type: ConnectionCreated + address: 42 + - type: ConnectionCreated + address: 42 + # main thread checking its Connection back in + - type: ConnectionCheckedIn + connectionId: 1 + address: 42 + # remaining thread checking out the returned Connection + - type: ConnectionCheckedOut + connectionId: 1 + address: 42 + # first two threads finishing Connection establishment + - type: ConnectionCheckedOut + address: 42 + - type: ConnectionCheckedOut + address: 42 +ignore: + - ConnectionPoolReady + - ConnectionClosed + - ConnectionReady + - ConnectionPoolCreated + - ConnectionCheckOutStarted From 466e7cd808132c944fe110e77631557492f7e0d7 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 7 Dec 2022 11:30:43 -0500 Subject: [PATCH 090/198] RUBY-3190 Break NoWritesPerformed-Only Error Sequence (#2675) --- .../insertOne-noWritesPerformedError.yml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 spec/spec_tests/data/retryable_writes/unified/insertOne-noWritesPerformedError.yml diff --git a/spec/spec_tests/data/retryable_writes/unified/insertOne-noWritesPerformedError.yml b/spec/spec_tests/data/retryable_writes/unified/insertOne-noWritesPerformedError.yml new file mode 100644 index 0000000000..3295d153dd --- /dev/null +++ b/spec/spec_tests/data/retryable_writes/unified/insertOne-noWritesPerformedError.yml @@ -0,0 +1,54 @@ +description: "retryable-writes insertOne noWritesPerformedErrors" + +schemaVersion: "1.0" + +runOnRequirements: + - minServerVersion: "6.0" + topologies: [ replicaset ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandFailedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName retryable-writes-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName no-writes-performed-collection + +tests: + - description: "InsertOne fails after NoWritesPerformed error" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - insert + errorCode: 64 + errorLabels: + - NoWritesPerformed + - RetryableWriteError + - name: insertOne + object: *collection0 + arguments: + document: + x: 1 + expectError: + errorCode: 64 + errorLabelsContain: + - NoWritesPerformed + - RetryableWriteError + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] From a76e8000b987a13c361e55d58e0e44050c339965 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Wed, 7 Dec 2022 11:31:43 -0500 Subject: [PATCH 091/198] RUBY-3185 Remove getNonce command usage for 6.2+ (#2676) * RUBY-3185 sync spec tests * remove unnecessary tests * add debug logging * fix debug logging * put max server version on get nonce * remove debug logging --- lib/mongo/loggable.rb | 4 ++-- spec/mongo/auth/cr_spec.rb | 22 +------------------ .../redacted-commands.yml | 8 +++++++ .../valid-pass/observeSensitiveCommands.yml | 6 +++++ 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/lib/mongo/loggable.rb b/lib/mongo/loggable.rb index 8115f42848..f0193af68e 100644 --- a/lib/mongo/loggable.rb +++ b/lib/mongo/loggable.rb @@ -96,7 +96,7 @@ def log_warn(message) # # @since 2.1.0 def logger - ((options && options[:logger]) || Logger.logger) + ((respond_to?(:options) && options && options[:logger]) || Logger.logger) end private @@ -106,7 +106,7 @@ def format_message(message) end def _mongo_log_prefix - (options && options[:log_prefix]) || PREFIX + (respond_to?(:options) && options && options[:log_prefix]) || PREFIX end end end diff --git a/spec/mongo/auth/cr_spec.rb b/spec/mongo/auth/cr_spec.rb index cbee7fdf9c..2eaf93a2b0 100644 --- a/spec/mongo/auth/cr_spec.rb +++ b/spec/mongo/auth/cr_spec.rb @@ -19,6 +19,7 @@ end context 'when the user is not authorized' do + max_server_fcv "4.0" let(:user) do Mongo::Auth::User.new( @@ -44,7 +45,6 @@ context 'when compression is used' do require_compression - min_server_fcv '3.6' it 'does not compress the message' do expect(Mongo::Protocol::Compressed).not_to receive(:new) @@ -55,24 +55,4 @@ end end end - - context 'when the user is authorized for the database' do - max_server_fcv '2.6' - - before do - connection.connect! - end - - let(:cr) do - described_class.new(root_user, connection) - end - - let(:login) do - cr.login - end - - it 'logs the user into the connection' do - expect(login['ok']).to eq(1) - end - end end diff --git a/spec/spec_tests/data/command_monitoring_unified/redacted-commands.yml b/spec/spec_tests/data/command_monitoring_unified/redacted-commands.yml index 570fb5a547..adeab99b18 100644 --- a/spec/spec_tests/data/command_monitoring_unified/redacted-commands.yml +++ b/spec/spec_tests/data/command_monitoring_unified/redacted-commands.yml @@ -93,6 +93,8 @@ tests: payload: { $$exists: false } - description: "getnonce" + runOnRequirements: + - maxServerVersion: 6.1.99 # getnonce removed as of 6.2 via SERVER-71007 operations: - name: runCommand object: *database @@ -159,6 +161,8 @@ tests: roles: { $$exists: false } - description: "copydbgetnonce" + runOnRequirements: + - maxServerVersion: 3.6.99 # copydbgetnonce was removed as of 4.0 via SERVER-32276 operations: - name: runCommand object: *database @@ -176,6 +180,8 @@ tests: command: { copydbgetnonce: { $$exists: false } } - description: "copydbsaslstart" + runOnRequirements: + - maxServerVersion: 4.0.99 # copydbsaslstart was removed as of 4.2 via SERVER-36211 operations: - name: runCommand object: *database @@ -193,6 +199,8 @@ tests: command: { copydbsaslstart: { $$exists: false } } - description: "copydb" + runOnRequirements: + - maxServerVersion: 4.0.99 # copydb was removed as of 4.2 via SERVER-36257 operations: - name: runCommand object: *database diff --git a/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml b/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml index 13db413706..cd4d27a165 100644 --- a/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml +++ b/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml @@ -38,6 +38,8 @@ createEntities: tests: - description: "getnonce is observed with observeSensitiveCommands=true" + runOnRequirements: + - maxServerVersion: 6.1.99 # getnonce removed as of 6.2 via SERVER-71007 operations: - name: runCommand object: *databaseObserveSensitiveCommands @@ -57,6 +59,8 @@ tests: nonce: { $$exists: false } - description: "getnonce is not observed with observeSensitiveCommands=false" + runOnRequirements: + - maxServerVersion: 6.1.99 # getnonce removed as of 6.2 via SERVER-71007 operations: - name: runCommand object: *databaseDoNotObserveSensitiveCommands @@ -68,6 +72,8 @@ tests: events: [] - description: "getnonce is not observed by default" + runOnRequirements: + - maxServerVersion: 6.1.99 # getnonce removed as of 6.2 via SERVER-71007 operations: - name: runCommand object: *databaseDoNotObserveSensitiveCommandsByDefault From 419ed2a50dba1e622525f940f09ab2187e11c470 Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 8 Dec 2022 13:51:01 -0500 Subject: [PATCH 092/198] RUBY-2492 Test that clients wait 500ms between failed isMaster checks (#2661) * RUBY-2492 Test that clients wait 500ms between failed isMaster checks * change to 0.5 elswhere * Update spec/integration/sdam_error_handling_spec.rb * restrict the version * add debug logging * change client options * remove debug logging --- lib/mongo/cluster.rb | 4 +- lib/mongo/server_selector/base.rb | 4 +- spec/integration/sdam_error_handling_spec.rb | 59 ++++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/lib/mongo/cluster.rb b/lib/mongo/cluster.rb index 561c5d20f3..3769254263 100644 --- a/lib/mongo/cluster.rb +++ b/lib/mongo/cluster.rb @@ -263,10 +263,10 @@ def initialize(seeds, monitoring, options = Options::Redacted.new) log_debug("Waiting for up to #{'%.2f' % time_remaining} seconds for servers to be scanned: #{summary}") # Since the semaphore may have been signaled between us checking # the servers list above and the wait call below, we should not - # wait for the full remaining time - wait for up to 1 second, then + # wait for the full remaining time - wait for up to 0.5 second, then # recheck the state. begin - server_selection_semaphore.wait([time_remaining, 1].min) + server_selection_semaphore.wait([time_remaining, 0.5].min) rescue ::Timeout::Error # nothing end diff --git a/lib/mongo/server_selector/base.rb b/lib/mongo/server_selector/base.rb index ad8d581e82..794c354732 100644 --- a/lib/mongo/server_selector/base.rb +++ b/lib/mongo/server_selector/base.rb @@ -625,9 +625,9 @@ def wait_for_server_selection(cluster, time_remaining) if cluster.server_selection_semaphore # Since the semaphore may have been signaled between us checking # the servers list earlier and the wait call below, we should not - # wait for the full remaining time - wait for up to 1 second, then + # wait for the full remaining time - wait for up to 0.5 second, then # recheck the state. - cluster.server_selection_semaphore.wait([time_remaining, 1].min) + cluster.server_selection_semaphore.wait([time_remaining, 0.5].min) else if Lint.enabled? raise Error::LintError, 'Waiting for server selection without having a server selection semaphore' diff --git a/spec/integration/sdam_error_handling_spec.rb b/spec/integration/sdam_error_handling_spec.rb index c2efd7bd7a..73a6bdc4ac 100644 --- a/spec/integration/sdam_error_handling_spec.rb +++ b/spec/integration/sdam_error_handling_spec.rb @@ -425,4 +425,63 @@ end end end + + context "when there is an error on the handshake" do + # require appName for fail point + min_server_version "4.9" + + let(:admin_client) do + new_local_client( + [SpecConfig.instance.addresses.first], + SpecConfig.instance.test_options.merge({ + connect: :direct, + populator_io: false, + direct_connection: true, + app_name: "SDAMMinHeartbeatFrequencyTest", + database: 'admin' + }) + ) + end + + let(:cmd_client) do + # Change the server selection timeout so that we are given a new cluster. + admin_client.with(server_selection_timeout: 5) + end + + let(:set_fail_point) do + admin_client.command( + configureFailPoint: 'failCommand', + mode: { times: 5 }, + data: { + failCommands: %w(isMaster hello), + errorCode: 1234, + appName: "SDAMMinHeartbeatFrequencyTest" + }, + ) + end + + let(:operation) do + expect(server.monitor.connection).not_to be nil + set_fail_point + end + + it "waits 500ms between failed hello checks" do + operation + start = Mongo::Utils.monotonic_time + cmd_client.command(hello: 1) + duration = Mongo::Utils.monotonic_time - start + expect(duration).to be >= 2 + expect(duration).to be <= 3.5 + + # The cluster that we use to set up the failpoint should not be the same + # one we ping on, so that the ping will have to select a server. The admin + # client has already selected a server. + expect(admin_client.cluster.object_id).to_not eq(cmd_client.cluster.object_id) + end + + after do + admin_client.command(configureFailPoint: 'failCommand', mode: 'off') + cmd_client.close + end + end end From 83f1055fe0593a85b905ffbb7a4ca5c464fa7f88 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 9 Dec 2022 09:22:14 +0100 Subject: [PATCH 093/198] RUBY-3192 Fix serverless test setup (#2679) --- spec/support/cluster_tools.rb | 7 ++++++- spec/support/spec_config.rb | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/support/cluster_tools.rb b/spec/support/cluster_tools.rb index 5ecdd31a8d..aedde7be4e 100644 --- a/spec/support/cluster_tools.rb +++ b/spec/support/cluster_tools.rb @@ -332,6 +332,11 @@ def admin_client end def direct_client(address, options = {}) + connect = if SpecConfig.instance.connect_options[:connect] == :load_balanced + :load_balanced + else + :direct + end @direct_clients ||= {} cache_key = {address: address}.update(options) ( @@ -339,7 +344,7 @@ def direct_client(address, options = {}) [address.to_s], SpecConfig.instance.test_options.merge( SpecConfig.instance.auth_options).merge( - connect: :direct, server_selection_timeout: 10).merge(options)) + connect: connect, server_selection_timeout: 10).merge(options)) ).tap do |client| ClientRegistry.reconnect_client_if_perished(client) end diff --git a/spec/support/spec_config.rb b/spec/support/spec_config.rb index bcf2301d40..ac87d7d193 100644 --- a/spec/support/spec_config.rb +++ b/spec/support/spec_config.rb @@ -87,6 +87,8 @@ def connect_options begin case client.cluster.topology.class.name + when /LoadBalanced/ + { connect: :load_balanced } when /Replica/ { connect: :replica_set, replica_set: client.cluster.topology.replica_set_name } when /Sharded/ From a94421d0f165b53a00a231052087c8dfc5382429 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 9 Dec 2022 09:24:07 +0100 Subject: [PATCH 094/198] RUBY-3187 Allow disable ocsp endpoint verify for KMS (#2678) --- lib/mongo/crypt/kms.rb | 1 - spec/mongo/crypt/kms_spec.rb | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/mongo/crypt/kms.rb b/lib/mongo/crypt/kms.rb index 975e119716..19050535b7 100644 --- a/lib/mongo/crypt/kms.rb +++ b/lib/mongo/crypt/kms.rb @@ -95,7 +95,6 @@ def validate_tls_options(options) %i( ssl_verify_certificate ssl_verify_hostname - ssl_verify_ocsp_endpoint ).each do |opt| if provider_opts[opt] == false raise ArgumentError.new( diff --git a/spec/mongo/crypt/kms_spec.rb b/spec/mongo/crypt/kms_spec.rb index 89f749af1d..f84ebadeb2 100644 --- a/spec/mongo/crypt/kms_spec.rb +++ b/spec/mongo/crypt/kms_spec.rb @@ -33,7 +33,6 @@ %i( ssl_verify_certificate ssl_verify_hostname - ssl_verify_ocsp_endpoint ).each do |insecure_opt| expect { Mongo::Crypt::KMS::Validations.validate_tls_options( @@ -46,14 +45,18 @@ end it 'allows valid options' do - options = { - aws: { - ssl: true, - ssl_cert_string: 'Content is not validated', - - } - } + expect do + Mongo::Crypt::KMS::Validations.validate_tls_options( + { + aws: { + ssl: true, + ssl_cert_string: 'Content is not validated', + ssl_verify_ocsp_endpoint: false + } + } + ) + end.not_to raise_error end end end -end \ No newline at end of file +end From 972456bfd3a0d901fe7890c3734a7ac0bde452ef Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 9 Dec 2022 09:26:29 +0100 Subject: [PATCH 095/198] Revert "RUBY-3184 Rename crypt_shared_lib_required (#2670)" (#2677) This reverts commit 932b06b7564a5e5ae8d4ad08fe8d6ceee629e4eb. --- docs/reference/client-side-encryption.txt | 2 +- lib/mongo/client.rb | 2 +- lib/mongo/crypt/auto_encrypter.rb | 8 ++++---- lib/mongo/crypt/handle.rb | 6 +++--- .../client_side_encryption/mongocryptd_prose_spec.rb | 2 +- spec/mongo/crypt/handle_spec.rb | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/reference/client-side-encryption.txt b/docs/reference/client-side-encryption.txt index 8f31ceca44..b20ad0d23a 100644 --- a/docs/reference/client-side-encryption.txt +++ b/docs/reference/client-side-encryption.txt @@ -111,7 +111,7 @@ If the library can be loaded, then the driver will not try to spawn mongocryptd The daemon will be still spawned if the shared library cannot be found. It is also possible to require using the shared library by passing -``crypt_shared_required: true`` option when creating a client. In this case, +``crypt_shared_lib_required: true`` option when creating a client. In this case, an error will be raised if the shared library cannot be loaded. .. note:: diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index b0654e83a1..92bde662e6 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -466,7 +466,7 @@ def hash # - :crypt_shared_lib_path => [ String | nil ] Path that should # be the used to load the crypt shared library. Providing this option # overrides default crypt shared library load paths for libmongocrypt. - # - :crypt_shared_required => [ Boolean | nil ] Whether + # - :crypt_shared_lib_required => [ Boolean | nil ] Whether # crypt shared library is required. If 'true', an error will be raised # if a crypt_shared library cannot be loaded by libmongocrypt. # diff --git a/lib/mongo/crypt/auto_encrypter.rb b/lib/mongo/crypt/auto_encrypter.rb index ad34486a77..1ac24430fd 100644 --- a/lib/mongo/crypt/auto_encrypter.rb +++ b/lib/mongo/crypt/auto_encrypter.rb @@ -84,7 +84,7 @@ class AutoEncrypter # @option options [ String | nil ] :crypt_shared_lib_path Path that should # be the used to load the crypt shared library. Providing this option # overrides default crypt shared library load paths for libmongocrypt. - # @option options [ Boolean | nil ] :crypt_shared_required Whether + # @option options [ Boolean | nil ] :crypt_shared_lib_required Whether # crypt shared library is required. If 'true', an error will be raised # if a crypt_shared library cannot be loaded by libmongocrypt. # @@ -103,7 +103,7 @@ def initialize(options) encrypted_fields_map: @options[:encrypted_fields_map], bypass_query_analysis: @options[:bypass_query_analysis], crypt_shared_lib_path: @options[:extra_options][:crypt_shared_lib_path], - crypt_shared_required: @options[:extra_options][:crypt_shared_required], + crypt_shared_lib_required: @options[:extra_options][:crypt_shared_lib_required], ) @mongocryptd_options = @options[:extra_options].slice( @@ -115,9 +115,9 @@ def initialize(options) @mongocryptd_options[:mongocryptd_bypass_spawn] = @options[:bypass_auto_encryption] || @options[:extra_options][:mongocryptd_bypass_spawn] || @crypt_handle.crypt_shared_lib_available? || - @options[:extra_options][:crypt_shared_required] + @options[:extra_options][:crypt_shared_lib_required] - unless @options[:extra_options][:crypt_shared_required] || @crypt_handle.crypt_shared_lib_available? + unless @options[:extra_options][:crypt_shared_lib_required] || @crypt_handle.crypt_shared_lib_available? # Set server selection timeout to 1 to prevent the client waiting for a # long timeout before spawning mongocryptd @mongocryptd_client = Client.new( diff --git a/lib/mongo/crypt/handle.rb b/lib/mongo/crypt/handle.rb index eb469fedbb..512cf10a6e 100644 --- a/lib/mongo/crypt/handle.rb +++ b/lib/mongo/crypt/handle.rb @@ -56,7 +56,7 @@ class Handle # @option options [ String | nil ] :crypt_shared_lib_path Path that should # be the used to load the crypt shared library. Providing this option # overrides default crypt shared library load paths for libmongocrypt. - # @option options [ Boolean | nil ] :crypt_shared_required Whether + # @option options [ Boolean | nil ] :crypt_shared_lib_required Whether # crypt_shared library is required. If 'true', an error will be raised # if a crypt_shared library cannot be loaded by libmongocrypt. # @option options [ Boolean | nil ] :explicit_encryption_only Whether this @@ -104,8 +104,8 @@ def initialize(kms_providers, kms_tls_options, options={}) initialize_mongocrypt - @crypt_shared_required = !!options[:crypt_shared_required] - if @crypt_shared_required && crypt_shared_lib_version == 0 + @crypt_shared_lib_required = !!options[:crypt_shared_lib_required] + if @crypt_shared_lib_required && crypt_shared_lib_version == 0 raise Mongo::Error::CryptError.new( "Crypt shared library is required, but cannot be loaded according to libmongocrypt" ) diff --git a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb index 9976896c24..46cfbc21f8 100644 --- a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb +++ b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb @@ -81,7 +81,7 @@ def done! let(:extra_options) do { crypt_shared_lib_path: SpecConfig.instance.crypt_shared_lib_path, - crypt_shared_required: true, + crypt_shared_lib_required: true, mongocryptd_uri: mongocryptd_uri, mongocryptd_spawn_args: [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27777"] } diff --git a/spec/mongo/crypt/handle_spec.rb b/spec/mongo/crypt/handle_spec.rb index 7cfba8ccbe..be5331e858 100644 --- a/spec/mongo/crypt/handle_spec.rb +++ b/spec/mongo/crypt/handle_spec.rb @@ -20,7 +20,7 @@ schema_map_path: schema_map_path, bypass_query_analysis: bypass_query_analysis, crypt_shared_lib_path: crypt_shared_lib_path, - crypt_shared_required: crypt_shared_required, + crypt_shared_lib_required: crypt_shared_lib_required, explicit_encryption_only: explicit_encryption_only, ) end @@ -41,7 +41,7 @@ nil end - let(:crypt_shared_required) do + let(:crypt_shared_lib_required) do nil end @@ -118,11 +118,11 @@ end end - context 'with crypt_shared_required' do + context 'with crypt_shared_lib_required' do min_server_version '6.0.0' context 'set to true' do - let(:crypt_shared_required) do + let(:crypt_shared_lib_required) do true end From e19bc7e8d50de473031e3954423656cb8ecf050c Mon Sep 17 00:00:00 2001 From: Neil Shweky Date: Thu, 15 Dec 2022 12:01:17 -0500 Subject: [PATCH 096/198] RUBY-2547 Add test that ensures the error returned from being evicted from the WaitQueue is retryable (#2674) * RUBY-2547 add retryable read test * RUBY-2547 add retryable write test * add spec test * restrict topology * Empty-Commit * Apply suggestions from code review * add debug logging * update min server version * add to failpoint * fix min_server_version * fix race condition * RUBY-2547 fix tests * require retry writes * update description * add debug logging * fix comments * fix race condition * remove debug logging * remove more logging * only raise on non load balancer * no need for service_id * fix errors * refactor * Revert "remove more logging" This reverts commit dbab8e867ed57b96036f03e914a1822bd72b7054. * Revert "remove debug logging" This reverts commit 41d8531ab5df59bd9a4c74426a1f40bd6a90b851. * add logging * RUBY-2547 add logging * fix retryable test * attempt to dix race condition * add back logging * wait on ready * fix retryable writes as well + cleanup * remove generation bumped code * get rid of debugging * add back logging * require retry writes * remove logging * Revert "remove logging" This reverts commit 6dce1a7f517ecd12126b92cbff4e86b768539f7a. * Revert "Revert "remove logging"" This reverts commit 7656394bb79a751a9b9980863fb976ce177e38a7. * remove falky test * RUBY-2547 remove debugging --- lib/mongo/server/connection_pool.rb | 4 +- .../retryable_reads_errors_spec.rb | 110 ++++++++++++++++++ .../retryable_writes_errors_spec.rb | 104 ++++++++++++++++- spec/stress/fork_reconnect_stress_spec.rb | 1 - 4 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 spec/integration/retryable_reads_errors_spec.rb diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index 2ac9756524..df924262da 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -551,10 +551,10 @@ def do_clear(options = nil) end # Broadcast here to cause all of the threads waiting on the max - # connecting to decrease to break out of the wait loop and error. + # connecting to break out of the wait loop and error. @max_connecting_cv.broadcast # Broadcast here to cause all of the threads waiting on the pool size - # to decrease to break out of the wait loop and error. + # to break out of the wait loop and error. @size_cv.broadcast end diff --git a/spec/integration/retryable_reads_errors_spec.rb b/spec/integration/retryable_reads_errors_spec.rb new file mode 100644 index 0000000000..aafff54c15 --- /dev/null +++ b/spec/integration/retryable_reads_errors_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'spec_helper' + +describe 'Retryable reads errors tests' do + + let(:client) { authorized_client.with(options.merge(retry_reads: true)) } + + let(:collection) do + client['retryable-reads-error-spec'] + end + + context "PoolClearedError retryability test" do + require_topology :single, :replica_set, :sharded + require_no_multi_mongos + min_server_version '4.2.9' + + let(:options) { { max_pool_size: 1, heartbeat_frequency: 1000 } } + + let(:failpoint) do + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: [ "find" ], + errorCode: 91, + blockConnection: true, + blockTimeMS: 1000 + } + } + end + + let(:subscriber) { Mrss::EventSubscriber.new } + + let(:threads) do + threads = [] + threads << Thread.new do + expect(collection.find(x: 1).first[:x]).to eq(1) + end + threads << Thread.new do + expect(collection.find(x: 1).first[:x]).to eq(1) + end + threads + end + + let(:find_events) do + subscriber.started_events.select { |e| e.command_name == "find" } + end + + let(:cmap_events) do + subscriber.published_events + end + + let(:event_types) do + [ + Mongo::Monitoring::Event::Cmap::ConnectionCheckedOut, + Mongo::Monitoring::Event::Cmap::ConnectionCheckOutFailed, + Mongo::Monitoring::Event::Cmap::PoolCleared, + ] + end + + let(:check_out_results) do + cmap_events.select do |e| + event_types.include?(e.class) + end + end + + before do + collection.insert_one(x: 1) + authorized_client.use(:admin).command(failpoint) + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + client.subscribe(Mongo::Monitoring::CONNECTION_POOL, subscriber) + end + + it "retries on PoolClearedError" do + # After the first find fails, the pool is paused and retry is triggered. + # Now, a race is started between the second find acquiring a connection, + # and the first retrying the read. Now, retry reads cause the cluster to + # be rescanned and the pool to be unpaused, allowing the second checkout + # to succeed (when it should fail). Therefore we want the second find's + # check out to win the race. This gives the check out a little head start. + allow_any_instance_of(Mongo::Server::ConnectionPool).to receive(:ready).and_wrap_original do |m, *args, &block| + ::Utils.wait_for_condition(5) do + # check_out_results should contain: + # - find1 connection check out successful + # - pool cleared + # - find2 connection check out failed + # We wait here for the third event to happen before we ready the pool. + cmap_events.select do |e| + event_types.include?(e.class) + end.length >= 3 + end + m.call(*args, &block) + end + threads.map(&:join) + expect(check_out_results[0]).to be_a(Mongo::Monitoring::Event::Cmap::ConnectionCheckedOut) + expect(check_out_results[1]).to be_a(Mongo::Monitoring::Event::Cmap::PoolCleared) + expect(check_out_results[2]).to be_a(Mongo::Monitoring::Event::Cmap::ConnectionCheckOutFailed) + expect(find_events.length).to eq(3) + end + + after do + authorized_client.use(:admin).command({ + configureFailPoint: "failCommand", + mode: "off", + }) + end + end +end diff --git a/spec/integration/retryable_writes_errors_spec.rb b/spec/integration/retryable_writes_errors_spec.rb index 4f1cffdc76..038196a570 100644 --- a/spec/integration/retryable_writes_errors_spec.rb +++ b/spec/integration/retryable_writes_errors_spec.rb @@ -5,8 +5,10 @@ describe 'Retryable writes errors tests' do + let(:options) { {} } + let(:client) do - authorized_client.with(retry_writes: true) + authorized_client.with(options.merge(retry_writes: true)) end let(:collection) do @@ -34,7 +36,7 @@ context "when encountering a NoWritesPerformed error after an error with a RetryableWriteError label" do require_topology :replica_set - require_fail_command + require_retry_writes min_server_version '4.4' let(:failpoint1) do @@ -89,4 +91,102 @@ end.to raise_error(Mongo::Error::OperationFailure, /\[91\]/) end end + + context "PoolClearedError retryability test" do + require_topology :single, :replica_set, :sharded + require_no_multi_mongos + require_fail_command + require_retry_writes + + let(:options) { { max_pool_size: 1 } } + + let(:failpoint) do + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: [ "insert" ], + errorCode: 91, + blockConnection: true, + blockTimeMS: 1000, + errorLabels: ["RetryableWriteError"] + } + } + end + + let(:subscriber) { Mrss::EventSubscriber.new } + + let(:threads) do + threads = [] + threads << Thread.new do + expect(collection.insert_one(x: 2)).to be_successful + end + threads << Thread.new do + expect(collection.insert_one(x: 2)).to be_successful + end + threads + end + + let(:insert_events) do + subscriber.started_events.select { |e| e.command_name == "insert" } + end + + let(:cmap_events) do + subscriber.published_events + end + + let(:event_types) do + [ + Mongo::Monitoring::Event::Cmap::ConnectionCheckedOut, + Mongo::Monitoring::Event::Cmap::ConnectionCheckOutFailed, + Mongo::Monitoring::Event::Cmap::PoolCleared, + ] + end + + let(:check_out_results) do + cmap_events.select do |e| + event_types.include?(e.class) + end + end + + before do + authorized_client.use(:admin).command(failpoint) + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + client.subscribe(Mongo::Monitoring::CONNECTION_POOL, subscriber) + end + + it "retries on PoolClearedError" do + # After the first insert fails, the pool is paused and retry is triggered. + # Now, a race is started between the second insert acquiring a connection, + # and the first retrying the read. Now, retry reads cause the cluster to + # be rescanned and the pool to be unpaused, allowing the second checkout + # to succeed (when it should fail). Therefore we want the second insert's + # check out to win the race. This gives the check out a little head start. + allow_any_instance_of(Mongo::Server::ConnectionPool).to receive(:ready).and_wrap_original do |m, *args, &block| + ::Utils.wait_for_condition(5) do + # check_out_results should contain: + # - insert1 connection check out successful + # - pool cleared + # - insert2 connection check out failed + # We wait here for the third event to happen before we ready the pool. + cmap_events.select do |e| + event_types.include?(e.class) + end.length >= 3 + end + m.call(*args, &block) + end + threads.map(&:join) + expect(check_out_results[0]).to be_a(Mongo::Monitoring::Event::Cmap::ConnectionCheckedOut) + expect(check_out_results[1]).to be_a(Mongo::Monitoring::Event::Cmap::PoolCleared) + expect(check_out_results[2]).to be_a(Mongo::Monitoring::Event::Cmap::ConnectionCheckOutFailed) + expect(insert_events.length).to eq(3) + end + + after do + authorized_client.use(:admin).command({ + configureFailPoint: "failCommand", + mode: "off", + }) + end + end end diff --git a/spec/stress/fork_reconnect_stress_spec.rb b/spec/stress/fork_reconnect_stress_spec.rb index 81bf83fc4d..adbe1df08d 100644 --- a/spec/stress/fork_reconnect_stress_spec.rb +++ b/spec/stress/fork_reconnect_stress_spec.rb @@ -103,7 +103,6 @@ status.exitstatus.should == 0 end end - end end end From ee89d34aeec8792b36571b05ac49a345975a047e Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Fri, 6 Jan 2023 10:11:52 -0700 Subject: [PATCH 097/198] RUBY-3191 Abstract retryability of errors (#2682) * dry up the references to retryable exception classes (and tidy some things up) * pull the unrelated refactorings (and alphabetize the exception list) --- lib/mongo/retryable.rb | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/mongo/retryable.rb b/lib/mongo/retryable.rb index 38d3dc729d..ef05e69dde 100644 --- a/lib/mongo/retryable.rb +++ b/lib/mongo/retryable.rb @@ -131,7 +131,7 @@ def read_with_retry(session = nil, server_selector = nil, &block) server = select_server(cluster, server_selector, session) begin yield server - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError, Error::OperationFailure => e + rescue *retryable_exceptions, Error::PoolError, Error::OperationFailure => e e.add_note('retries disabled') raise e end @@ -163,7 +163,7 @@ def read_with_retry(session = nil, server_selector = nil, &block) # @since 2.2.6 def read_with_one_retry(options = nil) yield - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError => e + rescue *retryable_exceptions, Error::PoolError => e if e.write_retryable? retry_message = options && options[:retry_message] log_retry(e, message: retry_message) @@ -247,7 +247,7 @@ def write_with_retry(write_concern, ending_transaction: false, context:, &block) # it later for the retry as well. yield(connection, txn_num, context.dup) end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError, Auth::Unauthorized => e + rescue *retryable_exceptions, Error::PoolError, Auth::Unauthorized => e e.add_note('modern retry') e.add_note("attempt 1") if !e.label?('RetryableWriteError') @@ -308,7 +308,7 @@ def nro_write_with_retry(write_concern, context:, &block) server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield connection, nil, context end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError, Error::OperationFailure => e + rescue *retryable_exceptions, Error::PoolError, Error::OperationFailure => e e.add_note('retries disabled') raise e end @@ -371,7 +371,7 @@ def modern_read_with_retry(session, server_selector, &block) server = select_server(cluster, server_selector, session) begin yield server - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished => e + rescue *retryable_exceptions => e e.add_note('modern retry') e.add_note("attempt 1") if session.in_transaction? @@ -394,7 +394,7 @@ def legacy_read_with_retry(session, server_selector) begin attempt += 1 yield server - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished => e + rescue *retryable_exceptions => e e.add_note('legacy retry') e.add_note("attempt #{attempt}") if attempt > client.max_read_retries || (session && session.in_transaction?) @@ -449,7 +449,7 @@ def retry_read(original_error, server_selector, session, &block) begin yield server, true - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished => e + rescue *retryable_exceptions => e e.add_note('modern retry') e.add_note("attempt 2") raise e @@ -496,7 +496,7 @@ def retry_write(original_error, txn_num, context:, &block) server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield(connection, txn_num, context) end - rescue Error::SocketError, Error::SocketTimeoutError, Error::ServerNotUsable, Error::ConnectionPerished, Error::PoolError => e + rescue *retryable_exceptions, Error::PoolError => e if e.write_retryable? e.add_note('modern retry') e.add_note('attempt 2') @@ -548,5 +548,15 @@ def raise_unsupported_error(e) new_error.set_backtrace(e.backtrace) raise new_error end + + # Returns a list of exception classes that are generally retryable. + def retryable_exceptions + [ + Error::ConnectionPerished, + Error::ServerNotUsable, + Error::SocketError, + Error::SocketTimeoutError + ].freeze + end end end From bdcac8471dccefdb273eb8bf064349eaa8295578 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Sun, 8 Jan 2023 15:30:21 +0100 Subject: [PATCH 098/198] RUBY-2732 Add service account support (#2681) --- .evergreen/auth_aws | 1 + .evergreen/aws | 102 ++++++++++------- .evergreen/config.yml | 16 ++- .evergreen/config/axes.yml.erb | 4 + .evergreen/config/common.yml.erb | 10 +- .evergreen/config/standard.yml.erb | 2 +- .evergreen/run-tests-aws-auth.sh | 50 +++++++-- .evergreen/run-tests.sh | 4 + docs/reference/authentication.txt | 13 +++ docs/release-notes.txt | 1 + lib/mongo/auth/aws/credentials_retriever.rb | 103 +++++++++++++++++- spec/integration/aws_auth_request_spec.rb | 2 +- .../aws_credentials_retriever_spec.rb | 26 +++++ .../client_construction_aws_auth_spec.rb | 11 +- spec/support/aws_utils/orchestrator.rb | 13 +++ 15 files changed, 297 insertions(+), 61 deletions(-) create mode 120000 .evergreen/auth_aws diff --git a/.evergreen/auth_aws b/.evergreen/auth_aws new file mode 120000 index 0000000000..60a22acdc3 --- /dev/null +++ b/.evergreen/auth_aws @@ -0,0 +1 @@ +../.mod/drivers-evergreen-tools/.evergreen/auth_aws \ No newline at end of file diff --git a/.evergreen/aws b/.evergreen/aws index fe937b5bbf..0fc0ce616b 100755 --- a/.evergreen/aws +++ b/.evergreen/aws @@ -5,36 +5,32 @@ $: << File.join(File.dirname(__FILE__), '../spec') require 'support/aws_utils' require 'optparse' -options = {} -OptionParser.new do |opts| - opts.banner = "Usage: aws [options] command ..." +def parse_options + options = {} + OptionParser.new do |opts| + opts.banner = "Usage: aws [options] command ..." - opts.on("-a", "--access-key-id=ID", "AWS access key ID") do |v| - options[:access_key_id] = v - end - - opts.on("-s", "--secret-access-key=SECRET", "AWS secret access key") do |v| - options[:secret_access_key] = v - end + opts.on("-a", "--access-key-id=ID", "AWS access key ID") do |v| + options[:access_key_id] = v + end - opts.on("-r", "--region=REGION", "AWS region") do |v| - options[:region] = v - end + opts.on("-s", "--secret-access-key=SECRET", "AWS secret access key") do |v| + options[:secret_access_key] = v + end - # launch-ecs options - opts.on('--ec2', 'Use EC2 launch type instead of Fargate') do |v| - options[:ec2] = true - end -end.parse! + opts.on("-r", "--region=REGION", "AWS region") do |v| + options[:region] = v + end -case cmd = ARGV.shift -when 'setup-resources' - AwsUtils::Provisioner.new.setup_aws_auth_resources -when 'reset-keys' - AwsUtils::Provisioner.new.reset_keys -when 'assume-role' - arn = ARGV.shift + # launch-ecs options + opts.on('--ec2', 'Use EC2 launch type instead of Fargate') do |v| + options[:ec2] = true + end + end.parse! + options +end +def assume_role(arn, options) orchestrator = AwsUtils::Orchestrator.new(**options) if arn.nil? arn = AwsUtils::Inspector.new(**options).assume_role_arn @@ -43,39 +39,36 @@ when 'assume-role' puts "AWS_ACCESS_KEY_ID=#{credentials.access_key_id}" puts "AWS_SECRET_ACCESS_KEY=#{credentials.secret_access_key}" puts "AWS_SESSION_TOKEN=#{credentials.session_token}" - #puts "EXPIRATION=#{credentials.expiration.utc.strftime('%Y%m%dT%H%M%SZ')}" puts -when 'set-instance-profile' - instance_id = ARGV.shift +end + +def set_instance_profile(instance_id, options) unless instance_id raise 'Instance id is required' end - orchestrator = AwsUtils::Orchestrator.new(**options) orchestrator.set_instance_profile(instance_id) -when 'clear-instance-profile' - instance_id = ARGV.shift +end + +def clear_instance_profile(instance_id, options) unless instance_id raise 'Instance id is required' end orchestrator = AwsUtils::Orchestrator.new(**options) orchestrator.clear_instance_profile(instance_id) -when 'key-pairs' - AwsUtils::Inspector.new(**options).list_key_pairs -when 'launch-ec2' - public_key_path, = ARGV +end + +def launch_ec2(public_key_path, options) unless public_key_path raise "Public key path must be given" end orchestrator = AwsUtils::Orchestrator.new(**options) orchestrator.provision_auth_ec2_instance(public_key_path: public_key_path) -when 'stop-ec2' - orchestrator = AwsUtils::Orchestrator.new(**options) - orchestrator.terminate_auth_ec2_instance -when 'launch-ecs' - public_key_path, = ARGV +end + +def launch_ecs(public_key_path, options) unless public_key_path raise "Public key path must be given" end @@ -84,6 +77,35 @@ when 'launch-ecs' orchestrator.provision_auth_ecs_task( public_key_path: public_key_path, ) +end + +options = parse_options + +case cmd = ARGV.shift +when 'setup-resources' + AwsUtils::Provisioner.new.setup_aws_auth_resources +when 'reset-keys' + AwsUtils::Provisioner.new.reset_keys +when 'assume-role' + arn = ARGV.shift + assume_role(arn, options) +when 'set-instance-profile' + instance_id = ARGV.shift + set_instance_profile(instance_id, options) +when 'clear-instance-profile' + instance_id = ARGV.shift + clear_instance_profile(instance_id, options) +when 'key-pairs' + AwsUtils::Inspector.new(**options).list_key_pairs +when 'launch-ec2' + public_key_path, = ARGV + launch_ec2(public_key_path, options) +when 'stop-ec2' + orchestrator = AwsUtils::Orchestrator.new(**options) + orchestrator.terminate_auth_ec2_instance +when 'launch-ecs' + public_key_path, = ARGV + launch_ecs(public_key_path, options) when 'stop-ecs' orchestrator = AwsUtils::Orchestrator.new(**options) orchestrator.terminate_auth_ecs_task diff --git a/.evergreen/config.yml b/.evergreen/config.yml index c889dd7300..1b9256be3c 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -142,9 +142,6 @@ functions: export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" - export AWS_ACCESS_KEY_ID="${fle_aws_key}" - export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" - export AWS_DEFAULT_REGION="${fle_aws_region}" # Needed for generating temporary aws credentials. if [ -n "${FLE}" ]; @@ -197,6 +194,13 @@ functions: IAM_AUTH_ECS_SUBNET_A="${iam_auth_ecs_subnet_a}" IAM_AUTH_ECS_SUBNET_B="${iam_auth_ecs_subnet_b}" IAM_AUTH_ECS_TASK_DEFINITION="${iam_auth_ecs_task_definition}" + + IAM_WEB_IDENTITY_ISSUER="${iam_web_identity_issuer}" + IAM_WEB_IDENTITY_JWKS_URI="${iam_web_identity_jwks_uri}" + IAM_WEB_IDENTITY_RSA_KEY="${iam_web_identity_rsa_key}" + IAM_WEB_IDENTITY_TOKEN_FILE="${iam_web_identity_token_file}" + IAM_AUTH_ASSUME_WEB_ROLE_NAME="${iam_auth_assume_web_role_name}" + EOT "export FLE credentials": @@ -712,6 +716,10 @@ axes: display_name: AWS Auth ECS Task variables: AUTH: aws-ecs + - id: aws-web-identity + display_name: AWS Auth Web Identity Task + variables: + AUTH: aws-web-identity - id: "ruby" display_name: Ruby Version @@ -1524,7 +1532,7 @@ buildvariants: - matrix_name: aws-auth-temporary matrix_spec: - auth-and-ssl: [aws-assume-role, aws-ec2, aws-ecs] + auth-and-ssl: [aws-assume-role, aws-ec2, aws-ecs, aws-web-identity] ruby: [ruby-3.1] topology: standalone mongodb-version: '4.4' diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index 961bfdc9c4..8abaa90043 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -125,6 +125,10 @@ axes: display_name: AWS Auth ECS Task variables: AUTH: aws-ecs + - id: aws-web-identity + display_name: AWS Auth Web Identity Task + variables: + AUTH: aws-web-identity - id: "ruby" display_name: Ruby Version diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index d377073b60..12a3c9f0e1 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -140,9 +140,6 @@ functions: export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" - export AWS_ACCESS_KEY_ID="${fle_aws_key}" - export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" - export AWS_DEFAULT_REGION="${fle_aws_region}" # Needed for generating temporary aws credentials. if [ -n "${FLE}" ]; @@ -195,6 +192,13 @@ functions: IAM_AUTH_ECS_SUBNET_A="${iam_auth_ecs_subnet_a}" IAM_AUTH_ECS_SUBNET_B="${iam_auth_ecs_subnet_b}" IAM_AUTH_ECS_TASK_DEFINITION="${iam_auth_ecs_task_definition}" + + IAM_WEB_IDENTITY_ISSUER="${iam_web_identity_issuer}" + IAM_WEB_IDENTITY_JWKS_URI="${iam_web_identity_jwks_uri}" + IAM_WEB_IDENTITY_RSA_KEY="${iam_web_identity_rsa_key}" + IAM_WEB_IDENTITY_TOKEN_FILE="${iam_web_identity_token_file}" + IAM_AUTH_ASSUME_WEB_ROLE_NAME="${iam_auth_assume_web_role_name}" + EOT "export FLE credentials": diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index c5dbcd71bb..ff7e8378fe 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -541,7 +541,7 @@ buildvariants: - matrix_name: aws-auth-temporary matrix_spec: - auth-and-ssl: [aws-assume-role, aws-ec2, aws-ecs] + auth-and-ssl: [aws-assume-role, aws-ec2, aws-ecs, aws-web-identity] ruby: [ruby-3.1] topology: standalone mongodb-version: '4.4' diff --git a/.evergreen/run-tests-aws-auth.sh b/.evergreen/run-tests-aws-auth.sh index b2f7d52a0e..1813400700 100755 --- a/.evergreen/run-tests-aws-auth.sh +++ b/.evergreen/run-tests-aws-auth.sh @@ -27,16 +27,16 @@ case "$AUTH" in export MONGO_RUBY_DRIVER_AWS_AUTH_SECRET_ACCESS_KEY="`get_var IAM_AUTH_ECS_SECRET_ACCESS_KEY`" export MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN="`get_var IAM_AUTH_ECS_ACCOUNT_ARN`" ;; - + aws-assume-role) export MONGO_RUBY_DRIVER_AWS_AUTH_ACCESS_KEY_ID="`get_var IAM_AUTH_ASSUME_AWS_ACCOUNT`" export MONGO_RUBY_DRIVER_AWS_AUTH_SECRET_ACCESS_KEY="`get_var IAM_AUTH_ASSUME_AWS_SECRET_ACCESS_KEY`" - + # This is the ARN provided in the AssumeRole request. It is different # from the ARN that the credentials returned by the AssumeRole request # resolve to. export MONGO_RUBY_DRIVER_AWS_AUTH_ASSUME_ROLE_ARN="`get_var IAM_AUTH_ASSUME_ROLE_NAME`" - + # This is the ARN that the credentials obtained by the AssumeRole # request resolve to. It is hardcoded in # https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_aws/aws_e2e_assume_role.js @@ -45,7 +45,7 @@ case "$AUTH" in # obtained from STS. See https://jira.mongodb.org/browse/RUBY-2425. export MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN="arn:aws:sts::557821124784:assumed-role/authtest_user_assume_role/*" ;; - + aws-ec2) export MONGO_RUBY_DRIVER_AWS_AUTH_ACCESS_KEY_ID="`get_var IAM_AUTH_EC2_INSTANCE_ACCOUNT`" export MONGO_RUBY_DRIVER_AWS_AUTH_SECRET_ACCESS_KEY="`get_var IAM_AUTH_EC2_INSTANCE_SECRET_ACCESS_KEY`" @@ -53,7 +53,7 @@ case "$AUTH" in # Region is not specified in Evergreen but can be specified when # testing locally. export MONGO_RUBY_DRIVER_AWS_AUTH_REGION=${MONGO_RUBY_DRIVER_AWS_AUTH_REGION:=us-east-1} - + if test -z "$MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN"; then # This is the ARN that the credentials obtained via EC2 instance metadata # resolve to. It is hardcoded in @@ -64,10 +64,10 @@ case "$AUTH" in # variable manually. export MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN="arn:aws:sts::557821124784:assumed-role/authtest_instance_profile_role/*" fi - + export TEST_CMD=${TEST_CMD:=rspec spec/integration/aws*spec.rb spec/integration/client_construction_aws*spec.rb} ;; - + aws-ecs) export MONGO_RUBY_DRIVER_AWS_AUTH_ACCESS_KEY_ID="`get_var IAM_AUTH_ECS_ACCOUNT`" export MONGO_RUBY_DRIVER_AWS_AUTH_SECRET_ACCESS_KEY="`get_var IAM_AUTH_ECS_SECRET_ACCESS_KEY`" @@ -78,7 +78,7 @@ case "$AUTH" in # Region is not specified in Evergreen but can be specified when # testing locally. export MONGO_RUBY_DRIVER_AWS_AUTH_REGION=${MONGO_RUBY_DRIVER_AWS_AUTH_REGION:=us-east-1} - + if test -z "$MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN"; then # This is the ARN that the credentials obtained via ECS task metadata # resolve to. It is hardcoded in @@ -89,11 +89,41 @@ case "$AUTH" in # variable manually. export MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN="arn:aws:sts::557821124784:assumed-role/ecsTaskExecutionRole/*" fi - + export TEST_CMD=${TEST_CMD:=rspec spec/integration/aws*spec.rb spec/integration/client_construction_aws*spec.rb} exec `dirname $0`/run-tests-ecs.sh ;; - + + aws-web-identity) + cd `dirname "$0"`/auth_aws + + . ./activate_venv.sh + export AWS_ACCESS_KEY_ID="`get_var IAM_AUTH_EC2_INSTANCE_ACCOUNT`" + export AWS_SECRET_ACCESS_KEY="`get_var IAM_AUTH_EC2_INSTANCE_SECRET_ACCESS_KEY`" + python -u lib/aws_unassign_instance_profile.py + unset AWS_ACCESS_KEY_ID + unset AWS_SECRET_ACCESS_KEY + + export IDP_ISSUER="`get_var IAM_WEB_IDENTITY_ISSUER`" + export IDP_JWKS_URI="`get_var IAM_WEB_IDENTITY_JWKS_URI`" + export IDP_RSA_KEY="`get_var IAM_WEB_IDENTITY_RSA_KEY`" + export AWS_WEB_IDENTITY_TOKEN_FILE="`get_var IAM_WEB_IDENTITY_TOKEN_FILE`" + python -u lib/aws_handle_oidc_creds.py token + unset IDP_ISSUER + unset IDP_JWKS_URI + unset IDP_RSA_KEY + + cd - + export MONGO_RUBY_DRIVER_AWS_AUTH_ACCESS_KEY_ID="`get_var IAM_AUTH_EC2_INSTANCE_ACCOUNT`" + export MONGO_RUBY_DRIVER_AWS_AUTH_SECRET_ACCESS_KEY="`get_var IAM_AUTH_EC2_INSTANCE_SECRET_ACCESS_KEY`" + export AWS_WEB_IDENTITY_TOKEN_FILE="`get_var IAM_WEB_IDENTITY_TOKEN_FILE`" + export AWS_ROLE_ARN="`get_var IAM_AUTH_ASSUME_WEB_ROLE_NAME`" + export MONGO_RUBY_DRIVER_AWS_AUTH_ASSUME_ROLE_ARN="`get_var IAM_AUTH_ASSUME_WEB_ROLE_NAME`" + export MONGO_RUBY_DRIVER_AWS_AUTH_USER_ARN="arn:aws:sts::857654397073:assumed-role/webIdentityTestRole/*" + + export TEST_CMD=${TEST_CMD:=rspec spec/integration/aws*spec.rb spec/integration/client_construction_aws*spec.rb} + ;; + *) echo "Unknown AUTH value $AUTH" 1>&2 exit 1 diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index a359236cec..b9d3ee6ecc 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -153,6 +153,10 @@ elif test "$AUTH" = aws-ecs; then eval export `strings /proc/1/environ |grep ^AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` fi + ruby -Ilib -I.evergreen/lib -rserver_setup -e ServerSetup.new.setup_aws_auth +elif test "$AUTH" = aws-web-identity; then + clear_instance_profile + ruby -Ilib -I.evergreen/lib -rserver_setup -e ServerSetup.new.setup_aws_auth elif test "$AUTH" = kerberos; then export MONGO_RUBY_DRIVER_KERBEROS=1 diff --git a/docs/reference/authentication.txt b/docs/reference/authentication.txt index acf9ab7f72..0c30c7056d 100644 --- a/docs/reference/authentication.txt +++ b/docs/reference/authentication.txt @@ -280,6 +280,7 @@ Temporary credentials are used with: - `EC2 instance roles `_. - `ECS task roles `_. - `AWS Lambda environment `_. +- `IAM roles for service accounts `_. The Ruby driver allows providing both regular and temporary credentials explicitly as Ruby options or URI options. If credentials are not explicitly @@ -362,6 +363,18 @@ the specified order: a variety of AWS-related libraries and tools such as the official AWS Ruby SDK and the AWS CLI. They are also defined when running in an AWS Lambda environment. +- The AWS STS `AssumeRoleWithWebIdentity action + `_. + This returns credentials associated with the service account token. This mechanism + requires the following environment variables to be set: + + - ``AWS_WEB_IDENTITY_TOKEN_FILE`` - path to a file containing the service + account token. + - ``AWS_ROLE_ARN`` - the Amazon Resource Name (ARN) of the role that the + caller is assuming. + - ``AWS_ROLE_SESSION_NAME`` (optional) - An identifier for the assumed role + session. If omitted, a random name will be generated by the driver. + - The AWS `ECS task metadata endpoint `_. This returns credentials associated with the ECS task role assigned to diff --git a/docs/release-notes.txt b/docs/release-notes.txt index e4b853da2b..d3ebfba7ae 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -29,6 +29,7 @@ This release includes the following new features: client side encryption. - Added support for automatic GCP credentials retrieval when Google Cloud Key Management is used for client side encryption. +- Added support for AWS IAM Roles for service accounts, EKS in particular. .. _release-notes-2.18: diff --git a/lib/mongo/auth/aws/credentials_retriever.rb b/lib/mongo/auth/aws/credentials_retriever.rb index 4943665bb7..b52d0b6874 100644 --- a/lib/mongo/auth/aws/credentials_retriever.rb +++ b/lib/mongo/auth/aws/credentials_retriever.rb @@ -24,7 +24,7 @@ class Aws # @api private class CredentialsNotFound < Mongo::Error::AuthError def initialize - super("Could not locate AWS credentials (checked Client URI and Ruby options, environment variables, ECS and EC2 metadata)") + super("Could not locate AWS credentials (checked Client URI and Ruby options, environment variables, ECS and EC2 metadata, and Web Identity)") end end @@ -36,6 +36,7 @@ def initialize # - AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN # environment variables (commonly used by AWS SDKs and various tools, # as well as AWS Lambda) + # - AssumeRoleWithWebIdentity API call # - EC2 metadata endpoint # - ECS metadata endpoint # @@ -95,6 +96,12 @@ def credentials return credentials end + credentials = web_identity_credentials + + if credentials && credentials_valid?(credentials, 'Web identity token') + return credentials + end + credentials = ecs_metadata_credentials if credentials && credentials_valid?(credentials, 'ECS task metadata') @@ -188,6 +195,100 @@ def ecs_metadata_credentials return nil end + # Returns credentials associated with web identity token that is + # stored in a file. This authentication mechanism is used to authenticate + # inside EKS. See https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html + # for further details. + # + # @return [ Auth::Aws::Credentials | nil ] A set of credentials, or nil + # if retrieval failed. + def web_identity_credentials + web_identity_token, role_arn, role_session_name = prepare_web_identity_inputs + return nil if web_identity_token.nil? + response = request_web_identity_credentials( + web_identity_token, role_arn, role_session_name + ) + return if response.nil? + credentials_from_web_identity_response(response) + end + + # Returns inputs for the AssumeRoleWithWebIdentity AWS API call. + # + # @return [ Array ] Web + # identity token, role arn, and role session name. + def prepare_web_identity_inputs + token_file = ENV['AWS_WEB_IDENTITY_TOKEN_FILE'] + role_arn = ENV['AWS_ROLE_ARN'] + if token_file.nil? || role_arn.nil? + return nil + end + web_identity_token = File.open(token_file).read + role_session_name = ENV['AWS_ROLE_SESSION_NAME'] + if role_session_name.nil? + role_session_name = "ruby-app-#{SecureRandom.alphanumeric(50)}" + end + [web_identity_token, role_arn, role_session_name] + rescue Errno::ENOENT, IOError, SystemCallError + nil + end + + # Calls AssumeRoleWithWebIdentity to obtain credentials for the + # given web identity token. + # + # @param [ String ] token The OAuth 2.0 access token or + # OpenID Connect ID token that is provided by the identity provider. + # @param [ String ] role_arn The Amazon Resource Name (ARN) of the role + # that the caller is assuming. + # @param [ String ] role_session_name An identifier for the assumed + # role session. + # + # @return [ Net::HTTPResponse | nil ] AWS API response if successful, + # otherwise nil. + def request_web_identity_credentials(token, role_arn, role_session_name) + uri = URI('https://sts.amazonaws.com/') + params = { + 'Action' => 'AssumeRoleWithWebIdentity', + 'Version' => '2011-06-15', + 'RoleArn' => role_arn, + 'WebIdentityToken' => token, + 'RoleSessionName' => role_session_name + } + uri.query = ::URI.encode_www_form(params) + req = Net::HTTP::Post.new(uri) + req['Accept'] = 'application/json' + resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |https| + https.request(req) + end + if resp.code != '200' + return nil + end + resp + rescue Errno::ENOENT, IOError, SystemCallError + nil + end + + # Extracts credentials from AssumeRoleWithWebIdentity response. + # + # @param [ Net::HTTPResponse ] response AssumeRoleWithWebIdentity + # call response. + # + # @return [ Auth::Aws::Credentials | nil ] A set of credentials, or nil + # if response parsing failed. + def credentials_from_web_identity_response(response) + payload = JSON.parse(response.body).dig( + 'AssumeRoleWithWebIdentityResponse', + 'AssumeRoleWithWebIdentityResult', + 'Credentials' + ) || {} + Credentials.new( + payload['AccessKeyId'], + payload['SecretAccessKey'], + payload['SessionToken'], + ) + rescue JSON::ParserError + nil + end + def http_get(http, uri, metadata_token) req = Net::HTTP::Get.new(uri, {'x-aws-ec2-metadata-token' => metadata_token}) diff --git a/spec/integration/aws_auth_request_spec.rb b/spec/integration/aws_auth_request_spec.rb index 25fb77764d..13241d97d1 100644 --- a/spec/integration/aws_auth_request_spec.rb +++ b/spec/integration/aws_auth_request_spec.rb @@ -8,7 +8,7 @@ require_aws_auth before(:all) do - if ENV['AUTH'] =~ /aws-(ec2|ecs)/ + if ENV['AUTH'] =~ /aws-(ec2|ecs|web)/ skip "This test requires explicit credentials to be provided" end end diff --git a/spec/integration/aws_credentials_retriever_spec.rb b/spec/integration/aws_credentials_retriever_spec.rb index f9d66cb5a0..b4b74796e6 100644 --- a/spec/integration/aws_credentials_retriever_spec.rb +++ b/spec/integration/aws_credentials_retriever_spec.rb @@ -102,5 +102,31 @@ it_behaves_like 'retrieves the credentials' end + + context 'web identity' do + before(:all) do + unless ENV['AUTH'] == 'aws-web-identity' + skip "Set AUTH=aws-web-identity in environment to run Wed identity tests" + end + end + + context 'with AWS_ROLE_SESSION_NAME' do + before do + stub_const('ENV', ENV.to_hash.merge('AWS_ROLE_SESSION_NAME' => 'mongo-ruby-driver-test-app')) + end + + it_behaves_like 'retrieves the credentials' + end + + context 'without AWS_ROLE_SESSION_NAME' do + before do + env = ENV.to_hash.dup + env.delete('AWS_ROLE_SESSION_NAME') + stub_const('ENV', env) + end + + it_behaves_like 'retrieves the credentials' + end + end end end diff --git a/spec/integration/client_construction_aws_auth_spec.rb b/spec/integration/client_construction_aws_auth_spec.rb index e1a1da069b..75c2979e31 100644 --- a/spec/integration/client_construction_aws_auth_spec.rb +++ b/spec/integration/client_construction_aws_auth_spec.rb @@ -163,7 +163,7 @@ end context 'credentials specified via instance/task metadata' do - require_auth 'aws-ec2', 'aws-ecs' + require_auth 'aws-ec2', 'aws-ecs', 'aws-web-identity' before(:all) do # No explicit credentials are expected in the tested configurations @@ -189,6 +189,15 @@ authenticated_user_name.should =~ /^arn:aws:sts:.*assumed-role.*ecstaskexecutionrole/i end end + + context 'when using web identity' do + require_auth 'aws-web-identity' + + it 'uses the expected user' do + puts "Authenticated as #{authenticated_user_name}" + authenticated_user_name.should =~ /^arn:aws:sts:.*assumed-role.*webIdentityTestRole/i + end + end end end diff --git a/spec/support/aws_utils/orchestrator.rb b/spec/support/aws_utils/orchestrator.rb index acc095142f..7ad595cbbb 100644 --- a/spec/support/aws_utils/orchestrator.rb +++ b/spec/support/aws_utils/orchestrator.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true # encoding: utf-8 +require 'securerandom' + module AwsUtils class Orchestrator < Base @@ -13,6 +15,17 @@ def assume_role(role_arn) resp.credentials end + def assume_role_with_web_identity(role_arn, token_file) + token = File.open(token_file).read + resp = sts_client.assume_role_with_web_identity( + role_arn: role_arn, + role_session_name: SecureRandom.uuid, + web_identity_token: token, + duration_seconds: 900 + ) + resp.credentials + end + def set_instance_profile(instance_id, instance_profile_name: AWS_AUTH_INSTANCE_PROFILE_NAME, instance_profile_arn: nil From 9aab22aeeba5a17bde691fed30673c8cd2e3dd80 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Fri, 13 Jan 2023 09:51:57 -0700 Subject: [PATCH 099/198] Mongo::Retryable refactoring (#2683) * dry up the references to retryable exception classes (and tidy some things up) * pull the unrelated refactorings (and alphabetize the exception list) * some refactoring to separate the concerns in Mongo::Retryable * a bit more comments, & some tidying * add newlines at EOF * clarify documentation (copy/paste flubs) * fix failing tests * it's a code smell, but we need to appease the expectations of the tests * retryable_exceptions has been refactored to Retryable::BaseWorker * doc tweaks per Dmitry's suggestions * make @option closer to @param for clarity --- lib/mongo/error/notable.rb | 7 + lib/mongo/retryable.rb | 549 +----------------- lib/mongo/retryable/base_worker.rb | 91 +++ lib/mongo/retryable/read_worker.rb | 296 ++++++++++ lib/mongo/retryable/write_worker.rb | 364 ++++++++++++ .../retryable_writes_errors_spec.rb | 2 +- spec/mongo/retryable_spec.rb | 6 +- 7 files changed, 793 insertions(+), 522 deletions(-) create mode 100644 lib/mongo/retryable/base_worker.rb create mode 100644 lib/mongo/retryable/read_worker.rb create mode 100644 lib/mongo/retryable/write_worker.rb diff --git a/lib/mongo/error/notable.rb b/lib/mongo/error/notable.rb index a57d4d333b..3bde21a655 100644 --- a/lib/mongo/error/notable.rb +++ b/lib/mongo/error/notable.rb @@ -57,6 +57,13 @@ def add_note(note) @notes << note end + # Allows multiple notes to be added in a single call, for convenience. + # + # @api private + def add_notes(*notes) + notes.each { |note| add_note(note) } + end + # Returns connection pool generation for the connection on which the # error occurred. # diff --git a/lib/mongo/retryable.rb b/lib/mongo/retryable.rb index ef05e69dde..3da64c1d79 100644 --- a/lib/mongo/retryable.rb +++ b/lib/mongo/retryable.rb @@ -15,548 +15,59 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'mongo/retryable/read_worker' +require 'mongo/retryable/write_worker' + module Mongo # Defines basic behavior around retrying operations. # # @since 2.1.0 module Retryable + extend Forwardable - # Execute a read operation returning a cursor with retrying. - # - # This method performs server selection for the specified server selector - # and yields to the provided block, which should execute the initial - # query operation and return its result. The block will be passed the - # server selected for the operation. If the block raises an exception, - # and this exception corresponds to a read retryable error, and read - # retries are enabled for the client, this method will perform server - # selection again and yield to the block again (with potentially a - # different server). If the block returns successfully, the result - # of the block (which should be a Mongo::Operation::Result) is used to - # construct a Mongo::Cursor object for the result set. The cursor - # is then returned. - # - # If modern retry reads are on (which is the default), the initial read - # operation will be retried once. If legacy retry reads are on, the - # initial read operation will be retried zero or more times depending - # on the :max_read_retries client setting, the default for which is 1. - # To disable read retries, turn off modern read retries by setting - # retry_reads: false and set :max_read_retries to 0 on the client. - # - # @api private - # - # @example Execute a read returning a cursor. - # cursor = read_with_retry_cursor(session, server_selector, view) do |server| - # # return a Mongo::Operation::Result - # ... - # end - # - # @param [ Mongo::Session ] session The session that the operation is being - # run on. - # @param [ Mongo::ServerSelector::Selectable ] server_selector Server - # selector for the operation. - # @param [ CollectionView ] view The +CollectionView+ defining the query. - # @param [ Proc ] block The block to execute. - # - # @return [ Cursor ] The cursor for the result set. - def read_with_retry_cursor(session, server_selector, view, &block) - read_with_retry(session, server_selector) do |server| - result = yield server + # Delegate the public read_with_retry methods to the read_worker + def_delegators :read_worker, + :read_with_retry_cursor, + :read_with_retry, + :read_with_one_retry - # RUBY-2367: This will be updated to allow the query cache to - # cache cursors with multi-batch results. - if QueryCache.enabled? && !view.collection.system_collection? - CachingCursor.new(view, result, server, session: session) - else - Cursor.new(view, result, server, session: session) - end - end - end - - # Execute a read operation with retrying. - # - # This method performs server selection for the specified server selector - # and yields to the provided block, which should execute the initial - # query operation and return its result. The block will be passed the - # server selected for the operation. If the block raises an exception, - # and this exception corresponds to a read retryable error, and read - # retries are enabled for the client, this method will perform server - # selection again and yield to the block again (with potentially a - # different server). If the block returns successfully, the result - # of the block is returned. - # - # If modern retry reads are on (which is the default), the initial read - # operation will be retried once. If legacy retry reads are on, the - # initial read operation will be retried zero or more times depending - # on the :max_read_retries client setting, the default for which is 1. - # To disable read retries, turn off modern read retries by setting - # retry_reads: false and set :max_read_retries to 0 on the client. - # - # @api private - # - # @example Execute the read. - # read_with_retry(session, server_selector) do |server| - # ... - # end - # - # @param [ Mongo::Session ] session The session that the operation is being - # run on. - # @param [ Mongo::ServerSelector::Selectable ] server_selector Server - # selector for the operation. - # @param [ Proc ] block The block to execute. - # - # @return [ Result ] The result of the operation. - def read_with_retry(session = nil, server_selector = nil, &block) - if session.nil? && server_selector.nil? - # Older versions of Mongoid call read_with_retry without arguments. - # This is already not correct in a MongoDB 3.6+ environment with - # sessions. For compatibility we emulate the legacy driver behavior - # here but upgrading Mongoid is strongly recommended. - unless $_mongo_read_with_retry_warned - $_mongo_read_with_retry_warned = true - Logger.logger.warn("Legacy read_with_retry invocation - please update the application and/or its dependencies") - end - # Since we don't have a session, we cannot use the modern read retries. - # And we need to select a server but we don't have a server selector. - # Use PrimaryPreferred which will work as long as there is a data - # bearing node in the cluster; the block may select a different server - # which is fine. - server_selector = ServerSelector.get(mode: :primary_preferred) - legacy_read_with_retry(nil, server_selector, &block) - elsif session && session.retry_reads? - modern_read_with_retry(session, server_selector, &block) - elsif client.max_read_retries > 0 - legacy_read_with_retry(session, server_selector, &block) - else - server = select_server(cluster, server_selector, session) - begin - yield server - rescue *retryable_exceptions, Error::PoolError, Error::OperationFailure => e - e.add_note('retries disabled') - raise e - end - end - end + # Delegate the public write_with_retry methods to the write_worker + def_delegators :write_worker, + :write_with_retry, + :nro_write_with_retry - # Execute a read operation with a single retry on network errors. + # This is a separate method to make it possible for the test suite to + # assert that server selection is performed during retry attempts. # - # This method is used by the driver for some of the internal housekeeping - # operations. Application-requested reads should use read_with_retry - # rather than this method. + # This is a public method so that it can be accessed via the read and + # write worker delegates, as needed. # # @api private # - # @example Execute the read. - # read_with_one_retry do - # ... - # end - # - # @note This only retries read operations on socket errors. - # - # @param [ Hash ] options Options. - # @yield Calls the provided block with no arguments - # - # @option options [ String ] :retry_message Message to log when retrying. - # - # @return [ Result ] The result of the operation. - # - # @since 2.2.6 - def read_with_one_retry(options = nil) - yield - rescue *retryable_exceptions, Error::PoolError => e - if e.write_retryable? - retry_message = options && options[:retry_message] - log_retry(e, message: retry_message) - yield - else - raise e - end + # @return [ Mongo::Server ] A server matching the server preference. + def select_server(cluster, server_selector, session) + server_selector.select_server(cluster, nil, session) end - # Implements write retrying functionality by yielding to the passed - # block one or more times. - # - # If the session is provided (hence, the deployment supports sessions), - # and modern retry writes are enabled on the client, the modern retry - # logic is invoked. Otherwise the legacy retry logic is invoked. - # - # If ending_transaction parameter is true, indicating that a transaction - # is being committed or aborted, the operation is executed exactly once. - # Note that, since transactions require sessions, this method will raise - # ArgumentError if ending_transaction is true and session is nil. + # Returns the read worker for handling retryable reads. # # @api private # - # @example Execute the write. - # write_with_retry do - # ... - # end - # - # @note This only retries operations on not master failures, since it is - # the only case we can be sure a partial write did not already occur. - # - # @param [ nil | Hash | WriteConcern::Base ] write_concern The write concern. - # @param [ true | false ] ending_transaction True if the write operation is - # abortTransaction or commitTransaction, false otherwise. - # @param [ Context ] context The context for the operation. - # @param [ Proc ] block The block to execute. - # - # @yieldparam [ Connection ] connection The connection through which the - # write should be sent. - # @yieldparam [ Integer ] txn_num Transaction number (NOT the ACID kind). - # @yieldparam [ Operation::Context ] context The operation context. - # - # @return [ Result ] The result of the operation. - # - # @since 2.1.0 - def write_with_retry(write_concern, ending_transaction: false, context:, &block) - session = context.session - - if ending_transaction && !session - raise ArgumentError, 'Cannot end a transaction without a session' - end - - unless ending_transaction || retry_write_allowed?(session, write_concern) - return legacy_write_with_retry(nil, context: context, &block) - end - - # If we are here, session is not nil. A session being nil would have - # failed retry_write_allowed? check. - - server = select_server(cluster, ServerSelector.primary, session) - - unless ending_transaction || server.retry_writes? - return legacy_write_with_retry(server, context: context, &block) - end - - txn_num = nil - - begin - connection_succeeded = false - server.with_connection(connection_global_id: context.connection_global_id) do |connection| - connection_succeeded = true - - session.materialize_if_needed - txn_num = if session.in_transaction? - session.txn_num - else - session.next_txn_num - end - - # The context needs to be duplicated here because we will be using - # it later for the retry as well. - yield(connection, txn_num, context.dup) - end - rescue *retryable_exceptions, Error::PoolError, Auth::Unauthorized => e - e.add_note('modern retry') - e.add_note("attempt 1") - if !e.label?('RetryableWriteError') - # If there was an error before the connection was successfully - # checked out and connected, there was no connection present to use - # for adding labels. Therefore, we should check if it is retryable, - # and if it is, add the label and retry it. - if !connection_succeeded && !session.in_transaction? && e.write_retryable? - e.add_label('RetryableWriteError') - else - raise e - end - end - - # Context#with creates a new context, which is not necessary here - # but the API is less prone to misuse this way. - retry_write(e, txn_num, context: context.with(is_retry: true), &block) - rescue Error::OperationFailure => e - e.add_note('modern retry') - e.add_note("attempt 1") - if e.unsupported_retryable_write? - raise_unsupported_error(e) - elsif !e.label?('RetryableWriteError') - raise e - end - - # Context#with creates a new context, which is not necessary here - # but the API is less prone to misuse this way. - retry_write(e, txn_num, context: context.with(is_retry: true), &block) - end + # @note this is only a public method so that tests can add expectations + # based on it. + def read_worker + @read_worker ||= ReadWorker.new(self) end - # Retryable writes wrapper for operations not supporting modern retryable - # writes. - # - # If the driver is configured to use modern retryable writes, this method - # yields to the passed block exactly once, thus not retrying any writes. - # - # If the driver is configured to use legacy retryable writes, this method - # delegates to legacy_write_with_retry which performs write retries using - # legacy logic. - # - # @param [ nil | Hash | WriteConcern::Base ] write_concern The write concern. - # @param [ Context ] context The context for the operation. - # - # @yieldparam [ Connection ] connection The connection through which the - # write should be sent. - # @yieldparam [ nil ] txn_num nil as transaction number. - # @yieldparam [ Operation::Context ] context The operation context. + # Returns the write worker for handling retryable writes. # # @api private - def nro_write_with_retry(write_concern, context:, &block) - session = context.session - - server = select_server(cluster, ServerSelector.primary, session) - if session && session.client.options[:retry_writes] - begin - server.with_connection(connection_global_id: context.connection_global_id) do |connection| - yield connection, nil, context - end - rescue *retryable_exceptions, Error::PoolError, Error::OperationFailure => e - e.add_note('retries disabled') - raise e - end - else - legacy_write_with_retry(server, context: context, &block) - end - end - - # Implements legacy write retrying functionality by yielding to the passed - # block one or more times. - # - # This method is used for operations which are not supported by modern - # retryable writes, such as delete_many and update_many. - # - # @param [ Server ] server The server which should be used for the - # operation. If not provided, the current primary will be retrieved from - # the cluster. - # @param [ Context ] context The context for the operation. # - # @yieldparam [ Connection ] connection The connection through which the - # write should be sent. - # @yieldparam [ nil ] txn_num nil as transaction number. - # @yieldparam [ Operation::Context ] context The operation context. - # - # @api private - def legacy_write_with_retry(server = nil, context:) - session = context.session - - # This is the pre-session retry logic, and is not subject to - # current retryable write specifications. - # In particular it does not retry on SocketError and SocketTimeoutError. - attempt = 0 - begin - attempt += 1 - server ||= select_server(cluster, ServerSelector.primary, session) - server.with_connection(connection_global_id: context.connection_global_id) do |connection| - # Legacy retries do not use txn_num - yield connection, nil, context.dup - end - rescue Error::OperationFailure => e - e.add_note('legacy retry') - e.add_note("attempt #{attempt}") - server = nil - if attempt > client.max_write_retries - raise e - end - if e.label?('RetryableWriteError') - log_retry(e, message: 'Legacy write retry') - cluster.scan!(false) - retry - else - raise e - end - end - end - - private - - def modern_read_with_retry(session, server_selector, &block) - server = select_server(cluster, server_selector, session) - begin - yield server - rescue *retryable_exceptions => e - e.add_note('modern retry') - e.add_note("attempt 1") - if session.in_transaction? - raise e - end - retry_read(e, server_selector, session, &block) - rescue Error::OperationFailure, Auth::Unauthorized, Error::PoolError => e - e.add_note('modern retry') - e.add_note("attempt 1") - if session.in_transaction? || !e.write_retryable? - raise e - end - retry_read(e, server_selector, session, &block) - end - end - - def legacy_read_with_retry(session, server_selector) - attempt = 0 - server = select_server(cluster, server_selector, session) - begin - attempt += 1 - yield server - rescue *retryable_exceptions => e - e.add_note('legacy retry') - e.add_note("attempt #{attempt}") - if attempt > client.max_read_retries || (session && session.in_transaction?) - raise e - end - log_retry(e, message: 'Legacy read retry') - server = select_server(cluster, server_selector, session) - retry - rescue Error::OperationFailure, Error::PoolError => e - e.add_note('legacy retry') - e.add_note("attempt #{attempt}") - if e.retryable? && !(session && session.in_transaction?) - if attempt > client.max_read_retries - raise e - end - log_retry(e, message: 'Legacy read retry') - sleep(client.read_retry_interval) - server = select_server(cluster, server_selector, session) - retry - else - raise e - end - end - end - - def retry_write_allowed?(session, write_concern) - unless session && session.retry_writes? - return false - end - - if write_concern.nil? - true - else - unless write_concern.is_a?(WriteConcern::Base) - write_concern = WriteConcern.get(write_concern) - end - write_concern.acknowledged? - end - end - - def retry_read(original_error, server_selector, session, &block) - begin - server = select_server(cluster, server_selector, session) - rescue Error, Error::AuthError => e - original_error.add_note("later retry failed: #{e.class}: #{e}") - - # See the corresponding note below in retry_write. - raise Error::RaiseOriginalError - end - - log_retry(original_error, message: 'Read retry') - - begin - yield server, true - rescue *retryable_exceptions => e - e.add_note('modern retry') - e.add_note("attempt 2") - raise e - rescue Error::OperationFailure, Error::PoolError => e - e.add_note('modern retry') - unless e.write_retryable? - original_error.add_note("later retry failed: #{e.class}: #{e}") - raise original_error - end - e.add_note("attempt 2") - raise e - rescue Error, Error::AuthError => e - e.add_note('modern retry') - original_error.add_note("later retry failed: #{e.class}: #{e}") - raise original_error - end - rescue Error::RaiseOriginalError - raise original_error - end - - def retry_write(original_error, txn_num, context:, &block) - session = context.session - - # We do not request a scan of the cluster here, because error handling - # for the error which triggered the retry should have updated the - # server description and/or topology as necessary (specifically, - # a socket error or a not master error should have marked the respective - # server unknown). Here we just need to wait for server selection. - server = select_server(cluster, ServerSelector.primary, session) - unless server.retry_writes? - # Do not need to add "modern retry" here, it should already be on - # the first exception. - original_error.add_note('did not retry because server selected for retry does not supoprt retryable writes') - - # When we want to raise the original error, we must not run the - # rescue blocks below that add diagnostics because the diagnostics - # added would either be rendundant (e.g. modern retry note) or wrong - # (e.g. "attempt 2", we are raising the exception produced in the - # first attempt and haven't attempted the second time). Use the - # special marker class to bypass the ordinarily applicable rescues. - raise Error::RaiseOriginalError - end - log_retry(original_error, message: 'Write retry') - server.with_connection(connection_global_id: context.connection_global_id) do |connection| - yield(connection, txn_num, context) - end - rescue *retryable_exceptions, Error::PoolError => e - if e.write_retryable? - e.add_note('modern retry') - e.add_note('attempt 2') - raise e - else - original_error.add_note("later retry failed: #{e.class}: #{e}") - raise original_error - end - rescue Error::OperationFailure => e - e.add_note('modern retry') - if e.label?('RetryableWriteError') && !e.label?('NoWritesPerformed') - e.add_note('attempt 2') - raise e - else - original_error.add_note("later retry failed: #{e.class}: #{e}") - raise original_error - end - rescue Error, Error::AuthError => e - # Do not need to add "modern retry" here, it should already be on - # the first exception. - original_error.add_note("later retry failed: #{e.class}: #{e}") - raise original_error - rescue Error::RaiseOriginalError - raise original_error - end - - # This is a separate method to make it possible for the test suite to - # assert that server selection is performed during retry attempts. - def select_server(cluster, server_selector, session) - server_selector.select_server(cluster, nil, session) - end - - # Log a warning so that any application slow down is immediately obvious. - def log_retry(e, options = nil) - message = if options && options[:message] - options[:message] - else - "Retry" - end - Logger.logger.warn "#{message} due to: #{e.class.name}: #{e.message}" - end - - # Retry writes on MMAPv1 should raise an actionable error; append actionable - # information to the error message and preserve the backtrace. - def raise_unsupported_error(e) - new_error = Error::OperationFailure.new("#{e.class}: #{e} "\ - "This MongoDB deployment does not support retryable writes. Please add "\ - "retryWrites=false to your connection string or use the retry_writes: false Ruby client option") - new_error.set_backtrace(e.backtrace) - raise new_error - end - - # Returns a list of exception classes that are generally retryable. - def retryable_exceptions - [ - Error::ConnectionPerished, - Error::ServerNotUsable, - Error::SocketError, - Error::SocketTimeoutError - ].freeze + # @note this is only a public method so that tests can add expectations + # based on it. + def write_worker + @write_worker ||= WriteWorker.new(self) end end end diff --git a/lib/mongo/retryable/base_worker.rb b/lib/mongo/retryable/base_worker.rb new file mode 100644 index 0000000000..59ac37db06 --- /dev/null +++ b/lib/mongo/retryable/base_worker.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2015-2023 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Retryable + + # The abstract superclass for workers employed by Mongo::Retryable. + # + # @api private + class BaseWorker + extend Forwardable + + def_delegators :retryable, + :client, + :cluster, + :select_server + + # @return [ Mongo::Retryable ] retryable A reference to the client object + # that instatiated this worker. + attr_reader :retryable + + # Constructs a new worker. + # + # @example Instantiating a new read worker + # worker = Mongo::Retryable::ReadWorker.new(self) + # + # @example Instantiating a new write worker + # worker = Mongo::Retryable::WriteWorker.new(self) + # + # @param [ Mongo::Retryable ] retryable The client object that is using + # this worker to perform a retryable operation + def initialize(retryable) + @retryable = retryable + end + + private + + # Indicate which exception classes that are generally retryable. + # + # @return [ Array ] Array of exception classes that are + # considered retryable. + def retryable_exceptions + [ + Error::ConnectionPerished, + Error::ServerNotUsable, + Error::SocketError, + Error::SocketTimeoutError + ].freeze + end + + # Tests to see if the given exception instance is of a type that can + # be retried. + # + # @return [ true | false ] true if the exception is retryable. + def is_retryable_exception?(e) + retryable_exceptions.any? { |klass| klass === e } + end + + # Logs the given deprecation warning the first time it is called for a + # given key; after that, it does nothing when given the same key. + def deprecation_warning(key, warning) + $_deprecation_warnings ||= {} + unless $_deprecation_warnings[key] + $_deprecation_warnings[key] = true + Logger.logger.warn(warning) + end + end + + # Log a warning so that any application slow down is immediately obvious. + def log_retry(e, options = nil) + message = (options || {}).fetch(:message, "Retry") + Logger.logger.warn "#{message} due to: #{e.class.name}: #{e.message}" + end + end + + end +end diff --git a/lib/mongo/retryable/read_worker.rb b/lib/mongo/retryable/read_worker.rb new file mode 100644 index 0000000000..7036bf4b88 --- /dev/null +++ b/lib/mongo/retryable/read_worker.rb @@ -0,0 +1,296 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2015-2023 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'mongo/retryable/base_worker' + +module Mongo + module Retryable + + # Implements the logic around retrying read operations. + # + # @api private + # + # @since 2.19.0 + class ReadWorker < BaseWorker + # Execute a read operation returning a cursor with retrying. + # + # This method performs server selection for the specified server selector + # and yields to the provided block, which should execute the initial + # query operation and return its result. The block will be passed the + # server selected for the operation. If the block raises an exception, + # and this exception corresponds to a read retryable error, and read + # retries are enabled for the client, this method will perform server + # selection again and yield to the block again (with potentially a + # different server). If the block returns successfully, the result + # of the block (which should be a Mongo::Operation::Result) is used to + # construct a Mongo::Cursor object for the result set. The cursor + # is then returned. + # + # If modern retry reads are on (which is the default), the initial read + # operation will be retried once. If legacy retry reads are on, the + # initial read operation will be retried zero or more times depending + # on the :max_read_retries client setting, the default for which is 1. + # To disable read retries, turn off modern read retries by setting + # retry_reads: false and set :max_read_retries to 0 on the client. + # + # @api private + # + # @example Execute a read returning a cursor. + # cursor = read_with_retry_cursor(session, server_selector, view) do |server| + # # return a Mongo::Operation::Result + # ... + # end + # + # @param [ Mongo::Session ] session The session that the operation is being + # run on. + # @param [ Mongo::ServerSelector::Selectable ] server_selector Server + # selector for the operation. + # @param [ CollectionView ] view The +CollectionView+ defining the query. + # @param [ Proc ] block The block to execute. + # + # @return [ Cursor ] The cursor for the result set. + def read_with_retry_cursor(session, server_selector, view, &block) + read_with_retry(session, server_selector) do |server| + result = yield server + + # RUBY-2367: This will be updated to allow the query cache to + # cache cursors with multi-batch results. + if QueryCache.enabled? && !view.collection.system_collection? + CachingCursor.new(view, result, server, session: session) + else + Cursor.new(view, result, server, session: session) + end + end + end + + # Execute a read operation with retrying. + # + # This method performs server selection for the specified server selector + # and yields to the provided block, which should execute the initial + # query operation and return its result. The block will be passed the + # server selected for the operation. If the block raises an exception, + # and this exception corresponds to a read retryable error, and read + # retries are enabled for the client, this method will perform server + # selection again and yield to the block again (with potentially a + # different server). If the block returns successfully, the result + # of the block is returned. + # + # If modern retry reads are on (which is the default), the initial read + # operation will be retried once. If legacy retry reads are on, the + # initial read operation will be retried zero or more times depending + # on the :max_read_retries client setting, the default for which is 1. + # To disable read retries, turn off modern read retries by setting + # retry_reads: false and set :max_read_retries to 0 on the client. + # + # @api private + # + # @example Execute the read. + # read_with_retry(session, server_selector) do |server| + # ... + # end + # + # @param [ Mongo::Session | nil ] session The session that the operation + # is being run on. + # @param [ Mongo::ServerSelector::Selectable | nil ] server_selector + # Server selector for the operation. + # @param [ Proc ] block The block to execute. + # + # @return [ Result ] The result of the operation. + def read_with_retry(session = nil, server_selector = nil, &block) + if session.nil? && server_selector.nil? + deprecated_legacy_read_with_retry(&block) + elsif session&.retry_reads? + modern_read_with_retry(session, server_selector, &block) + elsif client.max_read_retries > 0 + legacy_read_with_retry(session, server_selector, &block) + else + read_without_retry(session, server_selector, &block) + end + end + + # Execute a read operation with a single retry on network errors. + # + # This method is used by the driver for some of the internal housekeeping + # operations. Application-requested reads should use read_with_retry + # rather than this method. + # + # @api private + # + # @example Execute the read. + # read_with_one_retry do + # ... + # end + # + # @note This only retries read operations on socket errors. + # + # @param [ Hash | nil ] options Options. + # + # @option options [ String ] :retry_message Message to log when retrying. + # + # @yield Calls the provided block with no arguments + # + # @return [ Result ] The result of the operation. + # + # @since 2.2.6 + def read_with_one_retry(options = nil) + yield + rescue *retryable_exceptions, Error::PoolError => e + raise e unless e.write_retryable? + + retry_message = options && options[:retry_message] + log_retry(e, message: retry_message) + yield + end + + private + + # Attempts to do a legacy read_with_retry, without either a session or + # server_selector. This is a deprecated use-case, and a warning will be + # issued the first time this is invoked. + # + # @param [ Proc ] block The block to execute. + # + # @return [ Result ] The result of the operation. + def deprecated_legacy_read_with_retry(&block) + deprecation_warning :read_with_retry, + 'Legacy read_with_retry invocation - ' \ + 'please update the application and/or its dependencies' + + # Since we don't have a session, we cannot use the modern read retries. + # And we need to select a server but we don't have a server selector. + # Use PrimaryPreferred which will work as long as there is a data + # bearing node in the cluster; the block may select a different server + # which is fine. + server_selector = ServerSelector.get(mode: :primary_preferred) + legacy_read_with_retry(nil, server_selector, &block) + end + + # Attempts to do a "modern" read with retry. Only a single retry will + # be attempted. + # + # @param [ Mongo::Session ] session The session that the operation is + # being run on. + # @param [ Mongo::ServerSelector::Selectable ] server_selector Server + # selector for the operation. + # @param [ Proc ] block The block to execute. + # + # @return [ Result ] The result of the operation. + def modern_read_with_retry(session, server_selector, &block) + yield select_server(cluster, server_selector, session) + rescue *retryable_exceptions, Error::OperationFailure, Auth::Unauthorized, Error::PoolError => e + e.add_notes('modern retry', 'attempt 1') + raise e if session.in_transaction? + raise e if !is_retryable_exception?(e) && !e.write_retryable? + retry_read(e, session, server_selector, &block) + end + + # Attempts to do a "legacy" read with retry. The operation will be + # attempted multiple times, up to the client's `max_read_retries` + # setting. + # + # @param [ Mongo::Session ] session The session that the operation is + # being run on. + # @param [ Mongo::ServerSelector::Selectable ] server_selector Server + # selector for the operation. + # @param [ Proc ] block The block to execute. + # + # @return [ Result ] The result of the operation. + def legacy_read_with_retry(session, server_selector, &block) + attempt = attempt ? attempt + 1 : 1 + yield select_server(cluster, server_selector, session) + rescue *retryable_exceptions, Error::OperationFailure, Error::PoolError => e + e.add_notes('legacy retry', "attempt #{attempt}") + + if is_retryable_exception?(e) + raise e if attempt > client.max_read_retries || session&.in_transaction? + elsif e.retryable? && !session&.in_transaction? + raise e if attempt > client.max_read_retries + else + raise e + end + + log_retry(e, message: 'Legacy read retry') + sleep(client.read_retry_interval) unless is_retryable_exception?(e) + retry + end + + # Attempts to do a read *without* a retry; for example, when retries have + # been explicitly disabled. + # + # @param [ Mongo::Session ] session The session that the operation is + # being run on. + # @param [ Mongo::ServerSelector::Selectable ] server_selector Server + # selector for the operation. + # @param [ Proc ] block The block to execute. + # + # @return [ Result ] The result of the operation. + def read_without_retry(session, server_selector, &block) + server = select_server(cluster, server_selector, session) + + begin + yield server + rescue *retryable_exceptions, Error::PoolError, Error::OperationFailure => e + e.add_note('retries disabled') + raise e + end + end + + # The retry logic of the "modern" read_with_retry implementation. + # + # @param [ Exception ] original_error The original error that triggered + # the retry. + # @param [ Mongo::Session ] session The session that the operation is + # being run on. + # @param [ Mongo::ServerSelector::Selectable ] server_selector Server + # selector for the operation. + # @param [ Proc ] block The block to execute. + # + # @return [ Result ] The result of the operation. + def retry_read(original_error, session, server_selector, &block) + begin + server = select_server(cluster, server_selector, session) + rescue Error, Error::AuthError => e + original_error.add_note("later retry failed: #{e.class}: #{e}") + raise original_error + end + + log_retry(original_error, message: 'Read retry') + + begin + yield server, true + rescue *retryable_exceptions => e + e.add_notes('modern retry', 'attempt 2') + raise e + rescue Error::OperationFailure, Error::PoolError => e + e.add_note('modern retry') + unless e.write_retryable? + original_error.add_note("later retry failed: #{e.class}: #{e}") + raise original_error + end + e.add_note("attempt 2") + raise e + rescue Error, Error::AuthError => e + e.add_note('modern retry') + original_error.add_note("later retry failed: #{e.class}: #{e}") + raise original_error + end + end + + end + + end +end diff --git a/lib/mongo/retryable/write_worker.rb b/lib/mongo/retryable/write_worker.rb new file mode 100644 index 0000000000..cf0d915185 --- /dev/null +++ b/lib/mongo/retryable/write_worker.rb @@ -0,0 +1,364 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2015-2023 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'mongo/retryable/base_worker' + +module Mongo + module Retryable + + # Implements the logic around retrying write operations. + # + # @api private + # + # @since 2.19.0 + class WriteWorker < BaseWorker + # Implements write retrying functionality by yielding to the passed + # block one or more times. + # + # If the session is provided (hence, the deployment supports sessions), + # and modern retry writes are enabled on the client, the modern retry + # logic is invoked. Otherwise the legacy retry logic is invoked. + # + # If ending_transaction parameter is true, indicating that a transaction + # is being committed or aborted, the operation is executed exactly once. + # Note that, since transactions require sessions, this method will raise + # ArgumentError if ending_transaction is true and session is nil. + # + # @api private + # + # @example Execute the write. + # write_with_retry do + # ... + # end + # + # @note This only retries operations on not master failures, since it is + # the only case we can be sure a partial write did not already occur. + # + # @param [ nil | Hash | WriteConcern::Base ] write_concern The write concern. + # @param [ true | false ] ending_transaction True if the write operation is + # abortTransaction or commitTransaction, false otherwise. + # @param [ Context ] context The context for the operation. + # @param [ Proc ] block The block to execute. + # + # @yieldparam [ Connection ] connection The connection through which the + # write should be sent. + # @yieldparam [ Integer ] txn_num Transaction number (NOT the ACID kind). + # @yieldparam [ Operation::Context ] context The operation context. + # + # @return [ Result ] The result of the operation. + # + # @since 2.1.0 + def write_with_retry(write_concern, ending_transaction: false, context:, &block) + session = context.session + + ensure_valid_state!(ending_transaction, session) + + unless ending_transaction || retry_write_allowed?(session, write_concern) + return legacy_write_with_retry(nil, context: context, &block) + end + + # If we are here, session is not nil. A session being nil would have + # failed retry_write_allowed? check. + + server = select_server(cluster, ServerSelector.primary, session) + + unless ending_transaction || server.retry_writes? + return legacy_write_with_retry(server, context: context, &block) + end + + modern_write_with_retry(session, server, context, &block) + end + + # Retryable writes wrapper for operations not supporting modern retryable + # writes. + # + # If the driver is configured to use modern retryable writes, this method + # yields to the passed block exactly once, thus not retrying any writes. + # + # If the driver is configured to use legacy retryable writes, this method + # delegates to legacy_write_with_retry which performs write retries using + # legacy logic. + # + # @param [ nil | Hash | WriteConcern::Base ] write_concern The write concern. + # @param [ Context ] context The context for the operation. + # + # @yieldparam [ Connection ] connection The connection through which the + # write should be sent. + # @yieldparam [ nil ] txn_num nil as transaction number. + # @yieldparam [ Operation::Context ] context The operation context. + def nro_write_with_retry(write_concern, context:, &block) + session = context.session + server = select_server(cluster, ServerSelector.primary, session) + + if session&.client.options[:retry_writes] + begin + server.with_connection(connection_global_id: context.connection_global_id) do |connection| + yield connection, nil, context + end + rescue *retryable_exceptions, Error::PoolError, Error::OperationFailure => e + e.add_note('retries disabled') + raise e + end + else + legacy_write_with_retry(server, context: context, &block) + end + end + + # Queries whether the session and write concern support retrying writes. + # + # @param [ Mongo::Session ] session The session that the operation is + # being run on. + # @param [ nil | Hash | WriteConcern::Base ] write_concern The write + # concern. + # + # @return [ true | false ] Whether write retries are allowed or not. + def retry_write_allowed?(session, write_concern) + return false unless session&.retry_writes? + + if write_concern.nil? + true + else + WriteConcern.get(write_concern).acknowledged? + end + end + + private + + # Makes sure the state of the arguments is consistent and valid. + # + # @param [ true | false ] ending_transaction True if the write operation + # is abortTransaction or commitTransaction, false otherwise. + # @param [ nil | Mongo::Session ] session The session that the operation + # is being run on (if any). + def ensure_valid_state!(ending_transaction, session) + if ending_transaction && !session + raise ArgumentError, 'Cannot end a transaction without a session' + end + end + + # Implements legacy write retrying functionality by yielding to the passed + # block one or more times. + # + # This method is used for operations which are not supported by modern + # retryable writes, such as delete_many and update_many. + # + # @param [ Server ] server The server which should be used for the + # operation. If not provided, the current primary will be retrieved from + # the cluster. + # @param [ Context ] context The context for the operation. + # + # @yieldparam [ Connection ] connection The connection through which the + # write should be sent. + # @yieldparam [ nil ] txn_num nil as transaction number. + # @yieldparam [ Operation::Context ] context The operation context. + # + # @api private + def legacy_write_with_retry(server = nil, context:) + session = context.session + + # This is the pre-session retry logic, and is not subject to + # current retryable write specifications. + # In particular it does not retry on SocketError and SocketTimeoutError. + attempt = 0 + begin + attempt += 1 + server ||= select_server(cluster, ServerSelector.primary, session) + server.with_connection(connection_global_id: context.connection_global_id) do |connection| + # Legacy retries do not use txn_num + yield connection, nil, context.dup + end + rescue Error::OperationFailure => e + e.add_note('legacy retry') + e.add_note("attempt #{attempt}") + server = nil + if attempt > client.max_write_retries + raise e + end + if e.label?('RetryableWriteError') + log_retry(e, message: 'Legacy write retry') + cluster.scan!(false) + retry + else + raise e + end + end + end + + # Implements modern write retrying functionality by yielding to the passed + # block no more than twice. + # + # @param [ Mongo::Session ] session The session that the operation is + # being run on. + # @param [ Server ] server The server which should be used for the + # operation. + # @param [ Operation::Context ] context The context for the operation. + # + # @yieldparam [ Connection ] connection The connection through which the + # write should be sent. + # @yieldparam [ Integer ] txn_num Transaction number (NOT the ACID kind). + # @yieldparam [ Operation::Context ] context The operation context. + # + # @return [ Result ] The result of the operation. + # + # @api private + def modern_write_with_retry(session, server, context, &block) + txn_num = nil + connection_succeeded = false + + server.with_connection(connection_global_id: context.connection_global_id) do |connection| + connection_succeeded = true + + session.materialize_if_needed + txn_num = session.in_transaction? ? session.txn_num : session.next_txn_num + + # The context needs to be duplicated here because we will be using + # it later for the retry as well. + yield connection, txn_num, context.dup + end + rescue *retryable_exceptions, Error::PoolError, Auth::Unauthorized, Error::OperationFailure => e + e.add_notes('modern retry', 'attempt 1') + + if e.is_a?(Error::OperationFailure) + ensure_retryable!(e) + else + ensure_labeled_retryable!(e, connection_succeeded, session) + end + + # Context#with creates a new context, which is not necessary here + # but the API is less prone to misuse this way. + retry_write(e, txn_num, context: context.with(is_retry: true), &block) + end + + # Called after a failed write, this will retry the write no more than + # once. + # + # @param [ Exception ] original_error The exception that triggered the + # retry. + # @param [ Number ] txn_num The transaction number. + # @param [ Operation::Context ] context The context for the operation. + # + # @return [ Result ] The result of the operation. + def retry_write(original_error, txn_num, context:, &block) + session = context.session + + # We do not request a scan of the cluster here, because error handling + # for the error which triggered the retry should have updated the + # server description and/or topology as necessary (specifically, + # a socket error or a not master error should have marked the respective + # server unknown). Here we just need to wait for server selection. + server = select_server(cluster, ServerSelector.primary, session) + + unless server.retry_writes? + # Do not need to add "modern retry" here, it should already be on + # the first exception. + original_error.add_note('did not retry because server selected for retry does not support retryable writes') + + # When we want to raise the original error, we must not run the + # rescue blocks below that add diagnostics because the diagnostics + # added would either be rendundant (e.g. modern retry note) or wrong + # (e.g. "attempt 2", we are raising the exception produced in the + # first attempt and haven't attempted the second time). Use the + # special marker class to bypass the ordinarily applicable rescues. + raise Error::RaiseOriginalError + end + + log_retry(original_error, message: 'Write retry') + server.with_connection(connection_global_id: context.connection_global_id) do |connection| + yield(connection, txn_num, context) + end + rescue *retryable_exceptions, Error::PoolError => e + fail_on_retryable!(e, original_error) + rescue Error::OperationFailure => e + fail_on_operation_failure!(e, original_error) + rescue Error, Error::AuthError => e + fail_on_other_error!(e, original_error) + rescue Error::RaiseOriginalError + raise original_error + end + + # Retry writes on MMAPv1 should raise an actionable error; append actionable + # information to the error message and preserve the backtrace. + def raise_unsupported_error(e) + new_error = Error::OperationFailure.new("#{e.class}: #{e} "\ + "This MongoDB deployment does not support retryable writes. Please add "\ + "retryWrites=false to your connection string or use the retry_writes: false Ruby client option") + new_error.set_backtrace(e.backtrace) + raise new_error + end + + # Make sure the exception object is labeled 'RetryableWriteError'. If it + # isn't, and should not be, re-raise the exception. + def ensure_labeled_retryable!(e, connection_succeeded, session) + if !e.label?('RetryableWriteError') + # If there was an error before the connection was successfully + # checked out and connected, there was no connection present to use + # for adding labels. Therefore, we should check if it is retryable, + # and if it is, add the label and retry it. + if !connection_succeeded && !session.in_transaction? && e.write_retryable? + e.add_label('RetryableWriteError') + else + raise e + end + end + end + + # Make sure the exception object supports retryable writes. If it does, + # make sure it has been appropriately labeled. If either condition fails, + # raise an exception. + def ensure_retryable!(e) + if e.unsupported_retryable_write? + raise_unsupported_error(e) + elsif !e.label?('RetryableWriteError') + raise e + end + end + + # Raise either e, or original_error, depending on whether e is + # write_retryable. + def fail_on_retryable!(e, original_error) + if e.write_retryable? + e.add_notes('modern retry', 'attempt 2') + raise e + else + original_error.add_note("later retry failed: #{e.class}: #{e}") + raise original_error + end + end + + # Raise either e, or original_error, depending on whether e is + # appropriately labeled. + def fail_on_operation_failure!(e, original_error) + e.add_note('modern retry') + if e.label?('RetryableWriteError') && !e.label?('NoWritesPerformed') + e.add_note('attempt 2') + raise e + else + original_error.add_note("later retry failed: #{e.class}: #{e}") + raise original_error + end + end + + # Raise the original error (after annotating). + def fail_on_other_error!(e, original_error) + # Do not need to add "modern retry" here, it should already be on + # the first exception. + original_error.add_note("later retry failed: #{e.class}: #{e}") + raise original_error + end + end + end +end diff --git a/spec/integration/retryable_writes_errors_spec.rb b/spec/integration/retryable_writes_errors_spec.rb index 038196a570..93342f212a 100644 --- a/spec/integration/retryable_writes_errors_spec.rb +++ b/spec/integration/retryable_writes_errors_spec.rb @@ -71,7 +71,7 @@ authorized_client.subscribe(Mongo::Monitoring::COMMAND, subscriber) authorized_client.use(:admin).command(failpoint1) - expect(authorized_collection).to receive(:retry_write).once.and_wrap_original do |m, *args, &block| + expect(authorized_collection.write_worker).to receive(:retry_write).once.and_wrap_original do |m, *args, &block| expect(args.first.code).to eq(91) authorized_client.use(:admin).command(failpoint2) m.call(*args, &block) diff --git a/spec/mongo/retryable_spec.rb b/spec/mongo/retryable_spec.rb index bac23e3971..c1e3388dcd 100644 --- a/spec/mongo/retryable_spec.rb +++ b/spec/mongo/retryable_spec.rb @@ -46,7 +46,7 @@ def write end def retry_write_allowed_as_configured? - retry_write_allowed?(session, write_concern) + write_worker.retry_write_allowed?(session, write_concern) end end @@ -87,7 +87,9 @@ def write_concern class RetryableHost include Mongo::Retryable - public :retry_write_allowed? + def retry_write_allowed?(*args) + write_worker.retry_write_allowed?(*args) + end end describe Mongo::Retryable do From ec94aa257d3b27ebf950d608e5d3610ee153d7e5 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 31 Jan 2023 17:58:51 +0100 Subject: [PATCH 100/198] Update public key (#2684) --- gem-public_cert.pem | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/gem-public_cert.pem b/gem-public_cert.pem index 40d6d3807b..ab7c15c7ce 100644 --- a/gem-public_cert.pem +++ b/gem-public_cert.pem @@ -1,21 +1,26 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- From 2516ff53e4d7f357af8c85121ff9e6acb0919a8b Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 3 Feb 2023 16:30:32 +0100 Subject: [PATCH 101/198] Fix KMIP test setup (#2687) --- .evergreen/config.yml | 29 ++++++++++++++++------------- .evergreen/config/standard.yml.erb | 29 ++++++++++++++++------------- .evergreen/run-tests-serverless.sh | 2 +- .evergreen/run-tests.sh | 2 +- spec/mongo/index/view_spec.rb | 2 ++ spec/shared | 2 +- 6 files changed, 37 insertions(+), 29 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 1b9256be3c..a42360798e 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1506,18 +1506,19 @@ buildvariants: tasks: - name: "test-fle" - - matrix_name: "fle-jruby" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [jruby-9.2, jruby-9.3] - topology: standalone - mongodb-version: ['5.0'] - os: ubuntu1804 - fle: helper - docker-distro: ubuntu1804 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" + # https://jira.mongodb.org/browse/RUBY-3217 + #- matrix_name: "fle-jruby" + # matrix_spec: + # auth-and-ssl: "noauth-and-nossl" + # ruby: [jruby-9.2, jruby-9.3] + # topology: standalone + # mongodb-version: ['5.0'] + # os: ubuntu1804 + # fle: helper + # docker-distro: ubuntu1804 + # display_name: "FLE: ${mongodb-version} ${ruby}" + # tasks: + # - name: "test-fle-via-docker" - matrix_name: aws-auth-regular matrix_spec: @@ -1774,7 +1775,9 @@ buildvariants: - matrix_name: "serverless" matrix_spec: - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + # https://jira.mongodb.org/browse/RUBY-3217 + # ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.3"] + ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1"] fle: helper display_name: "Atlas serverless ${ruby} single mongos" run_on: diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index ff7e8378fe..728079a657 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -515,18 +515,19 @@ buildvariants: tasks: - name: "test-fle" - - matrix_name: "fle-jruby" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [jruby-9.2, jruby-9.3] - topology: standalone - mongodb-version: ['5.0'] - os: ubuntu1804 - fle: helper - docker-distro: ubuntu1804 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" + # https://jira.mongodb.org/browse/RUBY-3217 + #- matrix_name: "fle-jruby" + # matrix_spec: + # auth-and-ssl: "noauth-and-nossl" + # ruby: [jruby-9.2, jruby-9.3] + # topology: standalone + # mongodb-version: ['5.0'] + # os: ubuntu1804 + # fle: helper + # docker-distro: ubuntu1804 + # display_name: "FLE: ${mongodb-version} ${ruby}" + # tasks: + # - name: "test-fle-via-docker" - matrix_name: aws-auth-regular matrix_spec: @@ -666,7 +667,9 @@ buildvariants: - matrix_name: "serverless" matrix_spec: - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + # https://jira.mongodb.org/browse/RUBY-3217 + # ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.3"] + ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1"] fle: helper display_name: "Atlas serverless ${ruby} single mongos" run_on: diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index df13341466..aa83912ad3 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -29,7 +29,7 @@ fi cd .evergreen/csfle . ./activate-kmstlsvenv.sh -pip install boto3~=1.19 'cryptography<3.4' pykmip~=0.10.0 +pip install boto3~=1.19 'cryptography<3.4' pykmip~=0.10.0 'sqlalchemy<2.0.0' python -u ./kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 7999 & python -u ./kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 8000 & diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index b9d3ee6ecc..de70d6f52e 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -182,7 +182,7 @@ if test -n "$FLE"; then # cryptography 3.4 requires rust, see # https://github.com/pyca/cryptography/issues/5771. #pip install boto3~=1.19 cryptography~=3.4.8 pykmip~=0.10.0 - pip3 install boto3~=1.19 'cryptography<3.4' pykmip~=0.10.0 + pip3 install boto3~=1.19 'cryptography<3.4' pykmip~=0.10.0 'sqlalchemy<2.0.0' fi python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/server.pem --port 7999 & python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/expired.pem --port 8000 & diff --git a/spec/mongo/index/view_spec.rb b/spec/mongo/index/view_spec.rb index 01627c61ce..7f074ddb5c 100644 --- a/spec/mongo/index/view_spec.rb +++ b/spec/mongo/index/view_spec.rb @@ -958,6 +958,8 @@ context 'on server versions >= 5.0' do min_server_fcv '5.0' + # https://jira.mongodb.org/browse/RUBY-3216 + max_server_fcv '6.2' it 'passes wildcardProjection correctly' do expect(indexes[:wildcardProjection]).to eq({ '_id' => false, 'rating' => true }) diff --git a/spec/shared b/spec/shared index 6fca775f07..fb60e69cc9 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit 6fca775f07e17dc1b6637eadaff768f8a87d8201 +Subproject commit fb60e69cc9f3a72c44cef0482a17a2666aceac6f From d727dba6a1cc3bfb15ade78bb9b809984f7bf849 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 9 Feb 2023 17:53:53 +0100 Subject: [PATCH 102/198] RUBY-3005 Support Range Indexes for auto encryption (#2689) --- gemfiles/standard.rb | 2 +- lib/mongo/crypt/binding.rb | 2 +- lib/mongo/crypt/encryption_io.rb | 2 +- lib/mongo/cursor.rb | 22 +- lib/mongo/database/view.rb | 39 +- spec/runners/crud/operation.rb | 2 +- spec/runners/crud/spec.rb | 2 +- spec/runners/crud/verifier.rb | 4 +- spec/runners/transactions/test.rb | 7 +- .../spec_tests/client_side_encryption_spec.rb | 2 +- .../fle2-Range-Date-Aggregate.yml | 227 +++ .../fle2-Range-Date-Correctness.yml | 420 ++++ .../fle2-Range-Date-Delete.yml | 185 ++ .../fle2-Range-Date-FindOneAndUpdate.yml | 243 +++ .../fle2-Range-Date-InsertFind.yml | 221 +++ .../fle2-Range-Date-Update.yml | 260 +++ .../fle2-Range-Decimal-Aggregate.yml | 1673 ++++++++++++++++ .../fle2-Range-Decimal-Correctness.yml | 291 +++ .../fle2-Range-Decimal-Delete.yml | 908 +++++++++ .../fle2-Range-Decimal-FindOneAndUpdate.yml | 1688 ++++++++++++++++ .../fle2-Range-Decimal-InsertFind.yml | 1666 ++++++++++++++++ .../fle2-Range-Decimal-Update.yml | 1705 +++++++++++++++++ .../fle2-Range-DecimalPrecision-Aggregate.yml | 315 +++ ...le2-Range-DecimalPrecision-Correctness.yml | 422 ++++ .../fle2-Range-DecimalPrecision-Delete.yml | 229 +++ ...ange-DecimalPrecision-FindOneAndUpdate.yml | 331 ++++ ...fle2-Range-DecimalPrecision-InsertFind.yml | 305 +++ .../fle2-Range-DecimalPrecision-Update.yml | 344 ++++ .../fle2-Range-Double-Aggregate.yml | 899 +++++++++ .../fle2-Range-Double-Correctness.yml | 290 +++ .../fle2-Range-Double-Delete.yml | 521 +++++ .../fle2-Range-Double-FindOneAndUpdate.yml | 915 +++++++++ .../fle2-Range-Double-InsertFind.yml | 893 +++++++++ .../fle2-Range-Double-Update.yml | 932 +++++++++ .../fle2-Range-DoublePrecision-Aggregate.yml | 311 +++ ...fle2-Range-DoublePrecision-Correctness.yml | 422 ++++ .../fle2-Range-DoublePrecision-Delete.yml | 227 +++ ...Range-DoublePrecision-FindOneAndUpdate.yml | 327 ++++ .../fle2-Range-DoublePrecision-InsertFind.yml | 305 +++ .../fle2-Range-DoublePrecision-Update.yml | 344 ++++ .../fle2-Range-Int-Aggregate.yml | 227 +++ .../fle2-Range-Int-Correctness.yml | 421 ++++ .../fle2-Range-Int-Delete.yml | 185 ++ .../fle2-Range-Int-FindOneAndUpdate.yml | 243 +++ .../fle2-Range-Int-InsertFind.yml | 221 +++ .../fle2-Range-Int-Update.yml | 260 +++ .../fle2-Range-Long-Aggregate.yml | 227 +++ .../fle2-Range-Long-Correctness.yml | 420 ++++ .../fle2-Range-Long-Delete.yml | 185 ++ .../fle2-Range-Long-FindOneAndUpdate.yml | 243 +++ .../fle2-Range-Long-InsertFind.yml | 221 +++ .../fle2-Range-Long-Update.yml | 260 +++ .../fle2-Range-WrongType.yml | 41 + spec/support/crypt.rb | 8 +- .../encryptedFields.json | 0 .../range-encryptedFields-Date.json | 30 + ...ge-encryptedFields-DecimalNoPrecision.json | 21 + ...ange-encryptedFields-DecimalPrecision.json | 29 + ...nge-encryptedFields-DoubleNoPrecision.json | 21 + ...range-encryptedFields-DoublePrecision.json | 30 + .../range-encryptedFields-Int.json | 27 + .../range-encryptedFields-Long.json | 27 + 62 files changed, 21236 insertions(+), 14 deletions(-) create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Aggregate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Correctness.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Delete.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Date-FindOneAndUpdate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Date-InsertFind.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Update.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Aggregate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Correctness.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Delete.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-FindOneAndUpdate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-InsertFind.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Update.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Aggregate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Correctness.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Delete.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-FindOneAndUpdate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-InsertFind.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Update.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Aggregate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Correctness.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Delete.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Double-FindOneAndUpdate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Double-InsertFind.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Update.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Aggregate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Correctness.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Delete.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-FindOneAndUpdate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-InsertFind.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Update.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Aggregate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Correctness.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Delete.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Int-FindOneAndUpdate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Int-InsertFind.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Update.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Aggregate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Correctness.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Delete.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Long-FindOneAndUpdate.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Long-InsertFind.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Update.yml create mode 100644 spec/spec_tests/data/client_side_encryption/fle2-Range-WrongType.yml rename spec/support/crypt/{ => encrypted_fields}/encryptedFields.json (100%) create mode 100644 spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json create mode 100644 spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json create mode 100644 spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json create mode 100644 spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json create mode 100644 spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json create mode 100644 spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json create mode 100644 spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index 81a27c9fb5..7ba55dd4d2 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -60,6 +60,6 @@ def standard_dependencies end if ENV['FLE'] == 'helper' - gem 'libmongocrypt-helper', '~> 1.6.1' + gem 'libmongocrypt-helper', '~> 1.7.0' end end diff --git a/lib/mongo/crypt/binding.rb b/lib/mongo/crypt/binding.rb index 64e17d0aa7..da706626b7 100644 --- a/lib/mongo/crypt/binding.rb +++ b/lib/mongo/crypt/binding.rb @@ -83,7 +83,7 @@ class Binding # will cause a `LoadError`. # # @api private - MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new("1.6.1") + MIN_LIBMONGOCRYPT_VERSION = Gem::Version.new("1.7.0") # @!method self.mongocrypt_version(len) # @api private diff --git a/lib/mongo/crypt/encryption_io.rb b/lib/mongo/crypt/encryption_io.rb index ef713a90fc..2ed63efc79 100644 --- a/lib/mongo/crypt/encryption_io.rb +++ b/lib/mongo/crypt/encryption_io.rb @@ -98,7 +98,7 @@ def collection_info(db_name, filter) raise ArgumentError, 'collection_info requires metadata_client to have been passed to the constructor, but it was not' end - @metadata_client.use(db_name).database.list_collections(filter: filter).first + @metadata_client.use(db_name).database.list_collections(filter: filter, deserialize_as_bson: true).first end # Send the command to mongocryptd to be marked with intent-to-encrypt markings diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index d6a5db77bc..29e5fce84c 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -76,7 +76,7 @@ def initialize(view, result, server, options = {}) @initial_result = result @namespace = result.namespace @remaining = limit if limited? - @cursor_id = result.cursor_id + set_cursor_id(result) if @cursor_id.nil? raise ArgumentError, 'Cursor id must be present in the result' end @@ -465,7 +465,7 @@ def process(result) # Thus we need to check both @cursor_id and the cursor_id of the result # prior to calling unregister here. unregister if !closed? && result.cursor_id == 0 - @cursor_id = result.cursor_id + @cursor_id = set_cursor_id(result) if result.respond_to?(:post_batch_resume_token) @post_batch_resume_token = result.post_batch_resume_token @@ -503,6 +503,24 @@ def execute_operation(op) ) op.execute(@server, context: context) end + + # Sets @cursor_id from the operation result. + # + # In the operation result cursor id can be represented either as Integer + # value or as BSON::Int64. This method ensures that the instance variable + # is always of type Integer. + # + # @param [ Operation::Result ] result The result of the operation. + # + # @api private + def set_cursor_id(result) + @cursor_id = if result.cursor_id.is_a?(BSON::Int64) + result.cursor_id.value + else + result.cursor_id + end + end + end end diff --git a/lib/mongo/database/view.rb b/lib/mongo/database/view.rb index 2bcab546b5..a7f16f3213 100644 --- a/lib/mongo/database/view.rb +++ b/lib/mongo/database/view.rb @@ -104,6 +104,9 @@ def collection_names(options = {}) # See https://mongodb.com/docs/manual/reference/command/listCollections/ # for more information and usage. # @option options [ Session ] :session The session to use. + # @option options [ Boolean ] :deserialize_as_bson Whether to deserialize + # this message using BSON types instead of native Ruby types wherever + # possible. # # @return [ Array ] Info for each collection in the database. # @@ -196,8 +199,42 @@ def initial_query_op(session, options = {}) Operation::CollectionsInfo.new(collections_info_spec(session, options)) end + # Sends command that obtains information about the database. + # + # This command returns a cursor, so there could be additional commands, + # therefore this method is called send *initial* command. + # + # @param [ Server ] server Server to send the query to. + # @param [ Session ] session Session that should be used to send the query. + # @param [ Hash ] options + # @option options [ Hash | nil ] :filter A query expression to filter + # the list of collections. + # @option options [ true | false | nil ] :name_only A flag to indicate + # whether the command should return just the collection/view names + # and type or return both the name and other information. + # @option options [ true | false | nil ] :authorized_collections A flag, + # when set to true and used with name_only: true, that allows a user + # without the required privilege (i.e. listCollections + # action on the database) to run the command when access control + # is enforced. + # @option options [ Object | nil ] :comment A user-provided comment to attach + # to this command. + # @option options [ true | false | nil ] :deserialize_as_bson Whether the + # query results should be deserialized to BSON types, or to Ruby + # types (where possible). + # + # @return [ Operation::Result ] Result of the query. def send_initial_query(server, session, options = {}) - initial_query_op(session, options).execute(server, context: Operation::Context.new(client: client, session: session)) + opts = options.dup + execution_opts = {} + if opts.key?(:deserialize_as_bson) + execution_opts[:deserialize_as_bson] = opts.delete(:deserialize_as_bson) + end + initial_query_op(session, opts).execute( + server, + context: Operation::Context.new(client: client, session: session), + options: execution_opts + ) end end end diff --git a/spec/runners/crud/operation.rb b/spec/runners/crud/operation.rb index a702a6120c..7010bb735d 100644 --- a/spec/runners/crud/operation.rb +++ b/spec/runners/crud/operation.rb @@ -32,7 +32,7 @@ def initialize(crud_test, spec, outcome_spec = nil) @spec = IceNine.deep_freeze(spec) @name = spec['name'] if spec['arguments'] - @arguments = BSON::ExtJSON.parse_obj(spec['arguments']) + @arguments = BSON::ExtJSON.parse_obj(spec['arguments'], mode: :bson) else @arguments = {} end diff --git a/spec/runners/crud/spec.rb b/spec/runners/crud/spec.rb index 5a3b53b576..cb20cb14fc 100644 --- a/spec/runners/crud/spec.rb +++ b/spec/runners/crud/spec.rb @@ -20,7 +20,7 @@ def initialize(test_path) # Introduced with Client-Side Encryption tests @json_schema = BSON::ExtJSON.parse_obj(@spec['json_schema']) @key_vault_data = BSON::ExtJSON.parse_obj(@spec['key_vault_data']) - @encrypted_fields = BSON::ExtJSON.parse_obj(@spec['encrypted_fields']) + @encrypted_fields = BSON::ExtJSON.parse_obj(@spec['encrypted_fields'], mode: :bson) @requirements = if run_on = @spec['runOn'] run_on.map do |spec| diff --git a/spec/runners/crud/verifier.rb b/spec/runners/crud/verifier.rb index 27483b444c..147d9ba16f 100644 --- a/spec/runners/crud/verifier.rb +++ b/spec/runners/crud/verifier.rb @@ -105,7 +105,7 @@ def verify_command_started_event(expected_events, actual_events, i) end end expect(actual_event).not_to be nil - expect(actual_event.keys).to eq(expected_event.keys) + expect(actual_event.keys.sort).to eq(expected_event.keys.sort) expected_command = expected_event.delete('command') actual_command = actual_event.delete('command') @@ -144,7 +144,7 @@ def verify_result(expected, actual) expected.each do |k, v| case k when 'errorContains' - expect(actual['errorContains']).to include(v) + expect(actual['errorContains'].downcase).to include(v.downcase) when 'errorLabelsContain' v.each do |label| expect(actual['errorLabels']).to include(label) diff --git a/spec/runners/transactions/test.rb b/spec/runners/transactions/test.rb index 3f6e8ab298..26db187bd7 100644 --- a/spec/runners/transactions/test.rb +++ b/spec/runners/transactions/test.rb @@ -72,14 +72,17 @@ def initialize(crud_spec, data, test, expectations_bson_types: true) end mode = if expectations_bson_types then :bson else nil end + if crud_spec.description == 'fle2-CreateCollection.yml' + mode = nil + end @expectations = BSON::ExtJSON.parse_obj(test['expectations'], mode: mode) if test['outcome'] - @outcome = Mongo::CRUD::Outcome.new(BSON::ExtJSON.parse_obj(test['outcome'], mode: :bson)) + @outcome = Mongo::CRUD::Outcome.new(BSON::ExtJSON.parse_obj(test['outcome'], mode: mode)) end @expected_results = operations.map do |o| - o = BSON::ExtJSON.parse_obj(o) + o = BSON::ExtJSON.parse_obj(o, mode: :bson) # We check both o.key('error') and o['error'] to provide a better # error message in case error: false is ever needed in the tests diff --git a/spec/spec_tests/client_side_encryption_spec.rb b/spec/spec_tests/client_side_encryption_spec.rb index d96007ab70..7f5000fd60 100644 --- a/spec/spec_tests/client_side_encryption_spec.rb +++ b/spec/spec_tests/client_side_encryption_spec.rb @@ -10,5 +10,5 @@ require_libmongocrypt require_enterprise - define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: false) + define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: true) end diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Aggregate.yml new file mode 100644 index 0000000000..4d17344dc5 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Aggregate.yml @@ -0,0 +1,227 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDate": { $gt: {$date: {$numberLong: "0" }}} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Correctness.yml new file mode 100644 index 0000000000..65382eec6b --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Correctness.yml @@ -0,0 +1,420 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" } } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" } } } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gte: { $date: { $numberLong: "0" } } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "1" } } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $lt: { $date: { $numberLong: "1" } } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $lte: { $date: { $numberLong: "1" } } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $lt: { $date: { $numberLong: "0" } } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "200" } } }} + result: + errorContains: must be less than the range maximum + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } }, $lt: { $date: {$numberLong: "2"}} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $date: { $numberLong: "0" } } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDate: { $date: { $numberLong: "1" } } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gte: { $date: {$numberLong: "0"}}, $lte: { $date: {$numberLong: "200"} } } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $in: [ {$date: {$numberLong: "0"}} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedDate: {$date: { $numberLong: "-1" }}} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedDate: { $date: { $numberLong: "200" } }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gte: { $date: { $numberLong: "0" } } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "1" } } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $lt: { $date: { $numberLong: "1" } } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $lte: { $date: { $numberLong: "1" } } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $lt: { $date: { $numberLong: "0" } } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "200" } } }} } + result: + errorContains: must be less than the range maximum + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "0" } }, $lt: { $date: {$numberLong: "2"}} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $date: { $numberLong: "0" } } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $date: { $numberLong: "1" } } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gte: {$date: {$numberLong: "0"}}, $lte: {$date: {$numberLong: "200"}} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $in: [ {$date: {$numberLong: "0"}} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDate: { $numberDouble: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDate: { $gte: { $numberDouble: "0" } }} + result: + # expect an error mongocryptd. + errorContains: "value type is a date" \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Delete.yml new file mode 100644 index 0000000000..6bb2535bb4 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Delete.yml @@ -0,0 +1,185 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } + - name: deleteOne + arguments: + filter: { "encryptedDate": { $gt: {$date: {$numberLong: "0" }}} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDate": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-FindOneAndUpdate.yml new file mode 100644 index 0000000000..d220bf8ddf --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-FindOneAndUpdate.yml @@ -0,0 +1,243 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } + - name: findOneAndUpdate + arguments: + filter: { encryptedDate: { $gt: {$date: {$numberLong: "0"}}} } + update: { "$set": { "encryptedDate": {$date: {$numberLong: "2"}}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDate": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDate": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-InsertFind.yml new file mode 100644 index 0000000000..50a06cedaf --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-InsertFind.yml @@ -0,0 +1,221 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" }} } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" }} } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" }} } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Update.yml new file mode 100644 index 0000000000..ba327caad7 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Update.yml @@ -0,0 +1,260 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" } }} + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" } }} + - name: updateOne + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } } }} + update: { "$set": { "encryptedDate": { $date: { $numberLong: "2" } }}} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDate": { $$type: "binData" } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": *encrypted_fields + }, + "deleteTokens": { + "default.default": { + "encryptedDate": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + }, + "$db": "default" + } + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Aggregate.yml new file mode 100644 index 0000000000..20c9cfd51e --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Aggregate.yml @@ -0,0 +1,1673 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDecimal": { $gt: {$numberDecimal: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDecimal": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", + "subType": "00" + } + } + ] + } + \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Correctness.yml new file mode 100644 index 0000000000..03df38175f --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Correctness.yml @@ -0,0 +1,291 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1.0" } } + - name: find + arguments: + filter: { encryptedDecimal: { $gt: { $numberDecimal: "0.0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimal: { $gte: { $numberDecimal: "0.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimal: { $gt: { $numberDecimal: "1.0" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimal: { $lt: { $numberDecimal: "1.0" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimal: { $lte: { $numberDecimal: "1.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimal: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimal: { $numberDecimal: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDecimal: { $numberDecimal: "1.0" } } + result: [*doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimal: { $in: [ {$numberDecimal: "0.0"} ] } } + result: [*doc0] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimal: { $gte: { $numberDecimal: "0.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimal: { $gt: { $numberDecimal: "1.0" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimal: { $lt: { $numberDecimal: "1.0" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimal: { $lte: { $numberDecimal: "1.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimal: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimal: { $numberDecimal: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimal: { $numberDecimal: "1.0" } } } + result: [*doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimal: { $in: [ {$numberDecimal: "0.0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDecimal: { $numberInt: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDecimal: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Delete.yml new file mode 100644 index 0000000000..fa2daa78e6 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Delete.yml @@ -0,0 +1,908 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDecimal": { $gt: {$numberDecimal: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDecimal": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDecimal": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-FindOneAndUpdate.yml new file mode 100644 index 0000000000..3b772cd6d7 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-FindOneAndUpdate.yml @@ -0,0 +1,1688 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDecimal: { $gt: {$numberDecimal: "0"}} } + update: { "$set": { "encryptedDecimal": {$numberDecimal: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDecimal": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDecimal": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDecimal": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-InsertFind.yml new file mode 100644 index 0000000000..48d44264a8 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-InsertFind.yml @@ -0,0 +1,1666 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + - name: find + arguments: + filter: { encryptedDecimal: { $gt: { $numberDecimal: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDecimal": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Update.yml new file mode 100644 index 0000000000..99ed254d94 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Update.yml @@ -0,0 +1,1705 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + - name: updateOne + arguments: + filter: { encryptedDecimal: { $gt: { $numberDecimal: "0" } } } + update: { "$set": { "encryptedDecimal": { $numberDecimal: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDecimal": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDecimal": { $$type: "binData" } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": *encrypted_fields + }, + "deleteTokens": { + "default.default": { + "encryptedDecimal": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + }, + "$db": "default" + } + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimal": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Aggregate.yml new file mode 100644 index 0000000000..26f7994753 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Aggregate.yml @@ -0,0 +1,315 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDecimalPrecision": { $gt: {$numberDecimal: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Correctness.yml new file mode 100644 index 0000000000..9e2ee80aed --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Correctness.yml @@ -0,0 +1,422 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1.0" } } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gte: { $numberDecimal: "0.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "1.0" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "1.0" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $lte: { $numberDecimal: "1.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "0.0" } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "200.0" } }} + result: + errorContains: must be less than the range max + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $numberDecimal: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $numberDecimal: "1.0" } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gte: {$numberDecimal: "0.0"}, $lte: {$numberDecimal: "200.0"} } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "-1" }} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedDecimalPrecision: { $numberDecimal: "200.0" }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gte: { $numberDecimal: "0.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "1.0" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "1.0" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $lte: { $numberDecimal: "1.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "0.0" } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "200.0" } }} } + result: + errorContains: must be less than the range max + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $numberDecimal: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $numberDecimal: "1.0" } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gte: {$numberDecimal: "0.0"}, $lte: {$numberDecimal: "200.0"} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDecimalPrecision: { $numberInt: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Delete.yml new file mode 100644 index 0000000000..5603f6d34c --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Delete.yml @@ -0,0 +1,229 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDecimalPrecision": { $gt: {$numberDecimal: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDecimalPrecision": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-FindOneAndUpdate.yml new file mode 100644 index 0000000000..c79f5b9b7f --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-FindOneAndUpdate.yml @@ -0,0 +1,331 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDecimalPrecision: { $gt: {$numberDecimal: "0"}} } + update: { "$set": { "encryptedDecimalPrecision": {$numberDecimal: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDecimalPrecision": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDecimalPrecision": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-InsertFind.yml new file mode 100644 index 0000000000..2bdd29cdb3 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-InsertFind.yml @@ -0,0 +1,305 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Update.yml new file mode 100644 index 0000000000..e852a30c61 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Update.yml @@ -0,0 +1,344 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: updateOne + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0" } } } + update: { "$set": { "encryptedDecimalPrecision": { $numberDecimal: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDecimalPrecision": { $$type: "binData" } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": *encrypted_fields + }, + "deleteTokens": { + "default.default": { + "encryptedDecimalPrecision": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + }, + "$db": "default" + } + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Aggregate.yml new file mode 100644 index 0000000000..67598094c1 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Aggregate.yml @@ -0,0 +1,899 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDouble": { $gt: {$numberDouble: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDouble": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Correctness.yml new file mode 100644 index 0000000000..bca46f2c66 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Correctness.yml @@ -0,0 +1,290 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1.0" } } + - name: find + arguments: + filter: { encryptedDouble: { $gt: { $numberDouble: "0.0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDouble: { $gte: { $numberDouble: "0.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDouble: { $gt: { $numberDouble: "1.0" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDouble: { $lt: { $numberDouble: "1.0" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDouble: { $lte: { $numberDouble: "1.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDouble: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDouble: { $numberDouble: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDouble: { $numberDouble: "1.0" } } + result: [*doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDouble: { $in: [ {$numberDouble: "0.0"} ] } } + result: [*doc0] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDouble: { $gte: { $numberDouble: "0.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDouble: { $gt: { $numberDouble: "1.0" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDouble: { $lt: { $numberDouble: "1.0" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDouble: { $lte: { $numberDouble: "1.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDouble: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDouble: { $numberDouble: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDouble: { $numberDouble: "1.0" } } } + result: [*doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDouble: { $in: [ {$numberDouble: "0.0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDouble: { $numberInt: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDouble: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Delete.yml new file mode 100644 index 0000000000..5fc9680ae0 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Delete.yml @@ -0,0 +1,521 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDouble": { $gt: {$numberDouble: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDouble": { + "$gt": { + "$binary": { + "base64": "Cq8kAAADcGF5bG9hZAB/JAAABGcAQyQAAAMwAH0AAAAFZAAgAAAAAHgYoMGjEE6fAlAhICv0+doHcVX8CmMVxyq7+jlyGrvmBXMAIAAAAAC/5MQZgTHuIr/O5Z3mXPvqrom5JTQ8IeSpQGhO9sB+8gVjACAAAAAAPMgD8Rqnd94atKnMyPjlTwthUQ710MKJVqgtwNXLFWwAAzEAfQAAAAVkACAAAAAA2kiWNvEc4zunJ1jzvuClFC9hjZMYruKCqAaxq+oY8EAFcwAgAAAAACofIS72Cm6s866UCk+evTH3CvKBj/uZd72sAL608rzTBWMAIAAAAAB7NkTFgsBuPIyxW5KtYZD4uxdgRynoAUwA2RDj3qZNdwADMgB9AAAABWQAIAAAAABkfoBGmU3hjYBvQbjNW19kfXneBQsQQPRfUL3UAwI2cAVzACAAAAAAUpK2BUOqX/DGdX5YJniEZMWkofxHqeAbXceEGJxhp8AFYwAgAAAAAENNdd+fMV7VQzshj6RdvsxqDHYkPDxggdUHw3JbmFBeAAMzAH0AAAAFZAAgAAAAAFNprhQ3ZwIcYbuzLolAT5n/vc14P9kUUQComDu6eFyKBXMAIAAAAAAcx9z9pk32YbPV/sfPZl9ALIEVsqoLXgqWLVK/tP+heAVjACAAAAAA3eqduo9CWE5BePqBq7KCh5++QqXnyjCwybB15Zoeiu4AAzQAfQAAAAVkACAAAAAAODI+pB2pCuB+YmNEUAgtMfNdt3DmSkrJ96gRzLphgb8FcwAgAAAAAAT7dewFDxUDECQ3zVq75/cUN4IP+zsqhkP5+czUwlJIBWMAIAAAAABYcCJA2jocGQiF/vZ9VDEktoK015OqfY64yZhmNPs9GQADNQB9AAAABWQAIAAAAACNAk+yTZ4Ewk1EnotQK8O3h1gg9I7pr9q2+4po1iJVgAVzACAAAAAAUj/LesmtEsgqYVzMJ67umVA11hJTdDXwbxDoQ71vWyUFYwAgAAAAAG32XaKjEO9YTSLMnDR5eiaUpgBQz0n4M3VpBqhWTaEnAAM2AH0AAAAFZAAgAAAAAD/FIrGYFDjyYmVb7oTMVwweWP7A6F9LnyIuNO4MjBnXBXMAIAAAAACIZgJCQRZu7NhuNMyOqCn1tf+DfU1qm10TPCfj5JYV3wVjACAAAAAAZCaO06/DjOBjNrTxB8jdxM7X4XvNu9QTa2Y00kZJwgEAAzcAfQAAAAVkACAAAAAAciRW40ORJLVwchOEpz87Svb+5toAFM6LxDWv928ECwQFcwAgAAAAAN0dipyESIkszfjRzdDi8kAGaa2Hf4wrPAtiWwboZLuxBWMAIAAAAADhvKHhF7zfNRnG8QfiIlKycV6L6iGxbGNKkHligA9rAgADOAB9AAAABWQAIAAAAACZqAyCzYQupJ95mrBJX54yIz9VY7I0WrxpNYElCI4dTQVzACAAAAAA/eyJb6d1xfE+jJlVXMTD3HS/NEYENPVKAuj56Dr2dSEFYwAgAAAAAMfcJKj77c6y/RBQ5hb5LSNdvbYvCAHR59KLnbmxj1gAAAM5AH0AAAAFZAAgAAAAAI5bm3YO0Xgf0VT+qjVTTfvckecM3Cwqj7DTKZXf8/NXBXMAIAAAAAD/m+h8fBhWaHm6Ykuz0WX1xL4Eme3ErLObyEVJf8NCywVjACAAAAAAiGdTXD22l1zDxHeF4NXUTiBnNTsdpzJRwM6riTPuOogAAzEwAH0AAAAFZAAgAAAAANqo4+p6qdtCzcB4BX1wQ6llU7eFBnuu4MtZwp4B6mDlBXMAIAAAAAAGiz+VaukMZ+6IH4jtn4KWWdKK4/W+O+gRioQDrfzpMgVjACAAAAAAl/wlBjSJW/hKkll8HSBCGw4Ce1pJ5rZuhVE09hKq4jQAAzExAH0AAAAFZAAgAAAAAPrFXmHP2Y4YAm7b/aqsdn/DPoDkv7B8egWkfe23XsM1BXMAIAAAAAAGhwpKAr7skeqHm3oseSbO7qKNhmYsuUrECBxJ5k+D2AVjACAAAAAAKSsxERy2OBhb5MiH9/H2BET2oeU6yVihiAoWi/16FroAAzEyAH0AAAAFZAAgAAAAABzjYxwAjXxXc0Uxv18rH8I3my0Aguow0kTwKyxbrm+cBXMAIAAAAADVbqJVr6IdokuhXkEtXF0C2gINLiAjMVN20lE20Vmp2QVjACAAAAAACJ+VAwl9X88mjC76yzkWeL4K6AamNYjbNkpS9B6fQE4AAzEzAH0AAAAFZAAgAAAAAFMm2feF2fFCm/UC6AfIyepX/xJDSmnnolQIBnHcPmb5BXMAIAAAAABLI11kFrQoaNVZFmq/38aRNImPOjdJh0Lo6irI8M/AaAVjACAAAAAAZFgYh4nV8YN+/AAqe/QhKDkNBPg8KraCG7y3bOzhPV4AAzE0AH0AAAAFZAAgAAAAAJvXB3KyNiNtQko4SSzo/9b2qmM2zU9CQTTDfLSBWMgRBXMAIAAAAAAvjuVP7KsLRDeqVqRziTKpBrjVyqKiIbO9Gw8Wl2wFTAVjACAAAAAARuBn3JdQ8so7Gy0XVH0CtlvtRoJWhSrJP6eRIqbyWh8AAzE1AH0AAAAFZAAgAAAAAPGdcxDiid8z8XYnfdDivNMYVPgBKdGOUw6UStU+48CdBXMAIAAAAAARj6g1Ap0eEfuCZ4X2TsEw+Djrhto3fA5nLwPaY0vCTgVjACAAAAAATxLY6SkcLPaoOBJpQsggEqoxgJgiY9seP3fBQM05PckAAzE2AH0AAAAFZAAgAAAAAP5rGPrYGt3aKob5f/ldP0qrW7bmWvqnKY4QwdDWz400BXMAIAAAAADTQkW2ymaaf/bhteOOGmSrIR97bAnJx+yN3yMj1bTeewVjACAAAAAA6O+wIMt3xMITuCxVrqOCNBPX5F122G+/Is+EYkzUvVYAAzE3AH0AAAAFZAAgAAAAAAlz6wJze5UkIxKpJOZFGCOf3v2KByWyI6NB6JM9wNcBBXMAIAAAAABUC7P/neUIHHoZtq0jFVBHY75tSFYr1Y5S16YN5XxC1QVjACAAAAAANv97jMd9JmZ7lMeCzK7jaZHZYsZJNl6N9g9WXzd2om0AAzE4AH0AAAAFZAAgAAAAAFJ8AtHcjia/9Y5pLEc3qVgH5xKiXw12G9Kn2A1EY8McBXMAIAAAAAAxe7Bdw7eUSBk/oAawa7uicTEDgXLymRNhBy1LAxhDvwVjACAAAAAAtOUZ2NeUWwESpq95O92dhqYBt8RtFnjT43E7k9mQF3oAAzE5AH0AAAAFZAAgAAAAAO8uwQUaKFb6vqR3Sv3Wn4QAonC2exOC9lGG1juqP5DtBXMAIAAAAABZf1KyJgQg8/Rf5c02DgDK2aQu0rNCOvaL60ohDHyY+gVjACAAAAAA5nAWj1Ek8p0MLYZEB3yoVFDfBYZ+/ZpIo71u+W9hprcAAzIwAH0AAAAFZAAgAAAAAE8YtqyRsGCeiR6hhiyisR/hccmK4nZqIMzO4lUBmEFzBXMAIAAAAAC1UYOSKqAeG1UJiKjWFVskRhuFKpj9Ezy+lICZvFlN5AVjACAAAAAA37oP30mTzVftI2FL9Uwyls/jqLqbmRDQk7p7nlx44uYAAzIxAH0AAAAFZAAgAAAAAPhCrMausDx1QUIEqp9rUdRKyM6a9AAx7jQ3ILIu8wNIBXMAIAAAAACmH8lotGCiF2q9VQxhsS+7LAZv79VUAsOUALaGxE/EpAVjACAAAAAAhVIkl8p19VZ0gpg32s++Jda3qsVSVB5tV4CKrtjhE3IAAzIyAH0AAAAFZAAgAAAAAOBi/GAYFcstMSJPgp3VkMiuuUUCrZytvqYaU8dwm8v2BXMAIAAAAACEZSZVyD3pKzGlbdwlYmWQhHHTV5SnNLknl2Gw8IaUTQVjACAAAAAAriBex2kK//RPhyVpCYJDBng4l5w8jD3m8BF7dVAP0p8AAzIzAH0AAAAFZAAgAAAAAIQuup+YGfH3mflzWopN8J1X8o8a0d9CSGIvrA5HOzraBXMAIAAAAADYvNLURXsC2ITMqK14LABQBI+hZZ5wNf24JMcKLW+84AVjACAAAAAAGeUzdG2kQrx5XypXJezZmPVzMYuqYZw7Bhbl4EPT0SMAAzI0AH0AAAAFZAAgAAAAAKsh0ADyOnVocFrOrf6MpTrNvAj8iaiE923DPryu124gBXMAIAAAAADg24a8NVE1GyScc6tmnTbmu5ulzO+896fE92lN08MeswVjACAAAAAAbO+DEBY3STVMQN7CbxmHDUBYrDR+80e797u/VmiK4vcAAzI1AH0AAAAFZAAgAAAAAKkVC2Y6HtRmv72tDnPUSjJBvse7SxLqnr09/Uuj9sVVBXMAIAAAAABYNFUkH7ylPMN+Bc3HWX1e0flGYNbtJNCY9SltJCW/UAVjACAAAAAAHlE3RLKcXpXto7Mr8nRgzEsmhjfGh4vcgPxashCSMg4AAzI2AH0AAAAFZAAgAAAAADDggcwcb/Yn1Kk39sOHsv7BO/MfP3m/AJzjGH506Wf9BXMAIAAAAAAYZIsdjICS0+BDyRUPnrSAZfPrwtuMaEDEn0/ijLNQmAVjACAAAAAA1c/sy3255NofBQrnBNSmVkSzMgsGPaaOUJShddVrnuQAAzI3AH0AAAAFZAAgAAAAAEWY7dEUOJBgjOoWVht1wLehsWAzB3rSOBtLgTuM2HC8BXMAIAAAAAAAoswiHRROurjwUW8u8D5EUT+67yvrgpB/j6PzBDAfVwVjACAAAAAALBvQumG8m7/bzJjGWN2cHSAncdN8jMtOSmEhEhGom24AAzI4AH0AAAAFZAAgAAAAAPZaapeAUUFPA7JTCMOWHJa9lnPFh0/gXfAPjA1ezm4ZBXMAIAAAAACmJvLY2nivw7/b3DOKH/X7bBXjJwoowqb1GtEFO3OYgAVjACAAAAAAGCDHuUJusGHKQ+r9nrFChmUUsRcqZKPGsRiLSk5gbFcAAzI5AH0AAAAFZAAgAAAAAK00u6jadxCZAiA+fTsPVDsnW5p5LCr4+kZZZOTDuZlfBXMAIAAAAAAote4zTEYMDgaaQbAdN8Dzv93ljPLdGjJzvnRn3KXgtQVjACAAAAAApZ6l+OrocOqgFek7WOqOP9JruTWZ+iW+5zdL3DZwzhkAAzMwAH0AAAAFZAAgAAAAAFbgabdyymiEVYYwtJSWa7lfl/oYuj/SukzJeDOR6wPVBXMAIAAAAADAFGFjS1vPbN6mQEhkDYTD6V2V23Ys9gUEUMGNvMPkaAVjACAAAAAAeICKly5Xtwmfd4JFD+5e1UWpu4V3KoRim7jeBICDs+UAAzMxAH0AAAAFZAAgAAAAABNMR6UBv2E627CqLtQ/eDYx7OEwQ7JrR4mSHFa1N8tLBXMAIAAAAAAxH4gucI4UmNVB7625C6hFSVCuIpJO3lusJlPuL8H5EQVjACAAAAAA0/uh1hrAlGGna/njCVaCqBwubxkifzF0zjCDQrIJOtUAAzMyAH0AAAAFZAAgAAAAAG8cd6WBneNunlqrQ2EmNf35W7OGObGq9WL4ePX+LUDmBXMAIAAAAAAjJ2+sX87NSis9hBsgb1QprVRnO7Bf+GObCGoUqyPE4wVjACAAAAAAAoJ4Ufdq45T9Aun5FupHlaBCIUsmUn6dXgV9KorpFikAAzMzAH0AAAAFZAAgAAAAAFWOUGkUpy8yf6gB3dio/aOfRKh7XuhvsUj48iESFJrGBXMAIAAAAAAY7sCDMcrUXvNuL6dO0m11WyijzXZvPIcOKob6IpC4PQVjACAAAAAAO/QFjczoznH95Iu0YEVMsU1GA1yxSGL8bcwSweNzAtkAAzM0AH0AAAAFZAAgAAAAAGUrIdKxOihwNmo6B+aG+Ag1qa0+iqdksHOjQj+Oy9bZBXMAIAAAAABwa5dbI2KmzBDNBTQBEkjZv4sPaeRkRNejcjdVymRFKQVjACAAAAAAUtdJucZoQvvPJiy65RonQrxBCcvtfHslpbgLbtWirCYAAzM1AH0AAAAFZAAgAAAAAOx89xV/hRk64/CkM9N2EMK6aldII0c8smdcsZ46NbP8BXMAIAAAAADBF6tfQ+7q9kTuLyuyrSnDgmrdmrXkdhl980i1KHuGHgVjACAAAAAAVc5njbLX1afpsuG662U/OBo4LanEk06lKbQtd95fswgAAzM2AH0AAAAFZAAgAAAAAMkN0L1oQWXhjwn9rAdudcYeN8/5VdCKU8cmDt7BokjsBXMAIAAAAAAT62pGXoRwExe9uvgYOI0hg5tOxilrWfoEmT0SMglWJwVjACAAAAAAhNc5ovIaxgtS856ZBfG+cAcHAz+3ewzB9C6zZoaggj4AAzM3AH0AAAAFZAAgAAAAANxfa4xCoaaB7k1C1RoH1LBhsCbN2yEq15BT9b+iqEC4BXMAIAAAAACAX9LV8Pemfw7NF0iB1/85NzM1Ef+1mUfyehacUVgobQVjACAAAAAAc0bIRNzuyc1Y0hkEmm7xV6Xp/LBlAgMq/ISCHr0Tq44AAzM4AH0AAAAFZAAgAAAAAOYIYoWkX7dGuyKfi3XssUlc7u/gWzqrR9KMkikKVdmSBXMAIAAAAABVF2OYjRTGi9Tw8XCAwZWLpX35Yl271TlNWp6N/nROhAVjACAAAAAAyGLbmHhe8eTQC8L2eDRcezUWULLZ9E8DPic7Mfp8/+4AAzM5AH0AAAAFZAAgAAAAAIy0+bXZi10QC+q7oSOLXK5Fee7VEk/qHSXukfeVIfgzBXMAIAAAAAAQ3IIV/JQCHW95AEbH5zGIHtJqyuPjWPMIZ+VmQHlxEwVjACAAAAAAD3azcNDdohU6tHO6ypT7F0rtydAYIMmLIsX6y7cXVkMAAzQwAH0AAAAFZAAgAAAAAL5SOJQ3LOhgdXJ5v086NNeAl1qonQnchObdpZJ1kHeEBXMAIAAAAAA+tEqTXODtik+ydJZSnUqXF9f18bPeze9eWtR7ExZJgQVjACAAAAAAUd8v2RN1WagYan1LbV+G81vcJKQ6HsX2GSBtnzMH7XEAAzQxAH0AAAAFZAAgAAAAAKl8zcHJRDjSjJeV/WvMxulW1zrTFtaeBy/aKKhadc6UBXMAIAAAAADBdWQl5SBIvtZZLIHszePwkO14W1mQ0izUk2Ov21cPNAVjACAAAAAAHvQLJ2X4ncjVv1BsOd/jbVouRPwf4222WlGSzPyAfnsAAzQyAH0AAAAFZAAgAAAAAFvotcNaoKnVt5CBCOPwjexFO0WGWuaIGL6H/6KSau+6BXMAIAAAAAD2y2mBN5xPu5PJoY2zcr0GnQDtHRBogA5+xzIxccE9fwVjACAAAAAA1OntPAxq1NJ4sfca+ysHcg68mj7QHwdEZQVxHMlV6x0AAzQzAH0AAAAFZAAgAAAAAPezU0/vNT4Q4YKbTbaeHqcwNLT+IjW/Y9QFpIooihjPBXMAIAAAAACj2x4O4rHter8ZnTws5LAP9jJ/6kk9C/V3vL50LoFZHAVjACAAAAAAlZd3bPMUXRhTwLWflrQuKIwtauTswenUiaDi2ibHYYYAAzQ0AH0AAAAFZAAgAAAAAMqpayM2XotEFmm0gwQd9rIzApy0X+7HfOhNk6VU7F5lBXMAIAAAAACJR9+q5T9qFHXFNgGbZnPubG8rkO6cwWhzITQTmd6VgwVjACAAAAAAEEOCUo+ihRGl1kuHlabWBWUFyJPAXSXzpQB4od57cMEAAzQ1AH0AAAAFZAAgAAAAANCeyW+3oebaQk+aqxNVhAcT/BZ5nhsTVdKS3tMrLSvWBXMAIAAAAADxRFMDhkyuEc++WnndMfoUMLNL7T7rWoeblcrpSI6soQVjACAAAAAA1KYQISxmBcGmgDMAjBpTcfQr/oci60Kf4QtEYEXPKSQAAzQ2AH0AAAAFZAAgAAAAAIbo5YBTxXM7HQhl7UP9NNgpPGFkBx871r1B65G47+K8BXMAIAAAAAC21dJSxnEhnxO5gzN5/34BL4von45e1meW92qowzb8fQVjACAAAAAAisxsskU/9ykphpSjB6bMMY+6Tbt6wpjhE2dg1IULAgYAAzQ3AH0AAAAFZAAgAAAAABm/6pF96j26Jm7z5KkY1y33zcAEXLx2n0DwC03bs/ixBXMAIAAAAAD01OMvTZI/mqMgxIhA5nLs068mW+GKl3OW3ilf2D8+LgVjACAAAAAAnPa8PTfcaSSAdzNAC54IMTicbShbtt/cSnFHz7u7g8wAAzQ4AH0AAAAFZAAgAAAAAJ/D3+17gaQdkBqkL2wMwccdmCaVOtxzIkM8VyI4xI5zBXMAIAAAAAAggLVmkc5u+YzBR+oNE+XgLVp64fC6MzUb/Ilu/Jsw0AVjACAAAAAAp5qCK8p0eTvAAUQ8fIOGyb2YM1YflQ8H6ggiWZ9vNI4AAzQ5AH0AAAAFZAAgAAAAAJMRyUW50oaTzspS6A3TUoXyC3gNYQoShUGPakMmeVZrBXMAIAAAAACona2Pqwt4U2PmFrtmu37jB9kQ/12okyAVtYa8TQkDiQVjACAAAAAAx/LOuDwvpaPFJeKLowZW/4q7SUsLtdldB27zc4//alEAAzUwAH0AAAAFZAAgAAAAAByuYl8dBvfaZ0LO/81JW4hYypeNmvLMaxsIdvqMPrWoBXMAIAAAAABNddwobOUJzm9HOUD8BMZJqkNCUCqstHZkC76FIdNg9AVjACAAAAAA4SEPY+qEZ2dCu/9485IEVybiM3Z7szXYM+izxklNm14AAzUxAH0AAAAFZAAgAAAAAHEzLtfmF/sBcYPPdj8867VmmQyU1xK9I/3Y0478azvABXMAIAAAAAAcmyFajZPnBTbO+oLInNwlApBocUekKkxz2hYFeSlQ+gVjACAAAAAA15ibZoBP1/pP27BDIfizDYNRDIpOmj8Ie34FvrtqjhMAAzUyAH0AAAAFZAAgAAAAAJam7JYsZe2cN20ZYm2W3v1pisNt5PLiniMzymBLWyMtBXMAIAAAAABxCsKVMZMTn3n+R2L7pVz5nW804r8HcK0mCBw3jUXKXAVjACAAAAAAElCgoMRrtlONqnyosjIpiz4mQTLq3+b/JUCVEq4QFiAAAzUzAH0AAAAFZAAgAAAAAMXrXx0saZ+5gORmwM2FLuZG6iuO2YS+1IGPoAtDKoKBBXMAIAAAAADIQsxCr8CfFKaBcx8kIeSywnGh7JHjKRJ9vJd9x79y7wVjACAAAAAAPzOTJ088k9QdGUpZ1ubUdkw3pTGkNF8bui4a9wqeo08AAzU0AH0AAAAFZAAgAAAAACbzcUD3INSnCRspOKF7ubne74OK9L0FTZvi9Ay0JVDYBXMAIAAAAADPebVQH8Btk9rhBIoUOdSAdpPvz7qIY4UC2i6IGisSAQVjACAAAAAAe4zSAgMQPjtz2t1DgxW1XOlV+fnNMXt6iQ7iV7DgQz4AAzU1AH0AAAAFZAAgAAAAACUc2CtD1MK/UTxtv+8iA9FoHEyTwdl43HKeSwDw2Lp5BXMAIAAAAACCIduIdw65bQMzRYRfjBJj62bc69T4QqH4QoWanwlvowVjACAAAAAA+i6aTnkoixxSOxDgQM/aWTylOy1m0ViCDU5asOM5ieYAAzU2AH0AAAAFZAAgAAAAAHSaHWs/dnmI9sc7nB50VB2Bzs0kHapMHCQdyVEYY30TBXMAIAAAAACkV22lhEjWv/9/DubfHBAcwJggKI5mIbSK5L2nyqloqQVjACAAAAAA1I5pLbnlFf/EfJlhE0RFIioDjzKrWNh4TO8H/ksrbMcAAzU3AH0AAAAFZAAgAAAAAAL8jhNBG0KXXZhmZ0bPXtfgapJCB/AI+BEHB0eZ3C75BXMAIAAAAADHx/fPa639EBmGV5quLi8IQT600ifiKSOhTDOK19DnzwVjACAAAAAANfWpWWy9iu7pgMEy2aVCggGEtJtXKQG1+3zCf8D6iEkAAzU4AH0AAAAFZAAgAAAAAAxgeclNl09H7HvzD1oLwb2YpFca5eaX90uStYXHilqKBXMAIAAAAACMU5pSxzIzWlQxHyW170Xs9EhD1hURASQk+qkx7K5Y6AVjACAAAAAA/kYlJPSFn2g1l7cy6djV/7wgnlPWphZ0IKSY8uenUF0AAzU5AH0AAAAFZAAgAAAAABmO7QD9vxWMmFjIHz13lyOeV6vHT6mYCsWxF7hb/yOjBXMAIAAAAACT9lmgkiqzuWG24afuzYiCeK9gmJqacmxAruIukd0xEAVjACAAAAAAyiZxtuOZQNSit4wFMJS3jnqLbDzyJ0OtDTKs6r0EO0cAAzYwAH0AAAAFZAAgAAAAAB89SjLtDJkqEghRGyj6aQ/2qvWLNuMROoXmzbYbCMKMBXMAIAAAAAC8sywgND+CjhVTF6HnRQeay8y9/HnVzDI42dEPah28LQVjACAAAAAAzBj8q9Ig4uhIc6rhNDVX1lII9lzfhJ0+ig8viBhdrtQAAzYxAH0AAAAFZAAgAAAAABN2alGq9Aats1mwERNGwL/fIwZSvVCe9/8XMHTFlpUpBXMAIAAAAACuDPjJgvvbBYhbLpjMiWUCsVppiYrhvR+yMysNPN8cZAVjACAAAAAAqcsLIvb3Pe/QKYB9wQ86Ye2OFSAJEIY4ve7LMM3SwGMAAzYyAH0AAAAFZAAgAAAAAL8YB6VAqGBiWD4CBv16IBscg5J7VQCTZu87n6pj+86KBXMAIAAAAAAmxm8e68geeyAdUjSMWBHzUjneVB0pG9TBXIoE6467hAVjACAAAAAA9Kd65vKIUckNPvxl/HHNPJVnFHXyErp4qibmD7BloukAAzYzAH0AAAAFZAAgAAAAAL4yLkCTV5Dmxa5toBu4JT8ge/cITAaURIOuFuOtFUkeBXMAIAAAAAAXoFNQOMGkAj7qEJP0wQafmFSXgWGeorDVbwyOxWLIsgVjACAAAAAAaFrBx0xXIryTe7V/kX+jfPHd0057x7k7MxzDAzi0RpcAAzY0AH0AAAAFZAAgAAAAAEU8DoUp46YtYjNFS9kNXwdYxQ9IW27vCTb+VcqqfnKNBXMAIAAAAADe7vBOgYReE8X78k5ARuUnv4GmzPZzg6SbConf4L2G3wVjACAAAAAAOl/tartywd/fJj5DNRsVH/ml9tJ8KkkCbKObsFe8lHcAAzY1AH0AAAAFZAAgAAAAAPa4yKTtkUtySuWo1ZQsp2QXtPb5SYqzA5vYDnS1P6c0BXMAIAAAAADKnF58R1sXlHlsHIvCBR3YWW/qk54z9CTDhZydkD1cOQVjACAAAAAAfn/jP8K3QUILngCNkydHARyBvBHIFdaJjzV0EXsFruMAAzY2AH0AAAAFZAAgAAAAAN5WJnMBmfgpuQPyonmY5X6OdRvuHw4nhsnGRnFAQ95VBXMAIAAAAACwftzu7KVV1rmGKwXtJjs3cJ1gE3apr8+N0SAg1F2cHwVjACAAAAAAf0XyQQLd/HIYaf9EeAV0o2h12K1AV5piLCpZihznBXoAAzY3AH0AAAAFZAAgAAAAACHvDsaPhoSb6DeGnKQ1QOpGYAgK82qpnqwcmzSeWaJHBXMAIAAAAABRq3C5+dOfnkAHM5Mg5hPB3O4jhwQlBgQWLA7Ph5bhgwVjACAAAAAAm2Ukk5kkQp+PDpBCCefQOqFKKZie4hINim3yvtypsEAAAzY4AH0AAAAFZAAgAAAAAOBePJvccPMJmy515KB1AkXF5Pi8NOG4V8psWy0SPRP+BXMAIAAAAAB3dOJG9xIDtEKCRzeNnPS3bFZepMj8UKBobKpSoCPqpgVjACAAAAAAnu33SngYPOtRgdJ3aBBuxWn80ti3OO6nZJcI6eh0VfQAAzY5AH0AAAAFZAAgAAAAABUvRrDQKEXLMdhnzXRdhiL6AGNs2TojPky+YVLXs+JnBXMAIAAAAAD1kYicbEEcPzD4QtuSYQQWDPq8fuUWGddpWayKn3dT9QVjACAAAAAAUc5dxcbNYY3qMO8+Xm2xis+pH9NjPLrHqHenwDEImAEAAzcwAH0AAAAFZAAgAAAAAOvSnpujeKNen4pqc2HR63C5s5oJ1Vf4CsbKoYQvkwl5BXMAIAAAAACw2+vAMdibzd2YVVNfk81yXkFZP0WLJ82JBxJmXnYE+QVjACAAAAAAnmej008JFQAgdImjk26PYvqdATjRYRufLi+vEVVwtucAAzcxAH0AAAAFZAAgAAAAAAo0xfGG7tJ3GWhgPVhW5Zn239nTD3PadShCNRc9TwdNBXMAIAAAAADZh243oOhenu0s/P/5KZLBDh9ADqKHtSWcXpO9D2sIjgVjACAAAAAAhpNHI4+aRdWHCvZfEjlgpRBz36g05wN9p/hj3NEwc7EAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDouble": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-FindOneAndUpdate.yml new file mode 100644 index 0000000000..0b3d39dffa --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-FindOneAndUpdate.yml @@ -0,0 +1,915 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDouble: { $gt: {$numberDouble: "0"}} } + update: { "$set": { "encryptedDouble": {$numberDouble: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDouble": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDouble": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDouble": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-InsertFind.yml new file mode 100644 index 0000000000..87f3f05d0b --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-InsertFind.yml @@ -0,0 +1,893 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + - name: find + arguments: + filter: { encryptedDouble: { $gt: { $numberDouble: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDouble": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Update.yml new file mode 100644 index 0000000000..5f8423b8fe --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Update.yml @@ -0,0 +1,932 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + - name: updateOne + arguments: + filter: { encryptedDouble: { $gt: { $numberDouble: "0" } } } + update: { "$set": { "encryptedDouble": { $numberDouble: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDouble": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDouble": { $$type: "binData" } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": *encrypted_fields + }, + "deleteTokens": { + "default.default": { + "encryptedDouble": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + }, + "$db": "default" + } + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDouble": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Aggregate.yml new file mode 100644 index 0000000000..47ac5d4444 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Aggregate.yml @@ -0,0 +1,311 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDoublePrecision": { $gt: {$numberDouble: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Correctness.yml new file mode 100644 index 0000000000..291ffa58ab --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Correctness.yml @@ -0,0 +1,422 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1.0" } } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gte: { $numberDouble: "0.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "1.0" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $lt: { $numberDouble: "1.0" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $lte: { $numberDouble: "1.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $lt: { $numberDouble: "0.0" } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "200.0" } }} + result: + errorContains: must be less than the range max + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $numberDouble: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDoublePrecision: { $numberDouble: "1.0" } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gte: {$numberDouble: "0.0"}, $lte: {$numberDouble: "200.0"} } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $in: [ {$numberDouble: "0.0"} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedDoublePrecision: { $numberDouble: "-1" }} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedDoublePrecision: { $numberDouble: "200.0" }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gte: { $numberDouble: "0.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "1.0" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $lt: { $numberDouble: "1.0" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $lte: { $numberDouble: "1.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $lt: { $numberDouble: "0.0" } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "200.0" } }} } + result: + errorContains: must be less than the range max + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $numberDouble: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $numberDouble: "1.0" } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gte: {$numberDouble: "0.0"}, $lte: {$numberDouble: "200.0"} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $in: [ {$numberDouble: "0.0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDoublePrecision: { $numberInt: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Delete.yml new file mode 100644 index 0000000000..dd8b862471 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Delete.yml @@ -0,0 +1,227 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDoublePrecision": { $gt: {$numberDouble: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDoublePrecision": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-FindOneAndUpdate.yml new file mode 100644 index 0000000000..a13f878e30 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-FindOneAndUpdate.yml @@ -0,0 +1,327 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDoublePrecision: { $gt: {$numberDouble: "0"}} } + update: { "$set": { "encryptedDoublePrecision": {$numberDouble: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDoublePrecision": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedDoublePrecision": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-InsertFind.yml new file mode 100644 index 0000000000..1cc9969c01 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-InsertFind.yml @@ -0,0 +1,305 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Update.yml new file mode 100644 index 0000000000..116b623ac0 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Update.yml @@ -0,0 +1,344 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: updateOne + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0" } } } + update: { "$set": { "encryptedDoublePrecision": { $numberDouble: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDoublePrecision": { $$type: "binData" } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": *encrypted_fields + }, + "deleteTokens": { + "default.default": { + "encryptedDoublePrecision": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + }, + "$db": "default" + } + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Aggregate.yml new file mode 100644 index 0000000000..163ff4f750 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Aggregate.yml @@ -0,0 +1,227 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedInt": { $gt: {$numberInt: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Correctness.yml new file mode 100644 index 0000000000..db2a1e46a8 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Correctness.yml @@ -0,0 +1,421 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "1" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $lt: { $numberInt: "1" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $lte: { $numberInt: "1" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $lt: { $numberInt: "0" } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "200" } }} + result: + errorContains: must be less than the range maximum + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" }, $lt: { $numberInt: "2"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $numberInt: "0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedInt: { $numberInt: "1" } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gte: {$numberInt: "0"}, $lte: {$numberInt: "200"} } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $in: [ {$numberInt: "0"} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedInt: { $numberInt: "-1" }} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedInt: { $numberInt: "200" }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gte: { $numberInt: "0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gt: { $numberInt: "1" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $lt: { $numberInt: "1" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $lte: { $numberInt: "1" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $lt: { $numberInt: "0" } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gt: { $numberInt: "200" } }} } + result: + errorContains: must be less than the range maximum + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gt: { $numberInt: "0" }, $lt: { $numberInt: "2"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $numberInt: "0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $numberInt: "1" } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gte: {$numberInt: "0"}, $lte: {$numberInt: "200"} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $in: [ {$numberInt: "0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedInt: { $numberDouble: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedInt: { $gte: { $numberDouble: "0" } }} + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Delete.yml new file mode 100644 index 0000000000..51a343493f --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Delete.yml @@ -0,0 +1,185 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedInt": { $gt: {$numberInt: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedInt": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-FindOneAndUpdate.yml new file mode 100644 index 0000000000..433be15d31 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-FindOneAndUpdate.yml @@ -0,0 +1,243 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedInt: { $gt: {$numberInt: "0"}} } + update: { "$set": { "encryptedInt": {$numberInt: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedInt": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedInt": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-InsertFind.yml new file mode 100644 index 0000000000..dc5215eae6 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-InsertFind.yml @@ -0,0 +1,221 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Update.yml new file mode 100644 index 0000000000..8e0bdcb8dc --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Update.yml @@ -0,0 +1,260 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: updateOne + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" } } } + update: { "$set": { "encryptedInt": { $numberInt: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedInt": { $$type: "binData" } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": *encrypted_fields + }, + "deleteTokens": { + "default.default": { + "encryptedInt": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + }, + "$db": "default" + } + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Aggregate.yml new file mode 100644 index 0000000000..8064fbeddd --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Aggregate.yml @@ -0,0 +1,227 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedLong": { $gt: {$numberLong: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Correctness.yml new file mode 100644 index 0000000000..696a09fa10 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Correctness.yml @@ -0,0 +1,420 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gte: { $numberLong: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "1" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $lt: { $numberLong: "1" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $lte: { $numberLong: "1" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $lt: { $numberLong: "0" } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "200" } }} + result: + errorContains: must be less than the range maximum + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "0" }, $lt: { $numberLong: "2"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $numberLong: "0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedLong: { $numberLong: "1" } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gte: {$numberLong: "0"}, $lte: {$numberLong: "200"} } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $in: [ {$numberLong: "0"} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedLong: { $numberLong: "-1" }} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedLong: { $numberLong: "200" }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gte: { $numberLong: "0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gt: { $numberLong: "1" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $lt: { $numberLong: "1" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $lte: { $numberLong: "1" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $lt: { $numberLong: "0" } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gt: { $numberLong: "200" } }} } + result: + errorContains: must be less than the range maximum + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gt: { $numberLong: "0" }, $lt: { $numberLong: "2"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $numberLong: "0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $numberLong: "1" } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gte: {$numberLong: "0"}, $lte: {$numberLong: "200"} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $in: [ {$numberLong: "0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedLong: { $numberDouble: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedLong: { $gte: { $numberDouble: "0" } }} + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Delete.yml new file mode 100644 index 0000000000..12080d60ae --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Delete.yml @@ -0,0 +1,185 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedLong": { $gt: {$numberLong: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedLong": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-FindOneAndUpdate.yml new file mode 100644 index 0000000000..a90e09c47c --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-FindOneAndUpdate.yml @@ -0,0 +1,243 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedLong: { $gt: {$numberLong: "0"}} } + update: { "$set": { "encryptedLong": {$numberLong: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedLong": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + "deleteTokens": { + "default.default": { + "encryptedLong": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-InsertFind.yml new file mode 100644 index 0000000000..f0c79b21dc --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-InsertFind.yml @@ -0,0 +1,221 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Update.yml new file mode 100644 index 0000000000..20783a7cdd --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Update.yml @@ -0,0 +1,260 @@ +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: updateOne + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "0" } } } + update: { "$set": { "encryptedLong": { $numberLong: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedLong": { $$type: "binData" } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": *encrypted_fields + }, + "deleteTokens": { + "default.default": { + "encryptedLong": { + "e": { + "$binary": { + "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", + "subType": "00" + } + }, + "o": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } + } + } + }, + "$db": "default" + } + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-WrongType.yml b/spec/spec_tests/data/client_side_encryption/fle2-Range-WrongType.yml new file mode 100644 index 0000000000..9bc2c53dfd --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2-Range-WrongType.yml @@ -0,0 +1,41 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. +# Requires libmongocrypt 1.7.0. +runOn: + - minServerVersion: "6.2.0" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Wrong type: Insert Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedInt: { $numberDouble: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedInt: { $numberInt: "0" }} } + - name: find + arguments: + filter: { encryptedInt: { $gte: { $numberDouble: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/spec/support/crypt.rb b/spec/support/crypt.rb index 70860f8ae8..7cf5e224b8 100644 --- a/spec/support/crypt.rb +++ b/spec/support/crypt.rb @@ -115,7 +115,13 @@ module Crypt end let(:encrypted_fields) do - BSON::ExtJSON.parse(File.read('spec/support/crypt/encryptedFields.json')) + BSON::ExtJSON.parse(File.read('spec/support/crypt/encrypted_fields/encryptedFields.json')) + end + + %w[Date DoubleNoPrecision DoublePrecision Int Long].each do |type| + let("range_encrypted_fields_#{type.downcase}".to_sym) do + BSON::ExtJSON.parse(File.read("spec/support/crypt/encrypted_fields/range-encryptedFields-#{type}.json")) + end end let(:key1_document) do diff --git a/spec/support/crypt/encryptedFields.json b/spec/support/crypt/encrypted_fields/encryptedFields.json similarity index 100% rename from spec/support/crypt/encryptedFields.json rename to spec/support/crypt/encrypted_fields/encryptedFields.json diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json new file mode 100644 index 0000000000..e19fc1e182 --- /dev/null +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json @@ -0,0 +1,30 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "rangePreview", + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] +} diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json new file mode 100644 index 0000000000..c6d129d4ca --- /dev/null +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json @@ -0,0 +1,21 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "rangePreview", + "sparsity": { + "$numberInt": "1" + } + } + } + ] + } + \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json new file mode 100644 index 0000000000..c23c3fa923 --- /dev/null +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json @@ -0,0 +1,29 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "rangePreview", + "sparsity": { + "$numberInt": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] +} diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json new file mode 100644 index 0000000000..4af6422714 --- /dev/null +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json @@ -0,0 +1,21 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "rangePreview", + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json new file mode 100644 index 0000000000..c1f388219d --- /dev/null +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json @@ -0,0 +1,30 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "rangePreview", + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json new file mode 100644 index 0000000000..217bf6743c --- /dev/null +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json @@ -0,0 +1,27 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "rangePreview", + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + \ No newline at end of file diff --git a/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json b/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json new file mode 100644 index 0000000000..0fb87edaef --- /dev/null +++ b/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json @@ -0,0 +1,27 @@ +{ + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "rangePreview", + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + \ No newline at end of file From 3c1379c2159e87e8e68d05911417f887ba78a807 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Sat, 11 Feb 2023 03:26:58 +0900 Subject: [PATCH 103/198] MONGOID-5304: Add Rubocop static code analyzer (#2460) * Add Rubocop static code analyzer (only run security cops on lib/**/* dir) * Update Rakefile * Also add solargraph * Remove evergreen configuration --------- Co-authored-by: shields Co-authored-by: Dmitry Rybakov --- .evergreen/config.yml | 4 ++-- .github/workflows/rubocop.yml | 20 ++++++++++++++++++++ .rubocop.yml | 29 +++++++++++++++++++++++++++++ gemfiles/standard.rb | 4 ++++ 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/rubocop.yml create mode 100644 .rubocop.yml diff --git a/.evergreen/config.yml b/.evergreen/config.yml index a42360798e..f31d90fc9a 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1544,7 +1544,7 @@ buildvariants: # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 # TODO Add ruby-3.0: RUBY-2717 - + - matrix_name: ocsp-verifier - ruby-2.5 matrix_spec: ocsp-verifier: true @@ -1576,7 +1576,7 @@ buildvariants: docker-distro: debian10 display_name: 'OCSP verifier: ${mongodb-version} ${ruby}' tasks: *1 - + - matrix_name: ocsp-must-staple matrix_spec: diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 0000000000..b5eb3da8ef --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,20 @@ +--- +name: Rubocop + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + env: + CI: true + TESTOPTS: "-v" + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby 3.0 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.0 + bundler-cache: true + - name: Run RuboCop + run: bundle exec rubocop --parallel diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..abe7183346 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,29 @@ +AllCops: + TargetRubyVersion: 2.5 + NewCops: disable + Include: + - 'lib/**/*' + +Bundler: + Enabled: false + +Gemspec: + Enabled: false + +Layout: + Enabled: false + +Lint: + Enabled: false + +Metrics: + Enabled: false + +Naming: + Enabled: false + +Security: + Enabled: true + +Style: + Enabled: false diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index 7ba55dd4d2..095ffeb048 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -29,6 +29,9 @@ def standard_dependencies gem 'yajl-ruby', platforms: :mri, require: false gem 'celluloid', platforms: :mri, require: false + # for static analysis + gem 'rubocop', '~> 1.27.0' + platform :mri do # Debugger for VSCode. if !ENV['CI'] && !ENV['DOCKER'] && RUBY_VERSION < '3.0' @@ -57,6 +60,7 @@ def standard_dependencies gem 'ruby-prof', platforms: :mri gem 'erubi' gem 'tilt' + gem 'solargraph' end if ENV['FLE'] == 'helper' From 2af5dbef1555a1594ca96260467d8d37838dbe68 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 13 Feb 2023 11:04:22 +0100 Subject: [PATCH 104/198] Fix failures on latest (#2692) --- .evergreen/config.yml | 12 ++++++++++++ .evergreen/config/common.yml.erb | 2 +- .evergreen/config/standard.yml.erb | 12 ++++++++++++ .evergreen/run-tests.sh | 21 ++++++++++++++++----- spec/mongo/collection_ddl_spec.rb | 2 ++ 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index f31d90fc9a..7fc3f557d9 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1458,6 +1458,18 @@ buildvariants: tasks: - name: "test-kerberos" + - matrix_name: "fle-latest" + matrix_spec: + auth-and-ssl: "noauth-and-nossl" + ruby: [ruby-3.1] + topology: [replica-set, sharded-cluster] + mongodb-version: ['latest'] + os: ubuntu2004 + fle: helper + display_name: "FLE: ${mongodb-version} ${ruby}" + tasks: + - name: "test-fle" + - matrix_name: "fle-6.0" matrix_spec: auth-and-ssl: "noauth-and-nossl" diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 12a3c9f0e1..f0b3ca74a1 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -11,7 +11,7 @@ pre_error_fails_task: true command_type: system # Protect ourself against rogue test case, or curl gone wild, that runs forever. -exec_timeout_secs: 2700 +exec_timeout_secs: 3600 <% egos_wrap = "./egos stdbuf -i0 -o0 -e0" %> diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index 728079a657..a2462654f7 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -467,6 +467,18 @@ buildvariants: tasks: - name: "test-kerberos" + - matrix_name: "fle-latest" + matrix_spec: + auth-and-ssl: "noauth-and-nossl" + ruby: [ruby-3.1] + topology: [replica-set, sharded-cluster] + mongodb-version: ['latest'] + os: ubuntu2004 + fle: helper + display_name: "FLE: ${mongodb-version} ${ruby}" + tasks: + - name: "test-fle" + - matrix_name: "fle-6.0" matrix_spec: auth-and-ssl: "noauth-and-nossl" diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index de70d6f52e..bd4ec1a5e9 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -164,11 +164,22 @@ fi if test -n "$FLE"; then # Downloading crypt shared lib - crypt_shared_version=`${BINDIR}/mongod --version | grep -oP 'db version v\K.*'` - python3 -u .evergreen/mongodl.py --component crypt_shared -V ${crypt_shared_version} --out `pwd`/csfle_lib --target `host_distro` || true - if test -f `pwd`/csfle_lib/lib/mongo_crypt_v1.so - then - export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=`pwd`/csfle_lib/lib/mongo_crypt_v1.so + if [ -z "$MONGO_CRYPT_SHARED_DOWNLOAD_URL" ]; then + crypt_shared_version=$("${BINDIR}"/mongod --version | grep -oP 'db version v\K.*') + python3 -u .evergreen/mongodl.py --component crypt_shared -V ${crypt_shared_version} --out $(pwd)/csfle_lib --target $(host_distro) || true + if test -f $(pwd)/csfle_lib/lib/mongo_crypt_v1.so + then + export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=$(pwd)/csfle_lib/lib/mongo_crypt_v1.so + else + echo 'Could not find crypt_shared library' + fi + else + echo "Downloading crypt_shared package from $MONGO_CRYPT_SHARED_DOWNLOAD_URL" + mkdir -p $(pwd)/csfle_lib + cd $(pwd)/csfle_lib + curl --retry 3 -fL $MONGO_CRYPT_SHARED_DOWNLOAD_URL | tar zxf - + export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=$(pwd)/lib/mongo_crypt_v1.so + cd - fi # Start the KMS servers first so that they are launching while we are diff --git a/spec/mongo/collection_ddl_spec.rb b/spec/mongo/collection_ddl_spec.rb index 36ae3337ae..b0c39831db 100644 --- a/spec/mongo/collection_ddl_spec.rb +++ b/spec/mongo/collection_ddl_spec.rb @@ -430,6 +430,7 @@ context 'when the collection does not exist' do require_set_write_concern + max_server_fcv '6.99.99' it 'does not raise an error' do expect(database['non-existent-coll'].drop).to be(false) @@ -490,6 +491,7 @@ context 'when the collection does not exist' do require_set_write_concern + max_server_fcv '6.99.99' before do begin From f2ac12e5dba49002807a96eafdc8a8db6e0776e7 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 13 Feb 2023 11:05:17 +0100 Subject: [PATCH 105/198] RUBY-3005 Range indexes in explicit encryption (#2690) --- .evergreen/config.yml | 2 +- lib/mongo/client_encryption.rb | 40 ++ lib/mongo/crypt.rb | 1 + lib/mongo/crypt/auto_encrypter.rb | 2 +- lib/mongo/crypt/binding.rb | 75 +++ lib/mongo/crypt/explicit_encrypter.rb | 59 ++ .../crypt/explicit_encryption_context.rb | 83 ++- .../explicit_encryption_expression_context.rb | 30 + .../range_explicit_encryption_prose_spec.rb | 532 ++++++++++++++++++ .../crypt/explicit_encryption_context_spec.rb | 4 +- spec/support/crypt.rb | 7 +- 11 files changed, 803 insertions(+), 32 deletions(-) create mode 100644 lib/mongo/crypt/explicit_encryption_expression_context.rb create mode 100644 spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 7fc3f557d9..487dc4da4b 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -14,7 +14,7 @@ pre_error_fails_task: true command_type: system # Protect ourself against rogue test case, or curl gone wild, that runs forever. -exec_timeout_secs: 2700 +exec_timeout_secs: 3600 # What to do when evergreen hits the timeout (`post:` tasks are run automatically) diff --git a/lib/mongo/client_encryption.rb b/lib/mongo/client_encryption.rb index e6cdb595db..e1af83b3ec 100644 --- a/lib/mongo/client_encryption.rb +++ b/lib/mongo/client_encryption.rb @@ -120,6 +120,46 @@ def encrypt(value, options={}) @encrypter.encrypt(value, options) end + # Encrypts a Match Expression or Aggregate Expression to query a range index. + # + # @example Encrypt Match Expression. + # encryption.encrypt_expression( + # {'$and' => [{'field' => {'$gt' => 10}}, {'field' => {'$lt' => 20 }}]} + # ) + # @example Encrypt Aggregate Expression. + # encryption.encrypt_expression( + # {'$and' => [{'$gt' => ['$field', 10]}, {'$lt' => ['$field', 20]}} + # ) + # {$and: [{$gt: [, ]}, {$lt: [, ]}] + # Only supported when queryType is "rangePreview" and algorithm is "RangePreview". + # @note: The Range algorithm is experimental only. It is not intended + # for public use. It is subject to breaking changes. + # + # @param [ Hash ] expression Expression to encrypt. + # # @param [ Hash ] options + # @option options [ BSON::Binary ] :key_id A BSON::Binary object of type :uuid + # representing the UUID of the encryption key as it is stored in the key + # vault collection. + # @option options [ String ] :key_alt_name The alternate name for the + # encryption key. + # @option options [ String ] :algorithm The algorithm used to encrypt the + # expression. The only allowed value is "RangePreview" + # @option options [ Integer | nil ] :contention_factor Contention factor + # to be applied If not provided, it defaults to a value of 0. + # @option options [ String | nil ] query_type Query type to be applied. + # The only allowed value is "rangePreview". + # + # @note The :key_id and :key_alt_name options are mutually exclusive. Only + # one is required to perform explicit encryption. + # + # @return [ BSON::Binary ] A BSON Binary object of subtype 6 (ciphertext) + # representing the encrypted expression. + # + # @raise [ ArgumentError ] if disallowed values in options are set. + def encrypt_expression(expression, options = {}) + @encrypter.encrypt_expression(expression, options) + end + # Decrypts a value that has already been encrypted. # # @param [ BSON::Binary ] value A BSON Binary object of subtype 6 (ciphertext) diff --git a/lib/mongo/crypt.rb b/lib/mongo/crypt.rb index 202def248b..612fe0e937 100644 --- a/lib/mongo/crypt.rb +++ b/lib/mongo/crypt.rb @@ -26,6 +26,7 @@ module Crypt autoload(:Context, 'mongo/crypt/context') autoload(:DataKeyContext, 'mongo/crypt/data_key_context') autoload(:ExplicitEncryptionContext, 'mongo/crypt/explicit_encryption_context') + autoload(:ExplicitEncryptionExpressionContext, 'mongo/crypt/explicit_encryption_expression_context') autoload(:AutoEncryptionContext, 'mongo/crypt/auto_encryption_context') autoload(:ExplicitDecryptionContext, 'mongo/crypt/explicit_decryption_context') autoload(:AutoDecryptionContext, 'mongo/crypt/auto_decryption_context') diff --git a/lib/mongo/crypt/auto_encrypter.rb b/lib/mongo/crypt/auto_encrypter.rb index 1ac24430fd..ea431ae2ba 100644 --- a/lib/mongo/crypt/auto_encrypter.rb +++ b/lib/mongo/crypt/auto_encrypter.rb @@ -117,7 +117,7 @@ def initialize(options) @crypt_handle.crypt_shared_lib_available? || @options[:extra_options][:crypt_shared_lib_required] - unless @options[:extra_options][:crypt_shared_lib_required] || @crypt_handle.crypt_shared_lib_available? + unless @options[:extra_options][:crypt_shared_lib_required] || @crypt_handle.crypt_shared_lib_available? || @options[:bypass_query_analysis] # Set server selection timeout to 1 to prevent the client waiting for a # long timeout before spawning mongocryptd @mongocryptd_client = Client.new( diff --git a/lib/mongo/crypt/binding.rb b/lib/mongo/crypt/binding.rb index da706626b7..15eb830c74 100644 --- a/lib/mongo/crypt/binding.rb +++ b/lib/mongo/crypt/binding.rb @@ -717,6 +717,40 @@ def self.ctx_explicit_encrypt_init(context, doc) end end + # @!method self.mongocrypt_ctx_explicit_encrypt_init(ctx, msg) + # @api private + # + # Initializes the ctx for explicit expression encryption. + # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object. + # @param [ FFI::Pointer ] msg A pointer to a mongocrypt_binary_t object + # that references the message to be encrypted as a binary string. + # @note Before calling this method, set a key_id, key_alt_name (optional), + # and encryption algorithm using the following methods: + # mongocrypt_ctx_setopt_key_id, mongocrypt_ctx_setopt_key_alt_name, + # and mongocrypt_ctx_setopt_algorithm. + # @return [ Boolean ] Whether the initialization was successful. + attach_function( + :mongocrypt_ctx_explicit_encrypt_expression_init, + [:pointer, :pointer], + :bool + ) + + # Initialize the Context for explicit expression encryption. + # + # @param [ Mongo::Crypt::Context ] context + # @param [ Hash ] doc A BSON document to encrypt + # + # @raise [ Mongo::Error::CryptError ] If initialization fails + def self.ctx_explicit_encrypt_expression_init(context, doc) + validate_document(doc) + data = doc.to_bson.to_s + Binary.wrap_string(data) do |data_p| + check_ctx_status(context) do + mongocrypt_ctx_explicit_encrypt_expression_init(context.ctx_p, data_p) + end + end + end + # @!method self.mongocrypt_ctx_decrypt_init(ctx, doc) # @api private # @@ -1654,6 +1688,47 @@ def self.ctx_setopt_contention_factor(context, factor) end end + # @!method self.mongocrypt_ctx_setopt_algorithm_range(ctx, opts) + # @api private + # + # Set options for explicit encryption with the "rangePreview" algorithm. + # + # @note The RangePreview algorithm is experimental only. It is not intended for + # public use. + # + # @param [ FFI::Pointer ] ctx A pointer to a mongocrypt_ctx_t object. + # @param [ FFI::Pointer ] opts opts A pointer to range + # options document. + # + # @return [ Boolean ] Whether setting this option succeeded. + attach_function( + :mongocrypt_ctx_setopt_algorithm_range, + [ + :pointer, + :pointer + ], + :bool + ) + + # Set options for explicit encryption with the "rangePreview" algorithm. + # + # @note The RangePreview algorithm is experimental only. It is not intended for + # public use. + # + # @param [ Mongo::Crypt::Context ] context + # @param [ Hash ] opts options + # + # @raise [ Mongo::Error::CryptError ] If the operation failed + def self.ctx_setopt_algorithm_range(context, opts) + validate_document(opts) + data = opts.to_bson.to_s + Binary.wrap_string(data) do |data_p| + check_ctx_status(context) do + mongocrypt_ctx_setopt_algorithm_range(context.ctx_p, data_p) + end + end + end + # Raise a Mongo::Error::CryptError based on the status of the underlying # mongocrypt_t object. # diff --git a/lib/mongo/crypt/explicit_encrypter.rb b/lib/mongo/crypt/explicit_encrypter.rb index e2256cb651..a3ed4fc47c 100644 --- a/lib/mongo/crypt/explicit_encrypter.rb +++ b/lib/mongo/crypt/explicit_encrypter.rb @@ -113,6 +113,65 @@ def encrypt(value, options) ).run_state_machine['v'] end + # Encrypts a Match Expression or Aggregate Expression to query a range index. + # + # @example Encrypt Match Expression. + # encryption.encrypt_expression( + # {'$and' => [{'field' => {'$gt' => 10}}, {'field' => {'$lt' => 20 }}]} + # ) + # @example Encrypt Aggregate Expression. + # encryption.encrypt_expression( + # {'$and' => [{'$gt' => ['$field', 10]}, {'$lt' => ['$field', 20]}} + # ) + # {$and: [{$gt: [, ]}, {$lt: [, ]}] + # Only supported when queryType is "rangePreview" and algorithm is "RangePreview". + # @note: The Range algorithm is experimental only. It is not intended + # for public use. It is subject to breaking changes. + # + # @param [ Hash ] expression Expression to encrypt. + # # @param [ Hash ] options + # @option options [ BSON::Binary ] :key_id A BSON::Binary object of type :uuid + # representing the UUID of the encryption key as it is stored in the key + # vault collection. + # @option options [ String ] :key_alt_name The alternate name for the + # encryption key. + # @option options [ String ] :algorithm The algorithm used to encrypt the + # expression. The only allowed value is "RangePreview" + # @option options [ Integer | nil ] :contention_factor Contention factor + # to be applied If not provided, it defaults to a value of 0. + # @option options [ String | nil ] query_type Query type to be applied. + # The only allowed value is "rangePreview". + # @option options [ Hash | nil ] :range_opts Specifies index options for + # a Queryable Encryption field supporting "rangePreview" queries. + # Allowed options are: + # - :min + # - :max + # - :sparsity + # - :precision + # min, max, sparsity, and range must match the values set in + # the encryptedFields of the destination collection. + # For double and decimal128, min/max/precision must all be set, + # or all be unset. + # + # @note The RangePreview algorithm is experimental only. It is not + # intended for public use. + # + # @note The :key_id and :key_alt_name options are mutually exclusive. Only + # one is required to perform explicit encryption. + # + # @return [ BSON::Binary ] A BSON Binary object of subtype 6 (ciphertext) + # representing the encrypted expression. + # + # @raise [ ArgumentError ] if disallowed values in options are set. + def encrypt_expression(expression, options) + Crypt::ExplicitEncryptionExpressionContext.new( + @crypt_handle, + @encryption_io, + { 'v': expression }, + options + ).run_state_machine['v'] + end + # Decrypts a value that has already been encrypted # # @param [ BSON::Binary ] value A BSON Binary object of subtype 6 (ciphertext) diff --git a/lib/mongo/crypt/explicit_encryption_context.rb b/lib/mongo/crypt/explicit_encryption_context.rb index 6dad1becc2..a7649eecfd 100644 --- a/lib/mongo/crypt/explicit_encryption_context.rb +++ b/lib/mongo/crypt/explicit_encryption_context.rb @@ -31,35 +31,57 @@ class ExplicitEncryptionContext < Context # that implements driver I/O methods required to run the # state machine # @param [ BSON::Document ] doc A document to encrypt - # @param [ Hash ] options # + # @param [ Hash ] options # @option options [ BSON::Binary ] :key_id A BSON::Binary object of type # :uuid representing the UUID of the data key to use for encryption. # @option options [ String ] :key_alt_name The alternate name of the data key # that will be used to encrypt the value. # @option options [ String ] :algorithm The algorithm used to encrypt the # value. Valid algorithms are "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", - # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed". + # "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "Indexed", "Unindexed", "RangePreview". # @option options [ Integer | nil ] :contention_factor Contention factor # to be applied if encryption algorithm is set to "Indexed". If not # provided, it defaults to a value of 0. Contention factor should be set # only if encryption algorithm is set to "Indexed". # @option options [ String | nil ] query_type Query type to be applied - # if encryption algorithm is set to "Indexed". Query type should be set - # only if encryption algorithm is set to "Indexed". The only allowed - # value is "equality". + # if encryption algorithm is set to "Indexed" or "RangePreview". + # Allowed values are "equality" and "rangePreview". + # @option options [ Hash | nil ] :range_opts Specifies index options for + # a Queryable Encryption field supporting "rangePreview" queries. + # Allowed options are: + # - :min + # - :max + # - :sparsity + # - :precision + # min, max, sparsity, and range must match the values set in + # the encryptedFields of the destination collection. + # For double and decimal128, min/max/precision must all be set, + # or all be unset. + # + # @note The RangePreview algorithm is experimental only. It is not intended for + # public use. # # @raise [ ArgumentError|Mongo::Error::CryptError ] If invalid options are provided - def initialize(mongocrypt, io, doc, options={}) + def initialize(mongocrypt, io, doc, options = {}) super(mongocrypt, io) + set_key_opts(options) + set_algorithm_opts(options) + init(doc) + end + def init(doc) + Binding.ctx_explicit_encrypt_init(self, doc) + end + + private + def set_key_opts(options) if options[:key_id].nil? && options[:key_alt_name].nil? raise ArgumentError.new( 'The :key_id and :key_alt_name options cannot both be nil. ' + 'Specify a :key_id option or :key_alt_name option (but not both)' ) end - if options[:key_id] && options[:key_alt_name] raise ArgumentError.new( 'The :key_id and :key_alt_name options cannot both be present. ' + @@ -67,30 +89,34 @@ def initialize(mongocrypt, io, doc, options={}) 'option or specifying its alternate name with the :key_alt_name option' ) end - - # Set the key id or key alt name option on the mongocrypt_ctx_t object - # and raise an exception if the key_id or key_alt_name is invalid. if options[:key_id] - unless options[:key_id].is_a?(BSON::Binary) && - options[:key_id].type == :uuid + set_key_id(options[:key_id]) + elsif options[:key_alt_name] + set_key_alt_name(options[:key_alt_name]) + end + end + + def set_key_id(key_id) + unless key_id.is_a?(BSON::Binary) && + key_id.type == :uuid raise ArgumentError.new( "Expected the :key_id option to be a BSON::Binary object with " + - "type :uuid. #{options[:key_id]} is an invalid :key_id option" + "type :uuid. #{key_id} is an invalid :key_id option" ) end + Binding.ctx_setopt_key_id(self, key_id.data) + end - Binding.ctx_setopt_key_id(self, options[:key_id].data) - elsif options[:key_alt_name] - unless options[:key_alt_name].is_a?(String) + def set_key_alt_name(key_alt_name) + unless key_alt_name.is_a?(String) raise ArgumentError.new(':key_alt_name option must be a String') end - Binding.ctx_setopt_key_alt_names(self, [options[:key_alt_name]]) - end + Binding.ctx_setopt_key_alt_names(self, [key_alt_name]) + end - # Set the algorithm option on the mongocrypt_ctx_t object and raises - # an exception if the algorithm is invalid. + def set_algorithm_opts(options) Binding.ctx_setopt_algorithm(self, options[:algorithm]) - if options[:algorithm] == 'Indexed' + if %w(Indexed RangePreview).include?(options[:algorithm]) if options[:contention_factor] Binding.ctx_setopt_contention_factor(self, options[:contention_factor]) end @@ -99,16 +125,21 @@ def initialize(mongocrypt, io, doc, options={}) end else if options[:contention_factor] - raise ArgumentError.new(':contention_factor is allowed only for "Indexed" algorithm') + raise ArgumentError.new(':contention_factor is allowed only for "Indexed" or "RangePreview" algorithms') end if options[:query_type] - raise ArgumentError.new(':query_type is allowed only for "Indexed" algorithm') + raise ArgumentError.new(':query_type is allowed only for "Indexed" or "RangePreview" algorithms') end end + if options[:algorithm] == 'RangePreview' + Binding.ctx_setopt_algorithm_range(self, convert_range_opts(options[:range_opts])) + end + end - # Initializes the mongocrypt_ctx_t object for explicit encryption and - # passes in the value to be encrypted. - Binding.ctx_explicit_encrypt_init(self, doc) + def convert_range_opts(range_opts) + range_opts.dup.tap do |opts| + opts[:sparsity] = BSON::Int64.new(opts[:sparsity]) unless opts[:sparsity].is_a?(BSON::Int64) + end end end end diff --git a/lib/mongo/crypt/explicit_encryption_expression_context.rb b/lib/mongo/crypt/explicit_encryption_expression_context.rb new file mode 100644 index 0000000000..d554649382 --- /dev/null +++ b/lib/mongo/crypt/explicit_encryption_expression_context.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true +# encoding: utf-8 + +# Copyright (C) 2019-2020 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + + # A Context object initialized for explicit expression encryption. + # + # @api private + class ExplicitEncryptionExpressionContext < ExplicitEncryptionContext + def init(doc) + Binding.ctx_explicit_encrypt_expression_init(self, doc) + end + end + end +end diff --git a/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb new file mode 100644 index 0000000000..551c20ad3a --- /dev/null +++ b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb @@ -0,0 +1,532 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Range Explicit Encryption' do + min_server_version '6.2' + require_libmongocrypt + include_context 'define shared FLE helpers' + + let(:key1_id) do + key1_document['_id'] + end + + before(:each) do + authorized_client['explicit_encryption'].drop(encrypted_fields: encrypted_fields) + authorized_client['explicit_encryption'].create(encrypted_fields: encrypted_fields) + authorized_client.use(key_vault_db)[key_vault_coll].drop + authorized_client.use(key_vault_db)[key_vault_coll, write_concern: {w: :majority}].insert_one(key1_document) + end + + let(:key_vault_client) do + ClientRegistry.instance.new_local_client(SpecConfig.instance.addresses) + end + + let(:client_encryption) do + Mongo::ClientEncryption.new( + key_vault_client, + kms_tls_options: kms_tls_options, + key_vault_namespace: key_vault_namespace, + kms_providers: local_kms_providers + ) + end + + let(:encrypted_client) do + ClientRegistry.instance.new_local_client( + SpecConfig.instance.addresses, + auto_encryption_options: { + key_vault_namespace: key_vault_namespace, + kms_providers: local_kms_providers, + bypass_query_analysis: true + }, + database: SpecConfig.instance.test_db + ) + end + + shared_examples 'common cases' do + it 'can decrypt a payload' do + value = value_converter.call(6) + insert_payload = client_encryption.encrypt( + value, + { + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + } + ) + decrypted_value = client_encryption.decrypt(insert_payload) + expect(value).to eq(decrypted_value) + end + + it 'can find encrypted range and return the maximum' do + expr = { + "$and": [ + {"encrypted#{type}" => {"$gte": value_converter.call(6)}}, + {"encrypted#{type}" => {"$lte": value_converter.call(200)}} + ] + } + find_payload = client_encryption.encrypt_expression( + expr, + { + key_id: key1_id, + algorithm: "RangePreview", + query_type: "rangePreview", + contention_factor: 0, + range_opts: range_opts + } + ) + results = encrypted_client['explicit_encryption'].find(find_payload, sort: {_id: 1}).to_a + expect(results.size).to eq(3) + value_converter.call([6, 30, 200]).each_with_index do |value, idx| + expect(results[idx]["encrypted#{type}"]).to eq(value) + end + end + + it 'can find encrypted range and return the minimum' do + expr = { + "$and": [ + {"encrypted#{type}" => {"$gte": value_converter.call(0)}}, + {"encrypted#{type}" => {"$lte": value_converter.call(6)}} + ] + } + find_payload = client_encryption.encrypt_expression( + expr, + { + key_id: key1_id, + algorithm: "RangePreview", + query_type: "rangePreview", + contention_factor: 0, + range_opts: range_opts + } + ) + results = encrypted_client['explicit_encryption'].find(find_payload, sort: {_id: 1}).to_a + expect(results.size).to eq(2) + value_converter.call([0, 6]).each_with_index do |value, idx| + expect(results[idx]["encrypted#{type}"]).to eq(value) + end + end + + it 'can find encrypted range with an open range query' do + expr = { + "$and": [ + { "encrypted#{type}" => { "$gt": value_converter.call(30) } } + ] + } + find_payload = client_encryption.encrypt_expression( + expr, + { + key_id: key1_id, + algorithm: "RangePreview", + query_type: "rangePreview", + contention_factor: 0, + range_opts: range_opts + } + ) + results = encrypted_client['explicit_encryption'].find(find_payload, sort: {_id: 1}).to_a + expect(results.size).to eq(1) + expect(results.first["encrypted#{type}"]).to eq(value_converter.call(200)) + end + + it 'can run an aggregation expression inside $expr' do + expr = {'$and': [ { '$lt': [ "$encrypted#{type}", value_converter.call(30) ] } ] } + find_payload = client_encryption.encrypt_expression( + expr, + { + key_id: key1_id, + algorithm: "RangePreview", + query_type: "rangePreview", + contention_factor: 0, + range_opts: range_opts + } + ) + results = encrypted_client['explicit_encryption'].find( + {'$expr' => find_payload}, + sort: { _id: 1 } + ).to_a + expect(results.size).to eq(2) + value_converter.call([0, 6]).each_with_index do |value, idx| + expect(results[idx]["encrypted#{type}"]).to eq(value) + end + end + + it 'encrypting a document greater than the maximum errors' do + skip if %w(DoubleNoPrecision DecimalNoPrecision).include?(type) + expect do + client_encryption.encrypt( + value_converter.call(201), + { + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + } + ) + end.to raise_error(Mongo::Error::CryptError, /less than or equal to the maximum value/) + end + + it 'encrypting a document of a different type errors ' do + skip if %w(DoubleNoPrecision DecimalNoPrecision).include?(type) + value = if type == 'Int' + 6.0 + else + 6 + end + expect do + client_encryption.encrypt( + value, + { + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + } + ) + end.to raise_error(Mongo::Error::CryptError, /expected matching 'min' and value type/) + end + + it 'setting precision errors if the type is not a double' do + skip if %w(DoublePrecision DoubleNoPrecision DecimalPrecision DecimalNoPrecision).include?(type) + expect do + client_encryption.encrypt( + value_converter.call(6), + { + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: { + min: value_converter.call(0), + max: value_converter.call(200), + sparsity: 1, + precision: 2 + } + } + ) + end.to raise_error(Mongo::Error::CryptError, /precision/) + end + end + + context 'Int' do + let(:type) do + 'Int' + end + + let(:value_converter) do + Proc.new do |value| + if value.is_a?(Array) + value.map(&:to_i) + else + value.to_i + end + end + end + + let(:encrypted_fields) do + range_encrypted_fields_int + end + + let(:range_opts) do + { + min: BSON::Int32.new(0), + max: BSON::Int32.new(200), + sparsity: 1 + } + end + + before(:each) do + [0, 6, 30, 200].each_with_index do |num, idx| + insert_payload = client_encryption.encrypt( + num, + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + ) + encrypted_client['explicit_encryption'].insert_one( + _id: idx, + "encrypted#{type}" => insert_payload + ) + end + end + + include_examples 'common cases' + end + + context 'Long' do + let(:type) do + 'Long' + end + + let(:value_converter) do + Proc.new do |value| + if value.is_a?(Array) + value.map { |i| BSON::Int64.new(i)} + else + BSON::Int64.new(value) + end + end + end + + let(:encrypted_fields) do + range_encrypted_fields_long + end + + let(:range_opts) do + { + min: BSON::Int64.new(0), + max: BSON::Int64.new(200), + sparsity: 1 + } + end + + before(:each) do + [0, 6, 30, 200].each_with_index do |num, idx| + insert_payload = client_encryption.encrypt( + BSON::Int64.new(num), + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + ) + encrypted_client['explicit_encryption'].insert_one( + _id: idx, + "encrypted#{type}" => insert_payload + ) + end + end + + include_examples 'common cases' + end + + context 'DoublePrecision' do + let(:type) do + 'DoublePrecision' + end + + let(:value_converter) do + Proc.new do |value| + if value.is_a?(Array) + value.map(&:to_f) + else + value.to_f + end + end + end + + let(:encrypted_fields) do + range_encrypted_fields_doubleprecision + end + + let(:range_opts) do + { + min: 0.0, + max: 200.0, + sparsity: 1, + precision: 2 + } + end + + before(:each) do + [0.0, 6.0, 30.0, 200.0].each_with_index do |num, idx| + insert_payload = client_encryption.encrypt( + num, + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + ) + encrypted_client['explicit_encryption'].insert_one( + _id: idx, + "encrypted#{type}" => insert_payload + ) + end + end + + include_examples 'common cases' + end + + context 'DoubleNoPrecision' do + let(:type) do + 'DoubleNoPrecision' + end + + let(:value_converter) do + Proc.new do |value| + if value.is_a?(Array) + value.map(&:to_f) + else + value.to_f + end + end + end + + let(:encrypted_fields) do + range_encrypted_fields_doublenoprecision + end + + let(:range_opts) do + { + sparsity: 1 + } + end + + before(:each) do + [0.0, 6.0, 30.0, 200.0].each_with_index do |num, idx| + insert_payload = client_encryption.encrypt( + num, + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + ) + encrypted_client['explicit_encryption'].insert_one( + _id: idx, + "encrypted#{type}" => insert_payload + ) + end + end + + include_examples 'common cases' + end + + context 'Date' do + let(:type) do + 'Date' + end + + let(:value_converter) do + Proc.new do |value| + if value.is_a?(Array) + value.map { |i| Time.new(i) } + else + Time.new(value) + end + end + end + + let(:encrypted_fields) do + range_encrypted_fields_date + end + + let(:range_opts) do + { + min: Time.new(0), + max: Time.new(200), + sparsity: 1 + } + end + + before(:each) do + [0, 6, 30, 200].each_with_index do |num, idx| + insert_payload = client_encryption.encrypt( + Time.new(num), + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + ) + encrypted_client['explicit_encryption'].insert_one( + _id: idx, + "encrypted#{type}" => insert_payload + ) + end + end + + include_examples 'common cases' + end + + context 'DecimalPrecision' do + require_topology :replica_set + + let(:type) do + 'DecimalPrecision' + end + + let(:value_converter) do + Proc.new do |value| + if value.is_a?(Array) + value.map { |val| BSON::Decimal128.new(val.to_s)} + else + BSON::Decimal128.new(value.to_s) + end + end + end + + let(:encrypted_fields) do + range_encrypted_fields_decimalprecision + end + + let(:range_opts) do + { + min: BSON::Decimal128.new('0.0'), + max: BSON::Decimal128.new('200.0'), + sparsity: 1, + precision: 2 + } + end + + before(:each) do + %w[0 6 30 200].each_with_index do |num, idx| + insert_payload = client_encryption.encrypt( + BSON::Decimal128.new(num), + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + ) + encrypted_client['explicit_encryption'].insert_one( + _id: idx, + "encrypted#{type}" => insert_payload + ) + end + end + + include_examples 'common cases' + end + + context 'DecimalNoPrecision' do + require_topology :replica_set + + let(:type) do + 'DecimalNoPrecision' + end + + let(:value_converter) do + Proc.new do |value| + if value.is_a?(Array) + value.map { |val| BSON::Decimal128.new(val.to_s)} + else + BSON::Decimal128.new(value.to_s) + end + end + end + + let(:encrypted_fields) do + range_encrypted_fields_decimalnoprecision + end + + let(:range_opts) do + { + sparsity: 1 + } + end + + before(:each) do + %w[0 6 30 200].each_with_index do |num, idx| + insert_payload = client_encryption.encrypt( + BSON::Decimal128.new(num), + key_id: key1_id, + algorithm: "RangePreview", + contention_factor: 0, + range_opts: range_opts + ) + encrypted_client['explicit_encryption'].insert_one( + _id: idx, + "encrypted#{type}" => insert_payload + ) + end + end + + include_examples 'common cases' + end +end diff --git a/spec/mongo/crypt/explicit_encryption_context_spec.rb b/spec/mongo/crypt/explicit_encryption_context_spec.rb index 479b27457d..4e209499ee 100644 --- a/spec/mongo/crypt/explicit_encryption_context_spec.rb +++ b/spec/mongo/crypt/explicit_encryption_context_spec.rb @@ -139,7 +139,7 @@ value, options.merge(query_type: "equality") ) - end.to raise_error(ArgumentError, /query_type is allowed only for "Indexed" algorithm/) + end.to raise_error(ArgumentError, /query_type is allowed only for "Indexed" or "RangePreview" algorithm/) end end @@ -154,7 +154,7 @@ value, options.merge(contention_factor: 10) ) - end.to raise_error(ArgumentError, /contention_factor is allowed only for "Indexed" algorithm/) + end.to raise_error(ArgumentError, /contention_factor is allowed only for "Indexed" or "RangePreview" algorithm/) end end diff --git a/spec/support/crypt.rb b/spec/support/crypt.rb index 7cf5e224b8..cf362c0edf 100644 --- a/spec/support/crypt.rb +++ b/spec/support/crypt.rb @@ -118,9 +118,12 @@ module Crypt BSON::ExtJSON.parse(File.read('spec/support/crypt/encrypted_fields/encryptedFields.json')) end - %w[Date DoubleNoPrecision DoublePrecision Int Long].each do |type| + %w[DecimalNoPrecision DecimalPrecision Date DoubleNoPrecision DoublePrecision Int Long].each do |type| let("range_encrypted_fields_#{type.downcase}".to_sym) do - BSON::ExtJSON.parse(File.read("spec/support/crypt/encrypted_fields/range-encryptedFields-#{type}.json")) + BSON::ExtJSON.parse( + File.read("spec/support/crypt/encrypted_fields/range-encryptedFields-#{type}.json"), + mode: :bson + ) end end From b92b7f4054ccc1375c965649d9f6527a67da3f9f Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 21 Feb 2023 19:12:31 +0100 Subject: [PATCH 106/198] RUBY-3052 Add create_encrypted_collection helper (#2693) --- lib/mongo/client_encryption.rb | 66 ++++++++ ...tomatic_data_encryption_keys_prose_spec.rb | 148 ++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb diff --git a/lib/mongo/client_encryption.rb b/lib/mongo/client_encryption.rb index e1af83b3ec..78dc9cbb73 100644 --- a/lib/mongo/client_encryption.rb +++ b/lib/mongo/client_encryption.rb @@ -244,5 +244,71 @@ def remove_key_alt_name(id, key_alt_name) def rewrap_many_data_key(filter, opts = {}) @encrypter.rewrap_many_data_key(filter, opts) end + + # Create collection with encrypted fields. + # + # If :encryption_fields contains a keyId with a null value, a data key + # will be automatically generated and assigned to keyId value. + # + # @note This method does not update the :encrypted_fields_map in the client's + # :auto_encryption_options. Therefore, in order to use the collection + # created by this method with automatic encryption, the user must create + # a new client after calling this function with the :encrypted_fields returned. + # + # @note This method is a part of the queryable encryption API and therefore + # is in public technical preview. This method should be considered + # experimental and is subject to change. + # + # @param [ Mongo::Database ] database Database to create collection in. + # @param [ String ] coll_name Name of collection to create. + # @param [ Hash ] coll_opts Options for collection to create. + # @param [ String ] kms_provider KMS provider to encrypt fields. + # @param [ Hash | nil ] master_key Document describing master key to encrypt fields. + # + # @return [ Array ] The result of the create + # collection operation and the encrypted fields map used to create + # the collection. + def create_encrypted_collection(database, coll_name, coll_opts, kms_provider, master_key) + raise ArgumentError, 'coll_opts must contain :encrypted_fields' unless coll_opts[:encrypted_fields] + + encrypted_fields = create_data_keys(coll_opts[:encrypted_fields], kms_provider, master_key) + begin + new_coll_opts = coll_opts.dup.merge(encrypted_fields: encrypted_fields) + [database[coll_name].create(new_coll_opts), encrypted_fields] + rescue Mongo::Error => e + raise Error::CryptError, "Error creating collection with encrypted fields \ + #{encrypted_fields}: #{e.class}: #{e.message}" + end + end + + private + + # Create data keys for fields in encrypted_fields that has :keyId key, + # but the value is nil. + # + # @param [ Hash ] encrypted_fields Encrypted fields map. + # @param [ String ] kms_provider KMS provider to encrypt fields. + # @param [ Hash | nil ] master_key Document describing master key to encrypt fields. + # + # @return [ Hash ] Encrypted fields map with keyIds for fields + # that did not have one. + def create_data_keys(encrypted_fields, kms_provider, master_key) + encrypted_fields = encrypted_fields.dup + # We must return the partially formed encrypted_fields hash if an error + # occurs - https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#create-encrypted-collection-helper + # Thefore, we do this in a loop instead of using #map. + encrypted_fields[:fields].size.times do |i| + field = encrypted_fields[:fields][i] + next unless field.is_a?(Hash) && field.fetch(:keyId, false).nil? + + begin + encrypted_fields[:fields][i][:keyId] = create_data_key(kms_provider, master_key: master_key) + rescue Error::CryptError => e + raise Error::CryptError, "Error creating data key for field #{field[:path]} \ + with encrypted fields #{encrypted_fields}: #{e.class}: #{e.message}" + end + end + encrypted_fields + end end end diff --git a/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb b/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb new file mode 100644 index 0000000000..7fbb73fdff --- /dev/null +++ b/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true +# encoding: utf-8 + +require 'spec_helper' + +describe 'Client-Side Encryption' do + describe 'Automatic Data Encryption Keys' do + require_libmongocrypt + require_enterprise + require_topology :replica_set, :sharded, :load_balanced + min_server_fcv '6.0' + + include_context 'define shared FLE helpers' + + let(:test_database_name) do + 'automatic_data_encryption_keys' + end + + let(:key_vault_client) do + ClientRegistry.instance.new_local_client(SpecConfig.instance.addresses) + end + + let(:client_encryption) do + Mongo::ClientEncryption.new( + key_vault_client, + kms_tls_options: kms_tls_options, + key_vault_namespace: key_vault_namespace, + kms_providers: { + local: { + key: local_master_key + }, + aws: { + access_key_id: SpecConfig.instance.fle_aws_key, + secret_access_key: SpecConfig.instance.fle_aws_secret, + } + } + ) + end + + let(:database) do + authorized_client.use(test_database_name).database + end + + before(:each) do + authorized_client.use(key_vault_db)[key_vault_coll].drop + authorized_client.use(test_database_name).database.drop + end + + shared_examples 'creates data keys automatically' do + it 'fails document validation when insert unencrypted value' do + opts = { + encrypted_fields: { + fields: [{ + path: 'ssn', + bsonType: 'string', + keyId: nil + }] + } + } + client_encryption.create_encrypted_collection( + database, + 'testing1', + opts, + kms_provider, + master_key + ) + expect do + database['testing1'].insert_one(ssn: '123-45-6789') + end.to raise_error(Mongo::Error::OperationFailure, /Document failed validation/) + end + + it 'fails when missing encrypted field' do + expect do + client_encryption.create_encrypted_collection( + database, + 'testing1', + {}, + kms_provider, + master_key + ) + end.to raise_error(ArgumentError, /coll_opts must contain :encrypted_fields/) + end + + it 'fails when invalid keyId provided' do + opts = { + encrypted_fields: { + fields: [{ + path: 'ssn', + bsonType: 'string', + keyId: false + }] + } + } + expect do + client_encryption.create_encrypted_collection( + database, + 'testing1', + opts, + kms_provider, + master_key + ) + end.to raise_error(Mongo::Error::CryptError, /keyId' is the wrong type/) + end + + it 'successfully inserts encrypted value' do + opts = { + encrypted_fields: { + fields: [{ + path: 'ssn', + bsonType: 'string', + keyId: nil + }] + } + } + _, new_encrypted_fields = client_encryption.create_encrypted_collection( + database, + 'testing1', + opts, + kms_provider, + master_key + ) + key_id = new_encrypted_fields[:fields].first[:keyId] + encrypted_payload = client_encryption.encrypt( + '123-45-6789', + key_id: key_id, + algorithm: 'Unindexed' + ) + expect do + database['testing1'].insert_one(ssn: encrypted_payload) + end.not_to raise_error + end + end + + context 'aws' do + let(:kms_provider) { 'aws' } + let(:master_key) { { region: 'us-east-1', key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0' } } + + it_behaves_like 'creates data keys automatically' + end + + context 'local' do + let(:kms_provider) { 'local' } + let(:master_key) { { key: local_master_key } } + + it_behaves_like 'creates data keys automatically' + end + end +end From 03dbe226646fa21944bf16e21a760c6fcc2c67ef Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 3 Mar 2023 09:13:47 +0100 Subject: [PATCH 107/198] RUBY-3066 Cache AWS Credentials Where Possible (#2695) --- lib/mongo/auth/aws.rb | 10 +- lib/mongo/auth/aws/credentials.rb | 37 ++++++ lib/mongo/auth/aws/credentials_cache.rb | 74 ++++++++++++ lib/mongo/auth/aws/credentials_retriever.rb | 110 +++++++++++------- .../aws_auth_credentials_cache_spec.rb | 50 ++++++++ .../aws_credentials_retriever_spec.rb | 4 + spec/mongo/auth/aws/credential_cache_spec.rb | 62 ++++++++++ .../auth/aws/credentials_retriever_spec.rb | 89 ++++++++++++++ spec/mongo/auth/aws/credentials_spec.rb | 45 +++++++ spec/support/client_registry.rb | 4 + 10 files changed, 438 insertions(+), 47 deletions(-) create mode 100644 lib/mongo/auth/aws/credentials.rb create mode 100644 lib/mongo/auth/aws/credentials_cache.rb create mode 100644 spec/integration/aws_auth_credentials_cache_spec.rb create mode 100644 spec/mongo/auth/aws/credential_cache_spec.rb create mode 100644 spec/mongo/auth/aws/credentials_retriever_spec.rb create mode 100644 spec/mongo/auth/aws/credentials_spec.rb diff --git a/lib/mongo/auth/aws.rb b/lib/mongo/auth/aws.rb index eb46c98b90..4589c029ea 100644 --- a/lib/mongo/auth/aws.rb +++ b/lib/mongo/auth/aws.rb @@ -25,16 +25,16 @@ class Aws < Base # @return [ BSON::Document ] The document of the authentication response. def login converse_2_step(connection, conversation) + rescue StandardError + CredentialsCache.instance.clear + raise end - - # The AWS credential set. - # - # @api private - Credentials = Struct.new(:access_key_id, :secret_access_key, :session_token) end end end require 'mongo/auth/aws/conversation' +require 'mongo/auth/aws/credentials' +require 'mongo/auth/aws/credentials_cache' require 'mongo/auth/aws/credentials_retriever' require 'mongo/auth/aws/request' diff --git a/lib/mongo/auth/aws/credentials.rb b/lib/mongo/auth/aws/credentials.rb new file mode 100644 index 0000000000..a661bb5e09 --- /dev/null +++ b/lib/mongo/auth/aws/credentials.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Copyright (C) 2023-present MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Auth + class Aws + # The AWS credential set. + # + # @api private + Credentials = Struct.new(:access_key_id, :secret_access_key, :session_token, :expiration) do + # @return [ true | false ] Whether the credentials have expired. + def expired? + if expiration.nil? + false + else + # According to the spec, Credentials are considered + # valid if they are more than five minutes away from expiring. + Time.now.utc >= expiration - 300 + end + end + end + end + end +end diff --git a/lib/mongo/auth/aws/credentials_cache.rb b/lib/mongo/auth/aws/credentials_cache.rb new file mode 100644 index 0000000000..fa52144380 --- /dev/null +++ b/lib/mongo/auth/aws/credentials_cache.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +# Copyright (C) 2023-present MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Auth + class Aws + # Thread safe cache to store AWS credentials. + # + # @api private + class CredentialsCache + # Get or create the singleton instance of the cache. + # + # @return [ CredentialsCache ] The singleton instance. + def self.instance + @instance ||= new + end + + def initialize + @lock = Mutex.new + @credentials = nil + end + + # Set the credentials in the cache. + # + # @param [ Aws::Credentials ] credentials The credentials to cache. + def credentials=(credentials) + @lock.synchronize do + @credentials = credentials + end + end + + # Get the credentials from the cache. + # + # @return [ Aws::Credentials ] The cached credentials. + def credentials + @lock.synchronize do + @credentials + end + end + + # Fetch the credentials from the cache or yield to get them + # if they are not in the cache or have expired. + # + # @return [ Aws::Credentials ] The cached credentials. + def fetch + @lock.synchronize do + @credentials = yield if @credentials.nil? || @credentials.expired? + @credentials + end + end + + # Clear the credentials from the cache. + def clear + @lock.synchronize do + @credentials = nil + end + end + end + end + end +end diff --git a/lib/mongo/auth/aws/credentials_retriever.rb b/lib/mongo/auth/aws/credentials_retriever.rb index b52d0b6874..1050d99056 100644 --- a/lib/mongo/auth/aws/credentials_retriever.rb +++ b/lib/mongo/auth/aws/credentials_retriever.rb @@ -18,7 +18,6 @@ module Mongo module Auth class Aws - # Raised when trying to authorize with an invalid configuration # # @api private @@ -51,15 +50,17 @@ def initialize # # @api private class CredentialsRetriever - # Timeout for metadata operations, in seconds. # # The auth spec suggests a 10 second timeout but this seems # excessively long given that the endpoint is essentially local. METADATA_TIMEOUT = 5 - def initialize(user = nil) + # @param [ Auth::User | nil ] user The user object, if one was provided. + # @param [ Auth::Aws::CredentialsCache ] credentials_cache The credentials cache. + def initialize(user = nil, credentials_cache: CredentialsCache.instance) @user = user + @credentials_cache = credentials_cache end # @return [ Auth::User | nil ] The user object, if one was provided. @@ -70,55 +71,77 @@ def initialize(user = nil) # # @return [ Auth::Aws::Credentials ] A valid set of credentials. # - # @raise Auth::InvalidConfiguration if credentials could not be - # retrieved for any reason, or if a source contains an invalid set + # @raise Auth::InvalidConfiguration if a source contains an invalid set # of credentials. + # @raise Auth::Aws::CredentialsNotFound if credentials could not be + # retrieved from any source. def credentials - if user - credentials = Credentials.new( - user.name, - user.password, - user.auth_mech_properties['aws_session_token'], - ) - - if credentials_valid?(credentials, 'Mongo::Client URI or Ruby options') - return credentials - end - end + credentials = credentials_from_user(user) + return credentials unless credentials.nil? - credentials = Credentials.new( - ENV['AWS_ACCESS_KEY_ID'], - ENV['AWS_SECRET_ACCESS_KEY'], - ENV['AWS_SESSION_TOKEN'], - ) + credentials = credentials_from_environment + return credentials unless credentials.nil? - if credentials_valid?(credentials, 'environment variables') - return credentials - end + credentials = @credentials_cache.fetch { obtain_credentials_from_endpoints } + return credentials unless credentials.nil? - credentials = web_identity_credentials + raise Auth::Aws::CredentialsNotFound + end - if credentials && credentials_valid?(credentials, 'Web identity token') - return credentials - end + private - credentials = ecs_metadata_credentials + # Returns credentials from the user object. + # + # @param [ Auth::User | nil ] user The user object, if one was provided. + # + # @return [ Auth::Aws::Credentials | nil ] A set of credentials, or nil + # + # @raise Auth::InvalidConfiguration if a source contains an invalid set + # of credentials. + def credentials_from_user(user) + return nil unless user - if credentials && credentials_valid?(credentials, 'ECS task metadata') - return credentials - end + credentials = Credentials.new( + user.name, + user.password, + user.auth_mech_properties['aws_session_token'] + ) + return credentials if credentials_valid?(credentials, 'Mongo::Client URI or Ruby options') + end - credentials = ec2_metadata_credentials + # Returns credentials from environment variables. + # + # @return [ Auth::Aws::Credentials | nil ] A set of credentials, or nil + # if retrieval failed or the obtained credentials are invalid. + # + # @raise Auth::InvalidConfiguration if a source contains an invalid set + # of credentials. + def credentials_from_environment + credentials = Credentials.new( + ENV['AWS_ACCESS_KEY_ID'], + ENV['AWS_SECRET_ACCESS_KEY'], + ENV['AWS_SESSION_TOKEN'] + ) + credentials if credentials && credentials_valid?(credentials, 'environment variables') + end - if credentials && credentials_valid?(credentials, 'EC2 instance metadata') - return credentials + # Returns credentials from the AWS metadata endpoints. + # + # @return [ Auth::Aws::Credentials | nil ] A set of credentials, or nil + # if retrieval failed or the obtained credentials are invalid. + # + # @raise Auth::InvalidConfiguration if a source contains an invalid set + # of credentials. + def obtain_credentials_from_endpoints + if (credentials = web_identity_credentials) && credentials_valid?(credentials, 'Web identity token') + credentials + elsif (credentials = ecs_metadata_credentials) && credentials_valid?(credentials, 'ECS task metadata') + credentials + elsif (credentials = ec2_metadata_credentials) && credentials_valid?(credentials, 'EC2 instance metadata') + credentials end - - raise Auth::Aws::CredentialsNotFound end - private - # Returns credentials from the EC2 metadata endpoint. The credentials # could be empty, partial or invalid. # @@ -158,10 +181,11 @@ def ec2_metadata_credentials payload['AccessKeyId'], payload['SecretAccessKey'], payload['Token'], + DateTime.parse(payload['Expiration']).to_time ) # When trying to use the EC2 metadata endpoint on ECS: # Errno::EINVAL: Failed to open TCP connection to 169.254.169.254:80 (Invalid argument - connect(2) for "169.254.169.254" port 80) - rescue ::Timeout::Error, IOError, SystemCallError + rescue ::Timeout::Error, IOError, SystemCallError, TypeError return nil end @@ -190,8 +214,9 @@ def ecs_metadata_credentials payload['AccessKeyId'], payload['SecretAccessKey'], payload['Token'], + DateTime.parse(payload['Expiration']).to_time ) - rescue ::Timeout::Error, IOError, SystemCallError + rescue ::Timeout::Error, IOError, SystemCallError, TypeError return nil end @@ -284,8 +309,9 @@ def credentials_from_web_identity_response(response) payload['AccessKeyId'], payload['SecretAccessKey'], payload['SessionToken'], + Time.at(payload['Expiration']) ) - rescue JSON::ParserError + rescue JSON::ParserError, TypeError nil end diff --git a/spec/integration/aws_auth_credentials_cache_spec.rb b/spec/integration/aws_auth_credentials_cache_spec.rb new file mode 100644 index 0000000000..78e521e0d3 --- /dev/null +++ b/spec/integration/aws_auth_credentials_cache_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mongo::Auth::Aws::CredentialsCache do + require_auth 'aws-ec2', 'aws-ecs', 'aws-web-identity' + + def new_client + ClientRegistry.instance.new_authorized_client.tap do |client| + @clients << client + end + end + + before do + @clients = [] + described_class.instance.clear + end + + after do + @clients.each(&:close) + end + + it 'caches the credentials' do + client1 = new_client + client1['test-collection'].find.to_a + expect(described_class.instance.credentials).not_to be_nil + + described_class.instance.credentials = Mongo::Auth::Aws::Credentials.new( + described_class.instance.credentials.access_key_id, + described_class.instance.credentials.secret_access_key, + described_class.instance.credentials.session_token, + Time.now + 60 + ) + client2 = new_client + client2['test-collection'].find.to_a + expect(described_class.instance.credentials).not_to be_expired + + described_class.instance.credentials = Mongo::Auth::Aws::Credentials.new( + 'bad_access_key_id', + described_class.instance.credentials.secret_access_key, + described_class.instance.credentials.session_token, + described_class.instance.credentials.expiration + ) + client3 = new_client + expect { client3['test-collection'].find.to_a }.to raise_error(Mongo::Auth::Unauthorized) + expect(described_class.instance.credentials).to be_nil + expect { client3['test-collection'].find.to_a }.not_to raise_error + expect(described_class.instance.credentials).not_to be_nil + end +end diff --git a/spec/integration/aws_credentials_retriever_spec.rb b/spec/integration/aws_credentials_retriever_spec.rb index b4b74796e6..0557da5f99 100644 --- a/spec/integration/aws_credentials_retriever_spec.rb +++ b/spec/integration/aws_credentials_retriever_spec.rb @@ -20,6 +20,10 @@ Mongo::Auth::User.new(auth_mech: :aws) end + before do + Mongo::Auth::Aws::CredentialsCache.instance.clear + end + shared_examples_for 'retrieves the credentials' do it 'retrieves' do credentials.should be_a(Mongo::Auth::Aws::Credentials) diff --git a/spec/mongo/auth/aws/credential_cache_spec.rb b/spec/mongo/auth/aws/credential_cache_spec.rb new file mode 100644 index 0000000000..694e42452f --- /dev/null +++ b/spec/mongo/auth/aws/credential_cache_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'lite_spec_helper' + +describe Mongo::Auth::Aws::CredentialsCache do + let(:subject) do + described_class.new + end + + describe '#fetch' do + context 'when credentials are not cached' do + it 'yields to the block' do + expect { |b| subject.fetch(&b) }.to yield_control + end + + it 'sets the credentials' do + credentials = double('credentials') + subject.fetch { credentials } + expect(subject.credentials).to eq(credentials) + end + end + + context 'when credentials are cached' do + context 'when credentials are not expired' do + let(:credentials) do + double('credentials', expired?: false) + end + + it 'does not yield to the block' do + subject.credentials = credentials + expect { |b| subject.fetch(&b) }.not_to yield_control + end + end + end + + context 'when credentials are expired' do + let(:credentials) do + double('credentials', expired?: true) + end + + it 'yields to the block' do + subject.credentials = credentials + expect { |b| subject.fetch(&b) }.to yield_control + end + + it 'sets the credentials' do + subject.credentials = credentials + new_credentials = double('new credentials') + subject.fetch { new_credentials } + expect(subject.credentials).to eq(new_credentials) + end + end + end + + describe '#clear' do + it 'clears the credentials' do + subject.credentials = double('credentials') + subject.clear + expect(subject.credentials).to be nil + end + end +end diff --git a/spec/mongo/auth/aws/credentials_retriever_spec.rb b/spec/mongo/auth/aws/credentials_retriever_spec.rb new file mode 100644 index 0000000000..041a2f9e46 --- /dev/null +++ b/spec/mongo/auth/aws/credentials_retriever_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'lite_spec_helper' + +describe Mongo::Auth::Aws::CredentialsRetriever do + describe '#credentials' do + context 'when credentials should be obtained from endpoints' do + let(:cache) do + Mongo::Auth::Aws::CredentialsCache.new + end + + let(:subject) do + described_class.new(credentials_cache: cache).tap do |retriever| + allow(retriever).to receive(:credentials_from_environment).and_return(nil) + end + end + + context 'when cached credentials are not expired' do + let(:credentials) do + double('credentials', expired?: false) + end + + before(:each) do + cache.credentials = credentials + end + + it 'returns the cached credentials' do + expect(subject.credentials).to eq(credentials) + end + + it 'does not obtain credentials from endpoints' do + expect(subject).not_to receive(:obtain_credentials_from_endpoints) + described_class.new(credentials_cache: cache).credentials + end + end + + shared_examples_for 'obtains credentials from endpoints' do + context 'when obtained credentials are not expired' do + let(:credentials) do + double('credentials', expired?: false) + end + + before(:each) do + expect(subject) + .to receive(:obtain_credentials_from_endpoints) + .and_return(credentials) + end + + it 'returns the obtained credentials' do + expect(subject.credentials).not_to be_expired + end + + it 'caches the obtained credentials' do + subject.credentials + expect(cache.credentials).to eq(credentials) + end + end + + context 'when cannot obtain credentials from endpoints' do + before(:each) do + expect(subject) + .to receive(:obtain_credentials_from_endpoints) + .and_return(nil) + end + + it 'raises an error' do + expect { subject.credentials }.to raise_error(Mongo::Auth::Aws::CredentialsNotFound) + end + end + end + + context 'when cached credentials expired' do + before(:each) do + cache.credentials = double('credentials', expired?: true) + end + + it_behaves_like 'obtains credentials from endpoints' + end + + context 'when no credentials cached' do + before(:each) do + cache.clear + end + + it_behaves_like 'obtains credentials from endpoints' + end + end + end +end diff --git a/spec/mongo/auth/aws/credentials_spec.rb b/spec/mongo/auth/aws/credentials_spec.rb new file mode 100644 index 0000000000..4b57584519 --- /dev/null +++ b/spec/mongo/auth/aws/credentials_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'lite_spec_helper' + +describe Mongo::Auth::Aws::Credentials do + describe '#expired?' do + context 'when expiration is nil' do + let(:credentials) do + described_class.new('access_key_id', 'secret_access_key', nil, nil) + end + + it 'returns false' do + expect(credentials.expired?).to be false + end + end + + context 'when expiration is not nil' do + before do + Timecop.freeze + end + after do + Timecop.return + end + context 'when the expiration is more than five minutes away' do + let(:credentials) do + described_class.new('access_key_id', 'secret_access_key', nil, Time.now.utc + 400) + end + + it 'returns false' do + expect(credentials.expired?).to be false + end + end + + context 'when the expiration is less than five minutes away' do + let(:credentials) do + described_class.new('access_key_id', 'secret_access_key', nil, Time.now.utc + 200) + end + + it 'returns true' do + expect(credentials.expired?).to be true + end + end + end + end +end diff --git a/spec/support/client_registry.rb b/spec/support/client_registry.rb index f42957e55d..1d8971091c 100644 --- a/spec/support/client_registry.rb +++ b/spec/support/client_registry.rb @@ -86,6 +86,10 @@ def global_client(name) @global_clients[name] = new_global_client(name) end + def new_authorized_client + new_global_client('authorized') + end + def new_global_client(name) case name # Provides a basic scanned client to do a hello check. From bd9de56eea7a840acf22e7dd7da4ee6556a6d259 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 14 Mar 2023 08:50:35 -0600 Subject: [PATCH 108/198] add the new spec test (#2697) --- .../writeConcernError.yml | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 spec/spec_tests/data/command_monitoring_unified/writeConcernError.yml diff --git a/spec/spec_tests/data/command_monitoring_unified/writeConcernError.yml b/spec/spec_tests/data/command_monitoring_unified/writeConcernError.yml new file mode 100644 index 0000000000..fbaa4a330d --- /dev/null +++ b/spec/spec_tests/data/command_monitoring_unified/writeConcernError.yml @@ -0,0 +1,80 @@ +description: "writeConcernError" +schemaVersion: "1.13" +runOnRequirements: + - + minServerVersion: 4.1.0 + topologies: + - replicaset + serverless: "forbid" + +createEntities: + - client: + id: &client client + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent + - database: + id: &database database + client: *client + databaseName: &databaseName command-monitoring-tests + - collection: + id: &collection collection + database: *database + collectionName: &collectionName test + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + +tests: + - description: "A retryable write with write concern errors publishes success event" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + writeConcernError: + code: 91 # ShutdownInProgress + errorLabels: [RetryableWriteError] + - name: insertOne + object: *collection + arguments: + document: { _id: 2, x: 22 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 2, x: 22 } + ordered: true + commandName: insert + databaseName: *databaseName + - commandSucceededEvent: + reply: + ok: 1 + n: 1 + writeConcernError: { code: 91, errorLabels: [ "RetryableWriteError" ] } + commandName: insert + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 2, x: 22 } + ordered: true + commandName: insert + databaseName: *databaseName + - commandSucceededEvent: + reply: + ok: 1 + n: 1 + commandName: insert From 39ea9c9231b160092f14b4223808bfa1eca18fad Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 23 Mar 2023 10:14:23 -0600 Subject: [PATCH 109/198] restore jruby FLE tests to see if they will pass (#2700) --- .evergreen/config.yml | 28 ++++++++++++++-------------- .evergreen/config/standard.yml.erb | 24 ++++++++++++------------ .gitignore | 1 + 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 487dc4da4b..f722724598 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1519,18 +1519,18 @@ buildvariants: - name: "test-fle" # https://jira.mongodb.org/browse/RUBY-3217 - #- matrix_name: "fle-jruby" - # matrix_spec: - # auth-and-ssl: "noauth-and-nossl" - # ruby: [jruby-9.2, jruby-9.3] - # topology: standalone - # mongodb-version: ['5.0'] - # os: ubuntu1804 - # fle: helper - # docker-distro: ubuntu1804 - # display_name: "FLE: ${mongodb-version} ${ruby}" - # tasks: - # - name: "test-fle-via-docker" + - matrix_name: "fle-jruby" + matrix_spec: + auth-and-ssl: "noauth-and-nossl" + ruby: [jruby-9.2, jruby-9.3] + topology: standalone + mongodb-version: ['5.0'] + os: ubuntu1804 + fle: helper + docker-distro: ubuntu1804 + display_name: "FLE: ${mongodb-version} ${ruby}" + tasks: + - name: "test-fle-via-docker" - matrix_name: aws-auth-regular matrix_spec: @@ -1556,7 +1556,7 @@ buildvariants: # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 # TODO Add ruby-3.0: RUBY-2717 - + - matrix_name: ocsp-verifier - ruby-2.5 matrix_spec: ocsp-verifier: true @@ -1588,7 +1588,7 @@ buildvariants: docker-distro: debian10 display_name: 'OCSP verifier: ${mongodb-version} ${ruby}' tasks: *1 - + - matrix_name: ocsp-must-staple matrix_spec: diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index a2462654f7..d8d7cb0524 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -528,18 +528,18 @@ buildvariants: - name: "test-fle" # https://jira.mongodb.org/browse/RUBY-3217 - #- matrix_name: "fle-jruby" - # matrix_spec: - # auth-and-ssl: "noauth-and-nossl" - # ruby: [jruby-9.2, jruby-9.3] - # topology: standalone - # mongodb-version: ['5.0'] - # os: ubuntu1804 - # fle: helper - # docker-distro: ubuntu1804 - # display_name: "FLE: ${mongodb-version} ${ruby}" - # tasks: - # - name: "test-fle-via-docker" + - matrix_name: "fle-jruby" + matrix_spec: + auth-and-ssl: "noauth-and-nossl" + ruby: [jruby-9.2, jruby-9.3] + topology: standalone + mongodb-version: ['5.0'] + os: ubuntu1804 + fle: helper + docker-distro: ubuntu1804 + display_name: "FLE: ${mongodb-version} ${ruby}" + tasks: + - name: "test-fle-via-docker" - matrix_name: aws-auth-regular matrix_spec: diff --git a/.gitignore b/.gitignore index 2d4d2b4126..b127af9a1c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ gem-private_key.pem nbproject tmp sandbox/* +data/* .byebug_history gemfiles/*.gemfile.lock .env.private* From 5defc85d538eb9a998df104c6e387e873f34cd74 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 27 Mar 2023 09:43:16 +0200 Subject: [PATCH 110/198] RUBY-3216 Skip failing view spec (#2701) --- spec/mongo/index/view_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/mongo/index/view_spec.rb b/spec/mongo/index/view_spec.rb index 7f074ddb5c..d1c874e37d 100644 --- a/spec/mongo/index/view_spec.rb +++ b/spec/mongo/index/view_spec.rb @@ -958,10 +958,9 @@ context 'on server versions >= 5.0' do min_server_fcv '5.0' - # https://jira.mongodb.org/browse/RUBY-3216 - max_server_fcv '6.2' it 'passes wildcardProjection correctly' do + skip 'https://jira.mongodb.org/browse/RUBY-3216' expect(indexes[:wildcardProjection]).to eq({ '_id' => false, 'rating' => true }) end end From 7f52414f3bb6193dba607d54c7bb4413a318555b Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Mon, 27 Mar 2023 14:46:18 -0400 Subject: [PATCH 111/198] RUBY-3215: Update client connection example to include Atlas (#2699) * RUBY-3215: Update client connection example to include Atlas * RUBY-3215: Fixed typo in codeblock --- docs/reference/create-client.txt | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index ba8525677a..c20f4d77ee 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -327,7 +327,41 @@ URI option: # Or using the URI syntax: Mongo::Client.new("mongodb://1.2.3.4:27017/mydb?loadBalanced=true&connect=load_balanced") +MongoDB Atlas Connection +------------------------ + +To connect to a MongoDB deployment on Atlas, first create a ``Mongo::Client`` instance using your +cluster's connection string and other client options. + +You can set the `Stable API `_ version as +a client option to avoid breaking changes when you upgrade to a new server version. + +The following code shows how you can specify the connection string and the Stable API client option +when connecting to a MongoDB deployment and verify that the connection is successful: + +.. code-block:: ruby + require 'mongo' + + # Replace the placeholders with your credentials + uri = "mongodb+srv://:@cluster0.sample.mongodb.net/?retryWrites=true&w=majority" + + # Set the server_api field of the options object to Stable API version 1 + options = { server_api: { version: "1" } } + + # Create a new client and connect to the server + client = Mongo::Client.new(uri, options) + + # Send a ping to confirm a successful connection + begin + admin_client = client.use('admin') + result = admin_client.database.command(ping: 1).documents.first + puts "Pinged your deployment. You successfully connected to MongoDB!" + rescue Mongo::Error::OperationFailure => ex + puts ex + ensure + client.close + end .. _srv-uri-notes: From f987336c49de5631c9d3fe3eefe14368d37c91d9 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 27 Mar 2023 14:03:45 -0600 Subject: [PATCH 112/198] update spec files (#2702) --- .../fle2-CreateCollection.yml | 218 +++++++++--------- ...-EncryptedFields-vs-EncryptedFieldsMap.yml | 6 +- 2 files changed, 112 insertions(+), 112 deletions(-) diff --git a/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml b/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml index 78d7e55e07..3413b47b10 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml @@ -15,9 +15,9 @@ tests: aws: {} # Credentials filled in from environment. encryptedFieldsMap: default.encryptedCollection: &encrypted_fields0 { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -41,17 +41,17 @@ tests: object: testRunner arguments: database: *database_name - collection: "encryptedCollection.esc" + collection: "enxcol_.encryptedCollection.esc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: "enxcol_.encryptedCollection.ecc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecoc" + collection: "enxcol_.encryptedCollection.ecoc" - name: assertCollectionExists object: testRunner arguments: @@ -68,17 +68,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -91,19 +91,19 @@ tests: # State collections are created first. - command_started_event: command: - create: "encryptedCollection.esc" + create: "enxcol_.encryptedCollection.esc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecc" + create: "enxcol_.encryptedCollection.ecc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecoc" + create: "enxcol_.encryptedCollection.ecoc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name @@ -418,9 +418,9 @@ tests: # encryptedCollection has encryptedCollection.esc as the escCollection. # encryptedCollection.esc has encryptedCollection as the escCollection. default.encryptedCollection: &encrypted_fields3 { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -430,9 +430,9 @@ tests: ] } default.encryptedCollection.esc: { - "escCollection": "encryptedCollection", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -455,17 +455,17 @@ tests: object: testRunner arguments: database: *database_name - collection: "encryptedCollection.esc" + collection: "enxcol_.encryptedCollection.esc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: "enxcol_.encryptedCollection.ecc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecoc" + collection: "enxcol_.encryptedCollection.ecoc" - name: assertCollectionExists object: testRunner arguments: @@ -481,17 +481,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -504,19 +504,19 @@ tests: # State collections are created first. - command_started_event: command: - create: "encryptedCollection.esc" + create: "enxcol_.encryptedCollection.esc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecc" + create: "enxcol_.encryptedCollection.ecc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecoc" + create: "enxcol_.encryptedCollection.ecoc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name @@ -544,9 +544,9 @@ tests: aws: {} # Credentials filled in from environment. encryptedFieldsMap: default.encryptedCollection: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -598,9 +598,9 @@ tests: aws: {} # Credentials filled in from environment. encryptedFieldsMap: default.encryptedCollection: &encrypted_fields4 { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -623,17 +623,17 @@ tests: object: testRunner arguments: database: *database_name - collection: "encryptedCollection.esc" + collection: "enxcol_.encryptedCollection.esc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: "enxcol_.encryptedCollection.ecc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecoc" + collection: "enxcol_.encryptedCollection.ecoc" - name: assertCollectionExists object: testRunner arguments: @@ -650,17 +650,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -673,19 +673,19 @@ tests: # State collections are created first. - command_started_event: command: - create: "encryptedCollection.esc" + create: "enxcol_.encryptedCollection.esc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecc" + create: "enxcol_.encryptedCollection.ecc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecoc" + create: "enxcol_.encryptedCollection.ecoc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name @@ -718,9 +718,9 @@ tests: arguments: collection: "encryptedCollection" encryptedFields: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -734,9 +734,9 @@ tests: arguments: collection: "encryptedCollection" encryptedFields: &encrypted_fields5 { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -749,17 +749,17 @@ tests: object: testRunner arguments: database: *database_name - collection: "encryptedCollection.esc" + collection: "enxcol_.encryptedCollection.esc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: "enxcol_.encryptedCollection.ecc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecoc" + collection: "enxcol_.encryptedCollection.ecoc" - name: assertCollectionExists object: testRunner arguments: @@ -776,17 +776,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -799,19 +799,19 @@ tests: # State collections are created first. - command_started_event: command: - create: "encryptedCollection.esc" + create: "enxcol_.encryptedCollection.esc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecc" + create: "enxcol_.encryptedCollection.ecc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecoc" + create: "enxcol_.encryptedCollection.ecoc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name @@ -847,9 +847,9 @@ tests: aws: {} # Credentials filled in from environment. encryptedFieldsMap: default.encryptedCollection: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -867,17 +867,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -899,9 +899,9 @@ tests: arguments: collection: "encryptedCollection" encryptedFields: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -915,9 +915,9 @@ tests: arguments: collection: "encryptedCollection" encryptedFields: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -930,17 +930,17 @@ tests: object: testRunner arguments: database: *database_name - collection: "encryptedCollection.esc" + collection: "enxcol_.encryptedCollection.esc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: "enxcol_.encryptedCollection.ecc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecoc" + collection: "enxcol_.encryptedCollection.ecoc" - name: assertCollectionExists object: testRunner arguments: @@ -957,9 +957,9 @@ tests: arguments: collection: "encryptedCollection" encryptedFields: &encrypted_fields6 { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -972,17 +972,17 @@ tests: object: testRunner arguments: database: *database_name - collection: "encryptedCollection.esc" + collection: "enxcol_.encryptedCollection.esc" - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: "enxcol_.encryptedCollection.ecc" - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecoc" + collection: "enxcol_.encryptedCollection.ecoc" - name: assertCollectionNotExists object: testRunner arguments: @@ -992,17 +992,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -1014,19 +1014,19 @@ tests: # events from createCollection ... begin - command_started_event: command: - create: "encryptedCollection.esc" + create: "enxcol_.encryptedCollection.esc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecc" + create: "enxcol_.encryptedCollection.ecc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecoc" + create: "enxcol_.encryptedCollection.ecoc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name @@ -1056,17 +1056,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -1090,9 +1090,9 @@ tests: arguments: collection: "encryptedCollection" encryptedFields: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1106,9 +1106,9 @@ tests: arguments: collection: "encryptedCollection" encryptedFields: &encrypted_fields7 { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + "escCollection": "enxcol_.encryptedCollection.esc", + "eccCollection": "enxcol_.encryptedCollection.ecc", + "ecocCollection": "enxcol_.encryptedCollection.ecoc", "fields": [ { "path": "firstName", @@ -1121,17 +1121,17 @@ tests: object: testRunner arguments: database: *database_name - collection: "encryptedCollection.esc" + collection: "enxcol_.encryptedCollection.esc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: "enxcol_.encryptedCollection.ecc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecoc" + collection: "enxcol_.encryptedCollection.ecoc" - name: assertCollectionExists object: testRunner arguments: @@ -1151,17 +1151,17 @@ tests: object: testRunner arguments: database: *database_name - collection: "encryptedCollection.esc" + collection: "enxcol_.encryptedCollection.esc" - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: "enxcol_.encryptedCollection.ecc" - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecoc" + collection: "enxcol_.encryptedCollection.ecoc" - name: assertCollectionNotExists object: testRunner arguments: @@ -1172,17 +1172,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -1194,19 +1194,19 @@ tests: # events from createCollection ... begin - command_started_event: command: - create: "encryptedCollection.esc" + create: "enxcol_.encryptedCollection.esc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecc" + create: "enxcol_.encryptedCollection.ecc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection.ecoc" + create: "enxcol_.encryptedCollection.ecoc" clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name @@ -1242,17 +1242,17 @@ tests: database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.esc" + drop: "enxcol_.encryptedCollection.esc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: "enxcol_.encryptedCollection.ecc" command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecoc" + drop: "enxcol_.encryptedCollection.ecoc" command_name: drop database_name: *database_name - command_started_event: @@ -1260,4 +1260,4 @@ tests: drop: "encryptedCollection" command_name: drop database_name: *database_name - # events from dropCollection ... end + # events from dropCollection ... end \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml b/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml index ba4e7a7361..fff3e24561 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml @@ -15,9 +15,9 @@ tests: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: { "default.default": { - "escCollection": "esc", - "eccCollection": "ecc", - "ecocCollection": "ecoc", + "escCollection": "enxcol_.default.esc", + "eccCollection": "enxcol_.default.ecc", + "ecocCollection": "enxcol_.default.ecoc", "fields": [] } } From 75d392cb9f5041f873b79a513367267a5a42ad19 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 5 Apr 2023 07:56:19 -0600 Subject: [PATCH 113/198] RUBY-3143 split CSFLE and QE docs into separate sections (#2703) --- docs/reference/in-use-encryption.txt | 17 ++ .../client-side-encryption.txt | 223 ----------------- .../queryable-encryption.txt | 236 ++++++++++++++++++ docs/reference/working-with-data.txt | 2 +- 4 files changed, 254 insertions(+), 224 deletions(-) create mode 100644 docs/reference/in-use-encryption.txt rename docs/reference/{ => in-use-encryption}/client-side-encryption.txt (81%) create mode 100644 docs/reference/in-use-encryption/queryable-encryption.txt diff --git a/docs/reference/in-use-encryption.txt b/docs/reference/in-use-encryption.txt new file mode 100644 index 0000000000..165f399b8b --- /dev/null +++ b/docs/reference/in-use-encryption.txt @@ -0,0 +1,17 @@ +.. _in-use-encryption: + +***************** +In-Use Encryption +***************** + +.. default-domain:: mongodb + +This section describes the different encryption methods in use by the Ruby +driver for MongoDB. + +.. toctree:: + :titlesonly: + + /reference/in-use-encryption/queryable-encryption + /reference/in-use-encryption/client-side-encryption + \ No newline at end of file diff --git a/docs/reference/client-side-encryption.txt b/docs/reference/in-use-encryption/client-side-encryption.txt similarity index 81% rename from docs/reference/client-side-encryption.txt rename to docs/reference/in-use-encryption/client-side-encryption.txt index b20ad0d23a..78d1ca73c9 100644 --- a/docs/reference/client-side-encryption.txt +++ b/docs/reference/in-use-encryption/client-side-encryption.txt @@ -375,229 +375,6 @@ master key and create data keys, see the following sections of this tutorial: - :ref:`Creating A Master Key ` - :ref:`Creating A Data Key ` -Queryable Encryption -==================== - -Queryable encryption is a new feature in MongoDB 6.0. It also requires -libmongocrypt version 1.5.2 or above. - -You can find more information about queryable encryption in `MongoDB Manual -`_ - -.. note:: - - The queryable encryption feature is in public technical preview. - Therefore, the following options should be considered experimental - and are subject to change: - - - ``:encrypted_fields_map`` and ``:bypass_query_analysis`` in auto encryption options. - - ``:contention_factor`` and ``:query_type`` in client encryption options. - -Below is an example of using automatic queryable encryption using the Ruby driver: - -.. code-block:: ruby - - require 'mongo' - - ##################################### - # Step 1: Create a local master key # - ##################################### - - # A local master key is a 96-byte binary blob. - local_master_key = SecureRandom.random_bytes(96) - # => "\xB2\xBE\x8EN\xD4\x14\xC2\x13\xC3..." - - ############################# - # Step 2: Create a data key # - ############################# - - kms_providers = { - local: { - key: local_master_key - } - } - - # The key vault client is a Mongo::Client instance - # that will be used to store your data keys. - key_vault_client = Mongo::Client.new('mongodb://localhost:27017,localhost:27018') - - # Use an instance of Mongo::ClientEncryption to create a new data key - client_encryption = Mongo::ClientEncryption.new( - key_vault_client, - key_vault_namespace: 'encryption.__keyVault', - kms_providers: kms_providers - ) - - data_key_id = client_encryption.create_data_key('local') - # => - - ####################################################### - # Step 3: Configure Mongo::Client for auto-encryption # - ####################################################### - - # Create an encrypted fields map, which tells the Mongo::Client which fields to encrypt. - encrypted_fields_map = { - 'encryption_db.encryption_coll' => { - fields: [ - { - path: 'encrypted_field', - bsonType: 'string', - keyId: data_key_id, - queries: { - queryType: 'equality' - } - } - ] - } - } - - # Configure the client for automatic encryption - client = Mongo::Client.new( - 'mongodb://localhost:27017,localhost:27018', - auto_encryption_options: { - key_vault_namespace: 'encryption.__keyVault', - kms_providers: kms_providers, - encrypted_fields_map: encrypted_fields_map, - }, - database: 'encryption_db' - ) - - # Make sure there is no data in the collection. - client.database.drop - - # Create encrypted collection explicitly. - collection = client['encryption_coll'].create - - # The string "sensitive data" will be encrypted and stored in the database - # as ciphertext - collection.insert_one(encrypted_field: 'sensitive data') - - # The data is decrypted before being returned to the user - collection.find(encrypted_field: 'sensitive data').first['encrypted_field'] - # => "sensitive data" - - # A client with no auto_encryption_options is unable to decrypt the data - client_no_encryption = Mongo::Client.new(['localhost:27017'], database: 'encryption_db') - client_no_encryption['encryption_coll'].find.first['encrypted_field'] - # => - -The example above demonstrates using automatic encryption with a local master key. -For more information about using other key management services to create a -master key and create data keys, see the following sections of this tutorial: - -- :ref:`Creating A Master Key ` -- :ref:`Creating A Data Key ` - -Below is an example of explicit queryable encryption. - -.. code-block:: ruby - - require 'mongo' - - ##################################### - # Step 1: Create a local master key # - ##################################### - - # A local master key is a 96-byte binary blob. - local_master_key = SecureRandom.random_bytes(96) - # => "\xB2\xBE\x8EN\xD4\x14\xC2\x13\xC3..." - - ############################# - # Step 2: Create a data key # - ############################# - - kms_providers = { - local: { - key: local_master_key - } - } - - # The key vault client is a Mongo::Client instance - # that will be used to store your data keys. - key_vault_client = Mongo::Client.new('mongodb://localhost:27017,localhost:27018') - - # Use an instance of Mongo::ClientEncryption to create a new data key - client_encryption = Mongo::ClientEncryption.new( - key_vault_client, - key_vault_namespace: 'encryption.__keyVault', - kms_providers: kms_providers - ) - - data_key_id = client_encryption.create_data_key('local') - # => - - ########################################## - # Step 3: Create an encrypted collection # - ########################################## - encrypted_fields = { - fields: [ - { - path: 'encrypted_field', - bsonType: 'string', - keyId: data_key_id, - queries: { - queryType: 'equality', - contention: 0 - } - } - ] - } - - # Create the client you will use to read and write the data to MongoDB - # Please note that to insert or query with an "Indexed" encrypted payload, - # you should use a ``Mongo::Client`` that is configured with ``:auto_encryption_options``. - # ``auto_encryption_options[:bypass_query_analysis]`` may be true. - # ``auto_encryption_options[:bypass_auto_encryption]`` must be not set or false. - client = Mongo::Client.new( - ['localhost:27017'], - auto_encryption_options: { - key_vault_namespace: 'encryption.__keyVault', - kms_providers: kms_providers, - bypass_query_analysis: true, - }, - database: 'encryption_db', - ) - - # Make sure there is no data in the collection. - client['encryption_coll'].drop(encrypted_fields: encrypted_fields) - # Create encrypted collection explicitly. - client['encryption_coll'].create(encrypted_fields: encrypted_fields) - - ##################################################### - # Step 4: Encrypt a string with explicit encryption # - ##################################################### - - # The value to encrypt - value = 'sensitive data' - - # Encrypt the value - insert_payload = client_encryption.encrypt( - 'sensitive data', - { - key_id: data_key_id, - algorithm: "Indexed", - contention_factor: 0 - } - ) - - # Insert the encrypted value into the collection - client['encryption_coll'].insert_one(encrypted_field: insert_payload) - - # Use the client to read the encrypted value from the database, then - # use the ClientEncryption object to decrypt it. - find_payload = client_encryption.encrypt( - 'sensitive data', - { - key_id: data_key_id, - algorithm: "Indexed", - contention_factor: 0, - query_type: "equality" - } - ) - - find_result = client['encryption_coll'].find(encrypted_field: find_payload).first['encrypted_field'] - # => 'sensitive data' - .. _creating-a-master-key: Creating a Master Key diff --git a/docs/reference/in-use-encryption/queryable-encryption.txt b/docs/reference/in-use-encryption/queryable-encryption.txt new file mode 100644 index 0000000000..2be80ed5f2 --- /dev/null +++ b/docs/reference/in-use-encryption/queryable-encryption.txt @@ -0,0 +1,236 @@ +.. _queryable-encryption: + +********************** +Queryable Encryption +********************** + +.. default-domain:: mongodb + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +Queryable encryption is a new feature in MongoDB 6.0. It also requires +libmongocrypt version 1.5.2 or above. + +You can find more information about queryable encryption in `MongoDB Manual +`_. + +.. note:: + + The queryable encryption feature is in public technical preview. + Therefore, the following options should be considered experimental + and are subject to change: + + - ``:encrypted_fields_map`` and ``:bypass_query_analysis`` in auto encryption options. + - ``:contention_factor`` and ``:query_type`` in client encryption options. + +The following examples assume you are familiar with the concepts and techniques +described in :ref:`Client-Side Encryption `. + +Below is an example of using automatic queryable encryption using the Ruby driver: + +.. code-block:: ruby + + require 'mongo' + + ##################################### + # Step 1: Create a local master key # + ##################################### + + # A local master key is a 96-byte binary blob. + local_master_key = SecureRandom.random_bytes(96) + # => "\xB2\xBE\x8EN\xD4\x14\xC2\x13\xC3..." + + ############################# + # Step 2: Create a data key # + ############################# + + kms_providers = { + local: { + key: local_master_key + } + } + + # The key vault client is a Mongo::Client instance + # that will be used to store your data keys. + key_vault_client = Mongo::Client.new('mongodb://localhost:27017,localhost:27018') + + # Use an instance of Mongo::ClientEncryption to create a new data key + client_encryption = Mongo::ClientEncryption.new( + key_vault_client, + key_vault_namespace: 'encryption.__keyVault', + kms_providers: kms_providers + ) + + data_key_id = client_encryption.create_data_key('local') + # => + + ####################################################### + # Step 3: Configure Mongo::Client for auto-encryption # + ####################################################### + + # Create an encrypted fields map, which tells the Mongo::Client which fields to encrypt. + encrypted_fields_map = { + 'encryption_db.encryption_coll' => { + fields: [ + { + path: 'encrypted_field', + bsonType: 'string', + keyId: data_key_id, + queries: { + queryType: 'equality' + } + } + ] + } + } + + # Configure the client for automatic encryption + client = Mongo::Client.new( + 'mongodb://localhost:27017,localhost:27018', + auto_encryption_options: { + key_vault_namespace: 'encryption.__keyVault', + kms_providers: kms_providers, + encrypted_fields_map: encrypted_fields_map, + }, + database: 'encryption_db' + ) + + # Make sure there is no data in the collection. + client.database.drop + + # Create encrypted collection explicitly. + collection = client['encryption_coll'].create + + # The string "sensitive data" will be encrypted and stored in the database + # as ciphertext + collection.insert_one(encrypted_field: 'sensitive data') + + # The data is decrypted before being returned to the user + collection.find(encrypted_field: 'sensitive data').first['encrypted_field'] + # => "sensitive data" + + # A client with no auto_encryption_options is unable to decrypt the data + client_no_encryption = Mongo::Client.new(['localhost:27017'], database: 'encryption_db') + client_no_encryption['encryption_coll'].find.first['encrypted_field'] + # => + +The example above demonstrates using automatic encryption with a local master key. +For more information about using other key management services to create a +master key and create data keys, see the following sections of the :ref:`Client-Side Encryption ` tutorial: + +- :ref:`Creating A Master Key ` +- :ref:`Creating A Data Key ` + +Below is an example of explicit queryable encryption. + +.. code-block:: ruby + + require 'mongo' + + ##################################### + # Step 1: Create a local master key # + ##################################### + + # A local master key is a 96-byte binary blob. + local_master_key = SecureRandom.random_bytes(96) + # => "\xB2\xBE\x8EN\xD4\x14\xC2\x13\xC3..." + + ############################# + # Step 2: Create a data key # + ############################# + + kms_providers = { + local: { + key: local_master_key + } + } + + # The key vault client is a Mongo::Client instance + # that will be used to store your data keys. + key_vault_client = Mongo::Client.new('mongodb://localhost:27017,localhost:27018') + + # Use an instance of Mongo::ClientEncryption to create a new data key + client_encryption = Mongo::ClientEncryption.new( + key_vault_client, + key_vault_namespace: 'encryption.__keyVault', + kms_providers: kms_providers + ) + + data_key_id = client_encryption.create_data_key('local') + # => + + ########################################## + # Step 3: Create an encrypted collection # + ########################################## + encrypted_fields = { + fields: [ + { + path: 'encrypted_field', + bsonType: 'string', + keyId: data_key_id, + queries: { + queryType: 'equality', + contention: 0 + } + } + ] + } + + # Create the client you will use to read and write the data to MongoDB + # Please note that to insert or query with an "Indexed" encrypted payload, + # you should use a ``Mongo::Client`` that is configured with ``:auto_encryption_options``. + # ``auto_encryption_options[:bypass_query_analysis]`` may be true. + # ``auto_encryption_options[:bypass_auto_encryption]`` must be not set or false. + client = Mongo::Client.new( + ['localhost:27017'], + auto_encryption_options: { + key_vault_namespace: 'encryption.__keyVault', + kms_providers: kms_providers, + bypass_query_analysis: true, + }, + database: 'encryption_db', + ) + + # Make sure there is no data in the collection. + client['encryption_coll'].drop(encrypted_fields: encrypted_fields) + # Create encrypted collection explicitly. + client['encryption_coll'].create(encrypted_fields: encrypted_fields) + + ##################################################### + # Step 4: Encrypt a string with explicit encryption # + ##################################################### + + # The value to encrypt + value = 'sensitive data' + + # Encrypt the value + insert_payload = client_encryption.encrypt( + 'sensitive data', + { + key_id: data_key_id, + algorithm: "Indexed", + contention_factor: 0 + } + ) + + # Insert the encrypted value into the collection + client['encryption_coll'].insert_one(encrypted_field: insert_payload) + + # Use the client to read the encrypted value from the database, then + # use the ClientEncryption object to decrypt it. + find_payload = client_encryption.encrypt( + 'sensitive data', + { + key_id: data_key_id, + algorithm: "Indexed", + contention_factor: 0, + query_type: "equality" + } + ) + + find_result = client['encryption_coll'].find(encrypted_field: find_payload).first['encrypted_field'] + # => 'sensitive data' diff --git a/docs/reference/working-with-data.txt b/docs/reference/working-with-data.txt index 3382428f8e..0f36c6be47 100644 --- a/docs/reference/working-with-data.txt +++ b/docs/reference/working-with-data.txt @@ -24,4 +24,4 @@ implements for inserting, updating and retrieving data from MongoDB. /reference/change-streams /reference/sessions /reference/transactions - /reference/client-side-encryption + /reference/in-use-encryption From 413555d553148d8bbf8bea2855cbd9929f3ba587 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 6 Apr 2023 19:16:59 +0200 Subject: [PATCH 114/198] RUBY-3154 Fix broken mongocryptd spec (#2704) --- .../mongocryptd_prose_spec.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb index 46cfbc21f8..7b34b0e70a 100644 --- a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb +++ b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb @@ -30,6 +30,15 @@ ) end + before(:each) do + skip 'This test requires crypt shared library' unless SpecConfig.instance.crypt_shared_lib_path + + key_vault_collection.drop + key_vault_collection.insert_one(data_key) + + encryption_client['users'].drop + end + context 'when shared library is loaded' do let(:extra_options) do { @@ -70,8 +79,6 @@ def done! end it 'does not try to connect to mongocryptd' do - skip 'This test requires crypt shared library' unless SpecConfig.instance.crypt_shared_lib_path - encryption_client[:users].insert_one(ssn: ssn) expect(connect_attempt.done?).to eq(false) end @@ -88,8 +95,6 @@ def done! end it 'does not spawn mongocryptd' do - skip 'This test requires crypt shared library' unless SpecConfig.instance.crypt_shared_lib_path - expect do encryption_client[:users].insert_one(ssn: ssn) end.not_to raise_error From aa0813e0eaf0e235332b6b23fa4818965d08a4d7 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Mon, 10 Apr 2023 13:10:47 -0400 Subject: [PATCH 115/198] RUBY-3219: Add link for AWS Lambda connection to Atlas (#2707) --- docs/reference/create-client.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index c20f4d77ee..b71a866910 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -363,6 +363,13 @@ when connecting to a MongoDB deployment and verify that the connection is succes client.close end +Connect to MongoDB Atlas from AWS Lambda +---------------------------------------- + +To learn how to connect to Atlas from AWS Lambda, see the +`Manage Connections with AWS Lambda `__ +documentation. + .. _srv-uri-notes: SRV URI Notes From 30bcf22893b6904eeaad90f83a94c2309afdd06d Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 12 Apr 2023 15:04:08 -0600 Subject: [PATCH 116/198] RUBY-3154 Test crypt_shared with older server versions (#2705) * RUBY-3154 run all encryption tests against both mongocryptd and crypt_shared * allow fork_reconnect_spec to run on a mac * let's try disabling these tests on jruby... * no need to overcomplicate the without_crypt_shared_lib_path helper * lock older server versions to recent crypt_shared --- .evergreen/config.yml | 5 +++ .evergreen/config/axes.yml.erb | 4 ++ .evergreen/config/common.yml.erb | 1 + .evergreen/run-tests.sh | 2 +- spec/integration/fork_reconnect_spec.rb | 2 +- .../spec_tests/client_side_encryption_spec.rb | 18 +++++++- .../client_side_encryption_unified_spec.rb | 12 +++++- spec/support/spec_config.rb | 22 +++++++++- spec/support/utils.rb | 41 +++++++++++++------ 9 files changed, 89 insertions(+), 18 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index f722724598..a6a85b56fb 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -106,6 +106,7 @@ functions: export COMPRESSOR=${COMPRESSOR} export RVM_RUBY="${RVM_RUBY}" export MONGODB_VERSION=${MONGODB_VERSION} + export CRYPT_SHARED_VERSION=${CRYPT_SHARED_VERSION} export FCV=${FCV} export MONGO_RUBY_DRIVER_LINT=${LINT} export RETRY_READS=${RETRY_READS} @@ -616,18 +617,22 @@ axes: display_name: "5.3" variables: MONGODB_VERSION: "5.3" + CRYPT_SHARED_VERSION: "6.0.5" - id: "5.0" display_name: "5.0" variables: MONGODB_VERSION: "5.0" + CRYPT_SHARED_VERSION: "6.0.5" - id: "4.4" display_name: "4.4" variables: MONGODB_VERSION: "4.4" + CRYPT_SHARED_VERSION: "6.0.5" - id: "4.2" display_name: "4.2" variables: MONGODB_VERSION: "4.2" + CRYPT_SHARED_VERSION: "6.0.5" - id: "4.0" display_name: "4.0" variables: diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index 8abaa90043..e26b049b45 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -25,18 +25,22 @@ axes: display_name: "5.3" variables: MONGODB_VERSION: "5.3" + CRYPT_SHARED_VERSION: "6.0.5" - id: "5.0" display_name: "5.0" variables: MONGODB_VERSION: "5.0" + CRYPT_SHARED_VERSION: "6.0.5" - id: "4.4" display_name: "4.4" variables: MONGODB_VERSION: "4.4" + CRYPT_SHARED_VERSION: "6.0.5" - id: "4.2" display_name: "4.2" variables: MONGODB_VERSION: "4.2" + CRYPT_SHARED_VERSION: "6.0.5" - id: "4.0" display_name: "4.0" variables: diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index f0b3ca74a1..f12903d8a3 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -104,6 +104,7 @@ functions: export COMPRESSOR=${COMPRESSOR} export RVM_RUBY="${RVM_RUBY}" export MONGODB_VERSION=${MONGODB_VERSION} + export CRYPT_SHARED_VERSION=${CRYPT_SHARED_VERSION} export FCV=${FCV} export MONGO_RUBY_DRIVER_LINT=${LINT} export RETRY_READS=${RETRY_READS} diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index bd4ec1a5e9..193fa92e3c 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -165,7 +165,7 @@ fi if test -n "$FLE"; then # Downloading crypt shared lib if [ -z "$MONGO_CRYPT_SHARED_DOWNLOAD_URL" ]; then - crypt_shared_version=$("${BINDIR}"/mongod --version | grep -oP 'db version v\K.*') + crypt_shared_version=${CRYPT_SHARED_VERSION:-$("${BINDIR}"/mongod --version | grep -oP 'db version v\K.*')} python3 -u .evergreen/mongodl.py --component crypt_shared -V ${crypt_shared_version} --out $(pwd)/csfle_lib --target $(host_distro) || true if test -f $(pwd)/csfle_lib/lib/mongo_crypt_v1.so then diff --git a/spec/integration/fork_reconnect_spec.rb b/spec/integration/fork_reconnect_spec.rb index a7631d1693..1b573ea9fe 100644 --- a/spec/integration/fork_reconnect_spec.rb +++ b/spec/integration/fork_reconnect_spec.rb @@ -42,7 +42,7 @@ child_socket.object_id.should_not == socket.object_id # Exec so that we do not close any clients etc. in the child. - exec('/bin/true') + exec(Utils::BIN_TRUE) end # Connection should remain serviceable in the parent. diff --git a/spec/spec_tests/client_side_encryption_spec.rb b/spec/spec_tests/client_side_encryption_spec.rb index 7f5000fd60..67b01299a8 100644 --- a/spec/spec_tests/client_side_encryption_spec.rb +++ b/spec/spec_tests/client_side_encryption_spec.rb @@ -10,5 +10,21 @@ require_libmongocrypt require_enterprise - define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: true) + context 'with mongocryptd' do + SpecConfig.instance.without_crypt_shared_lib_path do + define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: true) + end + end + + context 'with crypt_shared' do + # Under JRuby+Evergreen, these specs complain about the crypt_shared + # library not loading; however, crypt_shared appears to load for other + # specs that require it (see the client_side_encryption_unified_spec and + # mongocryptd_prose_spec tests). + fails_on_jruby + + SpecConfig.instance.require_crypt_shared do + define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: true) + end + end end diff --git a/spec/spec_tests/client_side_encryption_unified_spec.rb b/spec/spec_tests/client_side_encryption_unified_spec.rb index 9cee069e7a..5422b93e19 100644 --- a/spec/spec_tests/client_side_encryption_unified_spec.rb +++ b/spec/spec_tests/client_side_encryption_unified_spec.rb @@ -12,5 +12,15 @@ require_libmongocrypt require_enterprise - define_unified_spec_tests(base, CLIENT_SIDE_ENCRYPTION_UNIFIED_TESTS) + context 'with mongocryptd' do + SpecConfig.instance.without_crypt_shared_lib_path do + define_unified_spec_tests(base, CLIENT_SIDE_ENCRYPTION_UNIFIED_TESTS) + end + end + + context 'with crypt_shared' do + SpecConfig.instance.require_crypt_shared do + define_unified_spec_tests(base, CLIENT_SIDE_ENCRYPTION_UNIFIED_TESTS) + end + end end diff --git a/spec/support/spec_config.rb b/spec/support/spec_config.rb index ac87d7d193..a86b087c6e 100644 --- a/spec/support/spec_config.rb +++ b/spec/support/spec_config.rb @@ -489,7 +489,27 @@ def mongocryptd_port end def crypt_shared_lib_path - ENV['MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH'] + if @without_crypt_shared_lib_path + nil + else + ENV['MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH'] + end + end + + def without_crypt_shared_lib_path + saved, @without_crypt_shared_lib_path = @without_crypt_shared_lib_path, true + yield + ensure + @without_crypt_shared_lib_path = saved + end + + attr_accessor :crypt_shared_lib_required + + def require_crypt_shared + saved, self.crypt_shared_lib_required = crypt_shared_lib_required, true + yield + ensure + self.crypt_shared_lib_required = saved end def auth? diff --git a/spec/support/utils.rb b/spec/support/utils.rb index bce1495565..0a206c50e3 100644 --- a/spec/support/utils.rb +++ b/spec/support/utils.rb @@ -138,16 +138,8 @@ def convert_client_options(spec_test_options) mapper = Mongo::URI::OptionsMapper.new spec_test_options.reduce({}) do |opts, (name, value)| if name == 'autoEncryptOpts' - opts.merge!( - auto_encryption_options: convert_auto_encryption_client_options(value) - .merge( - # Spawn mongocryptd on non-default port for sharded cluster tests - extra_options: { - mongocryptd_spawn_args: ["--port=#{SpecConfig.instance.mongocryptd_port}"], - mongocryptd_uri: "mongodb://localhost:#{SpecConfig.instance.mongocryptd_port}", - } - ) - ) + auto_encryption_options = convert_auto_encryption_client_options(value) + opts.merge!(auto_encryption_options: auto_encryption_options) else mapper.add_uri_option(name, value.to_s, opts) end @@ -206,10 +198,30 @@ def convert_client_options(spec_test_options) auto_encrypt_opts[:encrypted_fields_map] = BSON::ExtJSON.parse_obj(opts['encryptedFieldsMap']) end - auto_encrypt_opts + auto_encrypt_opts.merge!(extra_options: convert_auto_encryption_extra_options(auto_encrypt_opts)) end module_function :convert_auto_encryption_client_options + private def convert_auto_encryption_extra_options(opts) + # Spawn mongocryptd on non-default port for sharded cluster tests + extra_options = { + mongocryptd_spawn_args: [ "--port=#{SpecConfig.instance.mongocryptd_port}" ], + mongocryptd_uri: "mongodb://localhost:#{SpecConfig.instance.mongocryptd_port}" + }.merge(opts[:extra_options] || {}) + + # if bypass_query_analysis has been explicitly specified, then we ignore + # any requirement to use the shared library, as the two are not + # compatible. + if SpecConfig.instance.crypt_shared_lib_required && !opts[:bypass_query_analysis] + extra_options[:crypt_shared_lib_required] = SpecConfig.instance.crypt_shared_lib_required + extra_options[:crypt_shared_lib_path] = SpecConfig.instance.crypt_shared_lib_path + extra_options[:mongocryptd_uri] = "mongodb://localhost:27777" + end + + extra_options + end + module_function :convert_auto_encryption_extra_options + def order_hash(hash) Hash[hash.to_a.sort] end @@ -539,15 +551,18 @@ def match_with_type?(expected, actual) end end + BIN_FALSE = File.executable?("/bin/false") ? "/bin/false" : "false" + BIN_TRUE = File.executable?("/bin/true") ? "/bin/true" : "true" + module_function def wrap_forked_child begin yield rescue => e STDERR.puts "Failing process #{Process.pid} due to #{e.class}: #{e}" - exec('/bin/false') + exec(BIN_FALSE) else # Exec so that we do not close any clients etc. in the child. - exec('/bin/true') + exec(BIN_TRUE) end end From a0ed71fed54511c2e022e2f08a8b5e73299701e1 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 24 Apr 2023 08:16:46 -0600 Subject: [PATCH 117/198] Enable all cops, but opt all files out of rubocop (#2708) * Enable all cops, but opt all files out of rubocop * only install rubocop on ruby >= 2.6 * ignore rubocop for jrubies as well * solargraph 0.49 depends on rbs, which won't build for jruby for some reason * restrict solargraph to mri * fix typo --- .rubocop.yml | 38 ++++++++++++++----- Gemfile | 3 ++ Rakefile | 3 +- bin/mongo_console | 2 + examples/aggregate.rb | 2 +- examples/create.rb | 2 +- examples/delete.rb | 2 +- examples/index.rb | 2 +- examples/query.rb | 2 +- examples/update.rb | 2 +- gemfiles/bson_4-stable.gemfile | 1 + gemfiles/bson_master.gemfile | 1 + gemfiles/bson_min.gemfile | 1 + gemfiles/mongo_kerberos.gemfile | 1 + gemfiles/snappy_compression.gemfile | 1 + gemfiles/standard.rb | 15 ++++++-- gemfiles/zstd_compression.gemfile | 1 + lib/mongo.rb | 2 +- lib/mongo/active_support.rb | 2 +- lib/mongo/address.rb | 2 +- lib/mongo/address/ipv4.rb | 2 +- lib/mongo/address/ipv6.rb | 2 +- lib/mongo/address/unix.rb | 2 +- lib/mongo/address/validator.rb | 2 +- lib/mongo/auth.rb | 2 +- lib/mongo/auth/aws.rb | 2 +- lib/mongo/auth/aws/conversation.rb | 2 +- lib/mongo/auth/aws/credentials.rb | 1 + lib/mongo/auth/aws/credentials_cache.rb | 1 + lib/mongo/auth/aws/credentials_retriever.rb | 2 +- lib/mongo/auth/aws/request.rb | 2 +- lib/mongo/auth/base.rb | 2 +- lib/mongo/auth/conversation_base.rb | 2 +- lib/mongo/auth/cr.rb | 2 +- lib/mongo/auth/cr/conversation.rb | 2 +- lib/mongo/auth/credential_cache.rb | 2 +- lib/mongo/auth/gssapi.rb | 2 +- lib/mongo/auth/gssapi/conversation.rb | 2 +- lib/mongo/auth/ldap.rb | 2 +- lib/mongo/auth/ldap/conversation.rb | 2 +- lib/mongo/auth/roles.rb | 2 +- lib/mongo/auth/sasl_conversation_base.rb | 2 +- lib/mongo/auth/scram.rb | 2 +- lib/mongo/auth/scram/conversation.rb | 2 +- lib/mongo/auth/scram256.rb | 2 +- lib/mongo/auth/scram256/conversation.rb | 2 +- lib/mongo/auth/scram_conversation_base.rb | 2 +- lib/mongo/auth/stringprep.rb | 2 +- lib/mongo/auth/stringprep/profiles/sasl.rb | 2 +- lib/mongo/auth/stringprep/tables.rb | 2 +- .../stringprep/unicode_normalize/normalize.rb | 2 +- .../stringprep/unicode_normalize/tables.rb | 3 +- lib/mongo/auth/user.rb | 2 +- lib/mongo/auth/user/view.rb | 2 +- lib/mongo/auth/x509.rb | 2 +- lib/mongo/auth/x509/conversation.rb | 2 +- lib/mongo/background_thread.rb | 2 +- lib/mongo/bson.rb | 2 +- lib/mongo/bulk_write.rb | 2 +- lib/mongo/bulk_write/combineable.rb | 2 +- lib/mongo/bulk_write/ordered_combiner.rb | 2 +- lib/mongo/bulk_write/result.rb | 2 +- lib/mongo/bulk_write/result_combiner.rb | 2 +- lib/mongo/bulk_write/transformable.rb | 2 +- lib/mongo/bulk_write/unordered_combiner.rb | 2 +- lib/mongo/bulk_write/validatable.rb | 2 +- lib/mongo/caching_cursor.rb | 2 +- lib/mongo/client.rb | 2 +- lib/mongo/client_encryption.rb | 2 +- lib/mongo/cluster.rb | 2 +- lib/mongo/cluster/periodic_executor.rb | 2 +- lib/mongo/cluster/reapers/cursor_reaper.rb | 2 +- lib/mongo/cluster/reapers/socket_reaper.rb | 2 +- lib/mongo/cluster/sdam_flow.rb | 2 +- lib/mongo/cluster/topology.rb | 2 +- lib/mongo/cluster/topology/base.rb | 2 +- lib/mongo/cluster/topology/load_balanced.rb | 2 +- .../topology/no_replica_set_options.rb | 2 +- .../topology/replica_set_no_primary.rb | 2 +- .../topology/replica_set_with_primary.rb | 2 +- lib/mongo/cluster/topology/sharded.rb | 2 +- lib/mongo/cluster/topology/single.rb | 2 +- lib/mongo/cluster/topology/unknown.rb | 2 +- lib/mongo/cluster_time.rb | 2 +- lib/mongo/collection.rb | 2 +- lib/mongo/collection/helpers.rb | 2 +- lib/mongo/collection/queryable_encryption.rb | 2 +- lib/mongo/collection/view.rb | 2 +- lib/mongo/collection/view/aggregation.rb | 2 +- lib/mongo/collection/view/builder.rb | 2 +- .../collection/view/builder/aggregation.rb | 2 +- .../collection/view/builder/map_reduce.rb | 2 +- lib/mongo/collection/view/change_stream.rb | 2 +- .../view/change_stream/retryable.rb | 2 +- lib/mongo/collection/view/explainable.rb | 2 +- lib/mongo/collection/view/immutable.rb | 2 +- lib/mongo/collection/view/iterable.rb | 2 +- lib/mongo/collection/view/map_reduce.rb | 2 +- lib/mongo/collection/view/readable.rb | 2 +- lib/mongo/collection/view/writable.rb | 2 +- lib/mongo/condition_variable.rb | 2 +- lib/mongo/config.rb | 1 + lib/mongo/config/options.rb | 1 + lib/mongo/config/validators/option.rb | 1 + lib/mongo/crypt.rb | 2 +- lib/mongo/crypt/auto_decryption_context.rb | 2 +- lib/mongo/crypt/auto_encrypter.rb | 2 +- lib/mongo/crypt/auto_encryption_context.rb | 2 +- lib/mongo/crypt/binary.rb | 2 +- lib/mongo/crypt/binding.rb | 2 +- lib/mongo/crypt/context.rb | 2 +- lib/mongo/crypt/data_key_context.rb | 2 +- lib/mongo/crypt/encryption_io.rb | 2 +- .../crypt/explicit_decryption_context.rb | 2 +- lib/mongo/crypt/explicit_encrypter.rb | 2 +- .../crypt/explicit_encryption_context.rb | 2 +- .../explicit_encryption_expression_context.rb | 2 +- lib/mongo/crypt/handle.rb | 2 +- lib/mongo/crypt/hooks.rb | 2 +- lib/mongo/crypt/kms.rb | 2 +- lib/mongo/crypt/kms/aws.rb | 2 +- lib/mongo/crypt/kms/aws/credentials.rb | 2 +- lib/mongo/crypt/kms/aws/master_document.rb | 2 +- lib/mongo/crypt/kms/azure.rb | 2 +- lib/mongo/crypt/kms/azure/credentials.rb | 2 +- lib/mongo/crypt/kms/azure/master_document.rb | 2 +- lib/mongo/crypt/kms/credentials.rb | 2 +- lib/mongo/crypt/kms/gcp.rb | 2 +- lib/mongo/crypt/kms/gcp/credentials.rb | 2 +- .../crypt/kms/gcp/credentials_retriever.rb | 2 +- lib/mongo/crypt/kms/gcp/master_document.rb | 2 +- lib/mongo/crypt/kms/kmip.rb | 2 +- lib/mongo/crypt/kms/kmip/credentials.rb | 2 +- lib/mongo/crypt/kms/kmip/master_document.rb | 2 +- lib/mongo/crypt/kms/local.rb | 2 +- lib/mongo/crypt/kms/local/credentials.rb | 2 +- lib/mongo/crypt/kms/local/master_document.rb | 2 +- lib/mongo/crypt/kms/master_key_document.rb | 2 +- lib/mongo/crypt/kms_context.rb | 2 +- .../crypt/rewrap_many_data_key_context.rb | 2 +- .../crypt/rewrap_many_data_key_result.rb | 2 +- lib/mongo/crypt/status.rb | 2 +- lib/mongo/cursor.rb | 2 +- lib/mongo/cursor/kill_spec.rb | 2 +- lib/mongo/database.rb | 2 +- lib/mongo/database/view.rb | 2 +- lib/mongo/dbref.rb | 2 +- lib/mongo/distinguishing_semaphore.rb | 2 +- lib/mongo/error.rb | 2 +- lib/mongo/error/auth_error.rb | 2 +- lib/mongo/error/bad_load_balancer_target.rb | 2 +- lib/mongo/error/bulk_write_error.rb | 2 +- lib/mongo/error/change_stream_resumable.rb | 2 +- lib/mongo/error/client_closed.rb | 2 +- lib/mongo/error/closed_stream.rb | 2 +- .../error/connection_check_out_timeout.rb | 2 +- lib/mongo/error/connection_perished.rb | 2 +- lib/mongo/error/connection_unavailable.rb | 2 +- lib/mongo/error/credential_check_error.rb | 2 +- lib/mongo/error/crypt_error.rb | 2 +- lib/mongo/error/extra_file_chunk.rb | 2 +- .../error/failed_string_prep_validation.rb | 2 +- lib/mongo/error/file_not_found.rb | 2 +- lib/mongo/error/handshake_error.rb | 2 +- .../error/insufficient_iteration_count.rb | 2 +- lib/mongo/error/internal_driver_error.rb | 2 +- lib/mongo/error/invalid_address.rb | 2 +- lib/mongo/error/invalid_application_name.rb | 2 +- lib/mongo/error/invalid_bulk_operation.rb | 2 +- .../error/invalid_bulk_operation_type.rb | 2 +- lib/mongo/error/invalid_collection_name.rb | 2 +- lib/mongo/error/invalid_config_option.rb | 1 + lib/mongo/error/invalid_cursor_operation.rb | 2 +- lib/mongo/error/invalid_database_name.rb | 2 +- lib/mongo/error/invalid_document.rb | 2 +- lib/mongo/error/invalid_file.rb | 2 +- lib/mongo/error/invalid_file_revision.rb | 2 +- lib/mongo/error/invalid_min_pool_size.rb | 2 +- lib/mongo/error/invalid_nonce.rb | 2 +- lib/mongo/error/invalid_read_concern.rb | 2 +- lib/mongo/error/invalid_read_option.rb | 2 +- .../error/invalid_replacement_document.rb | 2 +- lib/mongo/error/invalid_server_auth_host.rb | 2 +- .../error/invalid_server_auth_response.rb | 2 +- lib/mongo/error/invalid_server_preference.rb | 2 +- lib/mongo/error/invalid_session.rb | 2 +- lib/mongo/error/invalid_signature.rb | 2 +- .../error/invalid_transaction_operation.rb | 2 +- lib/mongo/error/invalid_txt_record.rb | 2 +- lib/mongo/error/invalid_update_document.rb | 2 +- lib/mongo/error/invalid_uri.rb | 2 +- lib/mongo/error/invalid_write_concern.rb | 2 +- lib/mongo/error/kms_error.rb | 2 +- lib/mongo/error/labelable.rb | 2 +- lib/mongo/error/lint_error.rb | 2 +- lib/mongo/error/max_bson_size.rb | 2 +- lib/mongo/error/max_message_size.rb | 2 +- lib/mongo/error/mismatched_domain.rb | 2 +- lib/mongo/error/missing_connection.rb | 2 +- lib/mongo/error/missing_file_chunk.rb | 2 +- lib/mongo/error/missing_password.rb | 2 +- lib/mongo/error/missing_resume_token.rb | 2 +- .../error/missing_scram_server_signature.rb | 2 +- lib/mongo/error/missing_service_id.rb | 2 +- lib/mongo/error/mongocryptd_spawn_error.rb | 2 +- lib/mongo/error/multi_index_drop.rb | 2 +- lib/mongo/error/need_primary_server.rb | 2 +- lib/mongo/error/no_server_available.rb | 2 +- .../error/no_service_connection_available.rb | 2 +- lib/mongo/error/no_srv_records.rb | 2 +- lib/mongo/error/notable.rb | 2 +- lib/mongo/error/operation_failure.rb | 2 +- lib/mongo/error/parser.rb | 2 +- lib/mongo/error/pool_cleared_error.rb | 2 +- lib/mongo/error/pool_closed_error.rb | 2 +- lib/mongo/error/pool_error.rb | 2 +- lib/mongo/error/pool_paused_error.rb | 2 +- lib/mongo/error/raise_original_error.rb | 2 +- lib/mongo/error/read_write_retryable.rb | 2 +- lib/mongo/error/sdam_error_detection.rb | 2 +- lib/mongo/error/server_api_conflict.rb | 2 +- lib/mongo/error/server_api_not_supported.rb | 2 +- lib/mongo/error/server_certificate_revoked.rb | 2 +- lib/mongo/error/server_not_usable.rb | 2 +- lib/mongo/error/session_ended.rb | 2 +- lib/mongo/error/session_not_materialized.rb | 2 +- lib/mongo/error/sessions_not_supported.rb | 2 +- ...snapshot_session_invalid_server_version.rb | 2 +- ...snapshot_session_transaction_prohibited.rb | 2 +- lib/mongo/error/socket_error.rb | 2 +- lib/mongo/error/socket_timeout_error.rb | 2 +- .../error/unchangeable_collection_option.rb | 2 +- lib/mongo/error/unexpected_chunk_length.rb | 2 +- lib/mongo/error/unexpected_response.rb | 2 +- lib/mongo/error/unknown_payload_type.rb | 2 +- lib/mongo/error/unmet_dependency.rb | 2 +- lib/mongo/error/unsupported_array_filters.rb | 2 +- lib/mongo/error/unsupported_collation.rb | 2 +- lib/mongo/error/unsupported_features.rb | 2 +- lib/mongo/error/unsupported_message_type.rb | 2 +- lib/mongo/error/unsupported_option.rb | 2 +- lib/mongo/error/write_retryable.rb | 2 +- lib/mongo/event.rb | 2 +- lib/mongo/event/base.rb | 2 +- lib/mongo/event/listeners.rb | 2 +- lib/mongo/event/publisher.rb | 2 +- lib/mongo/event/subscriber.rb | 2 +- lib/mongo/grid.rb | 2 +- lib/mongo/grid/file.rb | 2 +- lib/mongo/grid/file/chunk.rb | 2 +- lib/mongo/grid/file/info.rb | 2 +- lib/mongo/grid/fs_bucket.rb | 2 +- lib/mongo/grid/stream.rb | 2 +- lib/mongo/grid/stream/read.rb | 2 +- lib/mongo/grid/stream/write.rb | 2 +- lib/mongo/id.rb | 2 +- lib/mongo/index.rb | 2 +- lib/mongo/index/view.rb | 2 +- lib/mongo/lint.rb | 2 +- lib/mongo/loggable.rb | 2 +- lib/mongo/logger.rb | 2 +- lib/mongo/monitoring.rb | 2 +- lib/mongo/monitoring/cmap_log_subscriber.rb | 2 +- .../monitoring/command_log_subscriber.rb | 2 +- lib/mongo/monitoring/event.rb | 2 +- lib/mongo/monitoring/event/cmap.rb | 2 +- lib/mongo/monitoring/event/cmap/base.rb | 2 +- .../event/cmap/connection_check_out_failed.rb | 2 +- .../cmap/connection_check_out_started.rb | 2 +- .../event/cmap/connection_checked_in.rb | 2 +- .../event/cmap/connection_checked_out.rb | 2 +- .../event/cmap/connection_closed.rb | 2 +- .../event/cmap/connection_created.rb | 2 +- .../monitoring/event/cmap/connection_ready.rb | 2 +- .../monitoring/event/cmap/pool_cleared.rb | 2 +- .../monitoring/event/cmap/pool_closed.rb | 2 +- .../monitoring/event/cmap/pool_created.rb | 2 +- lib/mongo/monitoring/event/cmap/pool_ready.rb | 2 +- lib/mongo/monitoring/event/command_failed.rb | 2 +- lib/mongo/monitoring/event/command_started.rb | 2 +- .../monitoring/event/command_succeeded.rb | 2 +- lib/mongo/monitoring/event/secure.rb | 2 +- lib/mongo/monitoring/event/server_closed.rb | 2 +- .../event/server_description_changed.rb | 2 +- .../event/server_heartbeat_failed.rb | 2 +- .../event/server_heartbeat_started.rb | 2 +- .../event/server_heartbeat_succeeded.rb | 2 +- lib/mongo/monitoring/event/server_opening.rb | 2 +- .../monitoring/event/topology_changed.rb | 2 +- lib/mongo/monitoring/event/topology_closed.rb | 2 +- .../monitoring/event/topology_opening.rb | 2 +- lib/mongo/monitoring/publishable.rb | 2 +- lib/mongo/monitoring/sdam_log_subscriber.rb | 2 +- .../server_closed_log_subscriber.rb | 2 +- ...rver_description_changed_log_subscriber.rb | 2 +- .../server_opening_log_subscriber.rb | 2 +- .../topology_changed_log_subscriber.rb | 2 +- .../topology_closed_log_subscriber.rb | 2 +- .../topology_opening_log_subscriber.rb | 2 +- .../monitoring/unified_sdam_log_subscriber.rb | 2 +- lib/mongo/operation.rb | 2 +- lib/mongo/operation/aggregate.rb | 2 +- lib/mongo/operation/aggregate/op_msg.rb | 2 +- lib/mongo/operation/aggregate/result.rb | 2 +- lib/mongo/operation/collections_info.rb | 2 +- .../operation/collections_info/result.rb | 2 +- lib/mongo/operation/command.rb | 2 +- lib/mongo/operation/command/op_msg.rb | 2 +- lib/mongo/operation/context.rb | 2 +- lib/mongo/operation/count.rb | 2 +- lib/mongo/operation/count/op_msg.rb | 2 +- lib/mongo/operation/create.rb | 2 +- lib/mongo/operation/create/op_msg.rb | 2 +- lib/mongo/operation/create_index.rb | 2 +- lib/mongo/operation/create_index/op_msg.rb | 2 +- lib/mongo/operation/create_user.rb | 2 +- lib/mongo/operation/create_user/op_msg.rb | 2 +- lib/mongo/operation/delete.rb | 2 +- lib/mongo/operation/delete/bulk_result.rb | 2 +- lib/mongo/operation/delete/op_msg.rb | 2 +- lib/mongo/operation/delete/result.rb | 2 +- lib/mongo/operation/distinct.rb | 2 +- lib/mongo/operation/distinct/op_msg.rb | 2 +- lib/mongo/operation/drop.rb | 2 +- lib/mongo/operation/drop/op_msg.rb | 2 +- lib/mongo/operation/drop_database.rb | 2 +- lib/mongo/operation/drop_database/op_msg.rb | 2 +- lib/mongo/operation/drop_index.rb | 2 +- lib/mongo/operation/drop_index/op_msg.rb | 2 +- lib/mongo/operation/explain.rb | 2 +- lib/mongo/operation/explain/op_msg.rb | 2 +- lib/mongo/operation/explain/result.rb | 2 +- lib/mongo/operation/find.rb | 2 +- lib/mongo/operation/find/builder.rb | 2 +- lib/mongo/operation/find/builder/command.rb | 2 +- lib/mongo/operation/find/builder/flags.rb | 2 +- lib/mongo/operation/find/builder/modifiers.rb | 2 +- lib/mongo/operation/find/op_msg.rb | 2 +- lib/mongo/operation/find/result.rb | 2 +- lib/mongo/operation/get_more.rb | 2 +- .../operation/get_more/command_builder.rb | 2 +- lib/mongo/operation/get_more/op_msg.rb | 2 +- lib/mongo/operation/get_more/result.rb | 2 +- lib/mongo/operation/indexes.rb | 2 +- lib/mongo/operation/indexes/op_msg.rb | 2 +- lib/mongo/operation/indexes/result.rb | 2 +- lib/mongo/operation/insert.rb | 2 +- lib/mongo/operation/insert/bulk_result.rb | 2 +- lib/mongo/operation/insert/op_msg.rb | 2 +- lib/mongo/operation/insert/result.rb | 2 +- lib/mongo/operation/kill_cursors.rb | 2 +- .../operation/kill_cursors/command_builder.rb | 2 +- lib/mongo/operation/kill_cursors/op_msg.rb | 2 +- lib/mongo/operation/list_collections.rb | 2 +- .../operation/list_collections/op_msg.rb | 2 +- .../operation/list_collections/result.rb | 2 +- lib/mongo/operation/map_reduce.rb | 2 +- lib/mongo/operation/map_reduce/op_msg.rb | 2 +- lib/mongo/operation/map_reduce/result.rb | 2 +- lib/mongo/operation/op_msg_base.rb | 2 +- lib/mongo/operation/parallel_scan.rb | 2 +- lib/mongo/operation/parallel_scan/op_msg.rb | 2 +- lib/mongo/operation/parallel_scan/result.rb | 2 +- lib/mongo/operation/remove_user.rb | 2 +- lib/mongo/operation/remove_user/op_msg.rb | 2 +- lib/mongo/operation/result.rb | 2 +- .../shared/bypass_document_validation.rb | 2 +- .../shared/causal_consistency_supported.rb | 2 +- lib/mongo/operation/shared/executable.rb | 2 +- .../shared/executable_no_validate.rb | 2 +- .../shared/executable_transaction_label.rb | 2 +- lib/mongo/operation/shared/idable.rb | 2 +- lib/mongo/operation/shared/limited.rb | 2 +- .../operation/shared/object_id_generator.rb | 2 +- .../operation/shared/op_msg_executable.rb | 2 +- .../operation/shared/polymorphic_lookup.rb | 2 +- .../operation/shared/polymorphic_result.rb | 2 +- .../shared/read_preference_supported.rb | 2 +- .../operation/shared/response_handling.rb | 2 +- .../operation/shared/result/aggregatable.rb | 2 +- .../shared/result/use_legacy_error_parser.rb | 2 +- .../operation/shared/sessions_supported.rb | 2 +- lib/mongo/operation/shared/specifiable.rb | 2 +- lib/mongo/operation/shared/validatable.rb | 2 +- lib/mongo/operation/shared/write.rb | 2 +- .../shared/write_concern_supported.rb | 2 +- lib/mongo/operation/update.rb | 2 +- lib/mongo/operation/update/bulk_result.rb | 2 +- lib/mongo/operation/update/op_msg.rb | 2 +- lib/mongo/operation/update/result.rb | 2 +- lib/mongo/operation/update_user.rb | 2 +- lib/mongo/operation/update_user/op_msg.rb | 2 +- lib/mongo/operation/users_info.rb | 2 +- lib/mongo/operation/users_info/op_msg.rb | 2 +- lib/mongo/operation/users_info/result.rb | 2 +- lib/mongo/operation/write_command.rb | 2 +- lib/mongo/operation/write_command/op_msg.rb | 2 +- lib/mongo/options.rb | 2 +- lib/mongo/options/mapper.rb | 2 +- lib/mongo/options/redacted.rb | 2 +- lib/mongo/protocol.rb | 2 +- lib/mongo/protocol/bit_vector.rb | 2 +- lib/mongo/protocol/caching_hash.rb | 2 +- lib/mongo/protocol/compressed.rb | 2 +- lib/mongo/protocol/get_more.rb | 2 +- lib/mongo/protocol/kill_cursors.rb | 2 +- lib/mongo/protocol/message.rb | 2 +- lib/mongo/protocol/msg.rb | 2 +- lib/mongo/protocol/query.rb | 2 +- lib/mongo/protocol/registry.rb | 2 +- lib/mongo/protocol/reply.rb | 2 +- lib/mongo/protocol/serializers.rb | 2 +- lib/mongo/query_cache.rb | 2 +- lib/mongo/retryable.rb | 2 +- lib/mongo/retryable/base_worker.rb | 2 +- lib/mongo/retryable/read_worker.rb | 2 +- lib/mongo/retryable/write_worker.rb | 2 +- lib/mongo/semaphore.rb | 2 +- lib/mongo/server.rb | 2 +- lib/mongo/server/app_metadata.rb | 2 +- lib/mongo/server/connection.rb | 2 +- lib/mongo/server/connection_base.rb | 2 +- lib/mongo/server/connection_common.rb | 2 +- lib/mongo/server/connection_pool.rb | 2 +- .../connection_pool/generation_manager.rb | 2 +- lib/mongo/server/connection_pool/populator.rb | 2 +- lib/mongo/server/description.rb | 2 +- lib/mongo/server/description/features.rb | 2 +- lib/mongo/server/description/load_balancer.rb | 2 +- lib/mongo/server/monitor.rb | 2 +- lib/mongo/server/monitor/app_metadata.rb | 2 +- lib/mongo/server/monitor/connection.rb | 2 +- lib/mongo/server/pending_connection.rb | 2 +- lib/mongo/server/push_monitor.rb | 2 +- lib/mongo/server/push_monitor/connection.rb | 2 +- lib/mongo/server/round_trip_time_averager.rb | 2 +- lib/mongo/server_selector.rb | 2 +- lib/mongo/server_selector/base.rb | 2 +- lib/mongo/server_selector/nearest.rb | 2 +- lib/mongo/server_selector/primary.rb | 2 +- .../server_selector/primary_preferred.rb | 2 +- lib/mongo/server_selector/secondary.rb | 2 +- .../server_selector/secondary_preferred.rb | 2 +- lib/mongo/session.rb | 2 +- lib/mongo/session/server_session.rb | 2 +- lib/mongo/session/session_pool.rb | 2 +- lib/mongo/socket.rb | 2 +- lib/mongo/socket/ocsp_cache.rb | 2 +- lib/mongo/socket/ocsp_verifier.rb | 2 +- lib/mongo/socket/ssl.rb | 2 +- lib/mongo/socket/tcp.rb | 2 +- lib/mongo/socket/unix.rb | 2 +- lib/mongo/srv.rb | 2 +- lib/mongo/srv/monitor.rb | 2 +- lib/mongo/srv/resolver.rb | 2 +- lib/mongo/srv/result.rb | 2 +- lib/mongo/timeout.rb | 2 +- lib/mongo/topology_version.rb | 2 +- lib/mongo/uri.rb | 2 +- lib/mongo/uri/options_mapper.rb | 2 +- lib/mongo/uri/srv_protocol.rb | 2 +- lib/mongo/utils.rb | 2 +- lib/mongo/version.rb | 2 +- lib/mongo/write_concern.rb | 2 +- lib/mongo/write_concern/acknowledged.rb | 2 +- lib/mongo/write_concern/base.rb | 2 +- lib/mongo/write_concern/unacknowledged.rb | 2 +- mongo.gemspec | 3 ++ profile/benchmark_21.rb | 2 +- profile/benchmarking.rb | 2 +- profile/benchmarking/helper.rb | 2 +- profile/benchmarking/micro.rb | 2 +- profile/benchmarking/multi_doc.rb | 2 +- profile/benchmarking/parallel.rb | 2 +- profile/benchmarking/single_doc.rb | 2 +- profile/profile.rb | 2 +- spec/atlas/atlas_connectivity_spec.rb | 2 +- spec/atlas/operations_spec.rb | 2 +- spec/integration/auth_spec.rb | 2 +- spec/integration/awaited_ismaster_spec.rb | 2 +- .../aws_auth_credentials_cache_spec.rb | 1 + spec/integration/aws_auth_request_spec.rb | 2 +- .../aws_credentials_retriever_spec.rb | 2 +- spec/integration/aws_lambda_examples_spec.rb | 2 +- spec/integration/bson_symbol_spec.rb | 2 +- spec/integration/bulk_insert_spec.rb | 2 +- .../bulk_write_error_message_spec.rb | 1 + spec/integration/bulk_write_spec.rb | 2 +- .../change_stream_examples_spec.rb | 2 +- spec/integration/change_stream_spec.rb | 2 +- spec/integration/check_clean_slate_spec.rb | 2 +- .../client_authentication_options_spec.rb | 2 +- spec/integration/client_connectivity_spec.rb | 2 +- .../client_construction_aws_auth_spec.rb | 2 +- spec/integration/client_construction_spec.rb | 2 +- .../auto_encryption_bulk_writes_spec.rb | 2 +- ...auto_encryption_command_monitoring_spec.rb | 2 +- .../auto_encryption_mongocryptd_spawn_spec.rb | 2 +- .../auto_encryption_old_wire_version_spec.rb | 2 +- .../auto_encryption_reconnect_spec.rb | 2 +- .../auto_encryption_spec.rb | 2 +- ...tomatic_data_encryption_keys_prose_spec.rb | 2 +- .../bson_size_limit_spec.rb | 2 +- .../bypass_mongocryptd_spawn_spec.rb | 2 +- .../client_close_spec.rb | 2 +- .../client_side_encryption/corpus_spec.rb | 2 +- .../custom_endpoint_spec.rb | 2 +- .../client_side_encryption/data_key_spec.rb | 2 +- .../decryption_events_prose_spec.rb | 2 +- .../explicit_encryption_spec.rb | 2 +- .../explicit_queryable_encryption_spec.rb | 2 +- .../external_key_vault_spec.rb | 2 +- .../kms_tls_options_spec.rb | 2 +- .../client_side_encryption/kms_tls_spec.rb | 2 +- .../mongocryptd_prose_spec.rb | 2 +- .../on_demand_aws_credentials_spec.rb | 2 +- .../on_demand_gcp_credentials_spec.rb | 2 +- .../queryable_encryption_examples_spec.rb | 2 +- .../range_explicit_encryption_prose_spec.rb | 1 + .../rewrap_prose_spec.rb | 2 +- ...nique_index_on_key_alt_names_prose_spec.rb | 2 +- .../client_side_encryption/views_spec.rb | 2 +- spec/integration/client_spec.rb | 2 +- spec/integration/client_update_spec.rb | 2 +- .../collection_indexes_prose_spec.rb | 2 +- spec/integration/command_monitoring_spec.rb | 2 +- spec/integration/command_spec.rb | 2 +- .../connect_single_rs_name_spec.rb | 2 +- .../connection_pool_populator_spec.rb | 2 +- spec/integration/connection_spec.rb | 2 +- spec/integration/crud_spec.rb | 2 +- spec/integration/cursor_pinning_spec.rb | 2 +- spec/integration/cursor_reaping_spec.rb | 2 +- spec/integration/docs_examples_spec.rb | 2 +- spec/integration/error_detection_spec.rb | 2 +- spec/integration/fork_reconnect_spec.rb | 2 +- spec/integration/get_more_spec.rb | 2 +- spec/integration/grid_fs_bucket_spec.rb | 2 +- spec/integration/heartbeat_events_spec.rb | 2 +- spec/integration/map_reduce_spec.rb | 2 +- spec/integration/mmapv1_spec.rb | 2 +- spec/integration/mongos_pinning_spec.rb | 2 +- spec/integration/ocsp_connectivity_spec.rb | 2 +- spec/integration/ocsp_verifier_cache_spec.rb | 2 +- spec/integration/ocsp_verifier_spec.rb | 2 +- .../operation_failure_code_spec.rb | 2 +- .../operation_failure_message_spec.rb | 1 + spec/integration/query_cache_spec.rb | 2 +- .../query_cache_transactions_spec.rb | 2 +- spec/integration/read_concern_spec.rb | 2 +- spec/integration/read_preference_spec.rb | 2 +- spec/integration/reconnect_spec.rb | 2 +- spec/integration/retryable_errors_spec.rb | 2 +- .../retryable_reads_errors_spec.rb | 2 +- .../retryable_writes_36_and_older_spec.rb | 2 +- .../retryable_writes_40_and_newer_spec.rb | 2 +- .../shared/adds_diagnostics.rb | 2 +- .../shared/does_not_support_retries.rb | 2 +- .../shared/only_supports_legacy_retries.rb | 2 +- .../shared/performs_legacy_retries.rb | 2 +- .../shared/performs_modern_retries.rb | 2 +- .../shared/performs_no_retries.rb | 2 +- .../shared/supports_legacy_retries.rb | 2 +- .../shared/supports_modern_retries.rb | 2 +- .../shared/supports_retries.rb | 2 +- .../retryable_writes_errors_spec.rb | 2 +- spec/integration/sdam_error_handling_spec.rb | 2 +- spec/integration/sdam_events_spec.rb | 2 +- spec/integration/sdam_prose_spec.rb | 2 +- spec/integration/secondary_reads_spec.rb | 1 + spec/integration/server_description_spec.rb | 2 +- spec/integration/server_monitor_spec.rb | 2 +- spec/integration/server_selection_spec.rb | 2 +- spec/integration/server_selector_spec.rb | 2 +- spec/integration/server_spec.rb | 2 +- spec/integration/shell_examples_spec.rb | 2 +- spec/integration/size_limit_spec.rb | 2 +- spec/integration/snappy_compression_spec.rb | 2 +- .../snapshot_query_examples_spec.rb | 2 +- spec/integration/srv_monitoring_spec.rb | 2 +- spec/integration/srv_spec.rb | 2 +- spec/integration/ssl_uri_options_spec.rb | 2 +- spec/integration/step_down_spec.rb | 2 +- spec/integration/time_zone_querying_spec.rb | 2 +- spec/integration/transaction_pinning_spec.rb | 2 +- .../transactions_api_examples_spec.rb | 2 +- .../integration/transactions_examples_spec.rb | 2 +- spec/integration/truncated_utf8_spec.rb | 2 +- .../versioned_api_examples_spec.rb | 2 +- spec/integration/x509_auth_spec.rb | 2 +- spec/integration/zlib_compression_spec.rb | 2 +- spec/integration/zstd_compression_spec.rb | 2 +- spec/kerberos/kerberos_spec.rb | 2 +- spec/lite_spec_helper.rb | 2 +- spec/mongo/address/ipv4_spec.rb | 2 +- spec/mongo/address/ipv6_spec.rb | 2 +- spec/mongo/address/unix_spec.rb | 2 +- spec/mongo/address/validator_spec.rb | 2 +- spec/mongo/address_spec.rb | 2 +- spec/mongo/auth/aws/credential_cache_spec.rb | 1 + .../auth/aws/credentials_retriever_spec.rb | 1 + spec/mongo/auth/aws/credentials_spec.rb | 1 + spec/mongo/auth/aws/request_region_spec.rb | 2 +- spec/mongo/auth/aws/request_spec.rb | 2 +- spec/mongo/auth/cr_spec.rb | 2 +- spec/mongo/auth/gssapi/conversation_spec.rb | 2 +- spec/mongo/auth/invalid_mechanism_spec.rb | 2 +- spec/mongo/auth/ldap/conversation_spec.rb | 2 +- spec/mongo/auth/ldap_spec.rb | 2 +- spec/mongo/auth/scram/conversation_spec.rb | 2 +- spec/mongo/auth/scram256/conversation_spec.rb | 2 +- spec/mongo/auth/scram_negotiation_spec.rb | 2 +- spec/mongo/auth/scram_spec.rb | 2 +- .../auth/stringprep/profiles/sasl_spec.rb | 2 +- spec/mongo/auth/stringprep_spec.rb | 2 +- spec/mongo/auth/user/view_spec.rb | 2 +- spec/mongo/auth/user_spec.rb | 2 +- spec/mongo/auth/x509/conversation_spec.rb | 2 +- spec/mongo/auth/x509_spec.rb | 2 +- spec/mongo/auth_spec.rb | 2 +- spec/mongo/bson_spec.rb | 2 +- .../mongo/bulk_write/ordered_combiner_spec.rb | 2 +- spec/mongo/bulk_write/result_spec.rb | 2 +- .../bulk_write/unordered_combiner_spec.rb | 2 +- spec/mongo/bulk_write_spec.rb | 2 +- spec/mongo/caching_cursor_spec.rb | 2 +- spec/mongo/client_construction_spec.rb | 2 +- spec/mongo/client_encryption_spec.rb | 2 +- spec/mongo/client_spec.rb | 2 +- spec/mongo/cluster/cursor_reaper_spec.rb | 2 +- spec/mongo/cluster/periodic_executor_spec.rb | 2 +- spec/mongo/cluster/socket_reaper_spec.rb | 2 +- .../cluster/topology/replica_set_spec.rb | 2 +- spec/mongo/cluster/topology/sharded_spec.rb | 2 +- spec/mongo/cluster/topology/single_spec.rb | 2 +- spec/mongo/cluster/topology/unknown_spec.rb | 2 +- spec/mongo/cluster/topology_spec.rb | 2 +- spec/mongo/cluster_spec.rb | 2 +- spec/mongo/cluster_time_spec.rb | 2 +- .../mongo/collection/view/aggregation_spec.rb | 2 +- .../view/builder/find_command_spec.rb | 2 +- .../collection/view/builder/op_query_spec.rb | 2 +- .../view/change_stream_resume_spec.rb | 2 +- .../collection/view/change_stream_spec.rb | 2 +- .../mongo/collection/view/explainable_spec.rb | 2 +- spec/mongo/collection/view/immutable_spec.rb | 2 +- spec/mongo/collection/view/iterable_spec.rb | 2 +- spec/mongo/collection/view/map_reduce_spec.rb | 2 +- spec/mongo/collection/view/readable_spec.rb | 2 +- spec/mongo/collection/view/writable_spec.rb | 2 +- spec/mongo/collection/view_spec.rb | 2 +- spec/mongo/collection_crud_spec.rb | 2 +- spec/mongo/collection_ddl_spec.rb | 2 +- spec/mongo/collection_spec.rb | 2 +- spec/mongo/condition_variable_spec.rb | 2 +- spec/mongo/config/options_spec.rb | 1 + spec/mongo/config_spec.rb | 3 +- .../crypt/auto_decryption_context_spec.rb | 2 +- spec/mongo/crypt/auto_encrypter_spec.rb | 2 +- .../crypt/auto_encryption_context_spec.rb | 2 +- spec/mongo/crypt/binary_spec.rb | 2 +- spec/mongo/crypt/binding/binary_spec.rb | 2 +- spec/mongo/crypt/binding/context_spec.rb | 2 +- spec/mongo/crypt/binding/helpers_spec.rb | 2 +- spec/mongo/crypt/binding/mongocrypt_spec.rb | 2 +- spec/mongo/crypt/binding/status_spec.rb | 2 +- spec/mongo/crypt/binding/version_spec.rb | 2 +- spec/mongo/crypt/binding_unloaded_spec.rb | 2 +- spec/mongo/crypt/data_key_context_spec.rb | 2 +- spec/mongo/crypt/encryption_io_spec.rb | 2 +- .../crypt/explicit_decryption_context_spec.rb | 2 +- .../crypt/explicit_encryption_context_spec.rb | 2 +- spec/mongo/crypt/handle_spec.rb | 2 +- .../crypt/helpers/mongo_crypt_spec_helper.rb | 2 +- spec/mongo/crypt/hooks_spec.rb | 4 +- spec/mongo/crypt/kms/credentials_spec.rb | 2 +- spec/mongo/crypt/kms_spec.rb | 2 +- spec/mongo/crypt/status_spec.rb | 2 +- .../cursor/builder/get_more_command_spec.rb | 2 +- spec/mongo/cursor/builder/op_get_more_spec.rb | 2 +- spec/mongo/cursor_spec.rb | 2 +- spec/mongo/database_spec.rb | 2 +- spec/mongo/distinguishing_semaphore_spec.rb | 2 +- spec/mongo/error/bulk_write_error_spec.rb | 2 +- spec/mongo/error/crypt_error_spec.rb | 2 +- spec/mongo/error/max_bson_size_spec.rb | 2 +- spec/mongo/error/no_server_available_spec.rb | 2 +- spec/mongo/error/notable_spec.rb | 2 +- .../error/operation_failure_heavy_spec.rb | 2 +- spec/mongo/error/operation_failure_spec.rb | 2 +- spec/mongo/error/parser_spec.rb | 2 +- spec/mongo/error/unsupported_option_spec.rb | 2 +- spec/mongo/event/publisher_spec.rb | 2 +- spec/mongo/event/subscriber_spec.rb | 2 +- spec/mongo/grid/file/chunk_spec.rb | 2 +- spec/mongo/grid/file/info_spec.rb | 2 +- spec/mongo/grid/file_spec.rb | 2 +- spec/mongo/grid/fs_bucket_spec.rb | 2 +- spec/mongo/grid/stream/read_spec.rb | 2 +- spec/mongo/grid/stream/write_spec.rb | 2 +- spec/mongo/grid/stream_spec.rb | 2 +- spec/mongo/id_spec.rb | 4 +- spec/mongo/index/view_spec.rb | 2 +- spec/mongo/lint_spec.rb | 2 +- spec/mongo/logger_spec.rb | 2 +- .../monitoring/command_log_subscriber_spec.rb | 2 +- .../cmap/connection_check_out_failed_spec.rb | 4 +- .../cmap/connection_check_out_started_spec.rb | 4 +- .../event/cmap/connection_checked_in_spec.rb | 4 +- .../event/cmap/connection_checked_out_spec.rb | 4 +- .../event/cmap/connection_closed_spec.rb | 4 +- .../event/cmap/connection_created_spec.rb | 4 +- .../event/cmap/connection_ready_spec.rb | 4 +- .../event/cmap/pool_cleared_spec.rb | 4 +- .../monitoring/event/cmap/pool_closed_spec.rb | 4 +- .../event/cmap/pool_created_spec.rb | 4 +- .../monitoring/event/command_failed_spec.rb | 2 +- .../monitoring/event/command_started_spec.rb | 2 +- .../event/command_succeeded_spec.rb | 2 +- spec/mongo/monitoring/event/secure_spec.rb | 2 +- .../monitoring/event/server_closed_spec.rb | 2 +- .../event/server_description_changed_spec.rb | 2 +- .../event/server_heartbeat_failed_spec.rb | 2 +- .../event/server_heartbeat_started_spec.rb | 2 +- .../event/server_heartbeat_succeeded_spec.rb | 2 +- .../monitoring/event/server_opening_spec.rb | 2 +- .../monitoring/event/topology_changed_spec.rb | 2 +- .../monitoring/event/topology_closed_spec.rb | 2 +- .../monitoring/event/topology_opening_spec.rb | 2 +- spec/mongo/monitoring_spec.rb | 2 +- spec/mongo/operation/aggregate/result_spec.rb | 2 +- spec/mongo/operation/aggregate_spec.rb | 2 +- spec/mongo/operation/collections_info_spec.rb | 2 +- spec/mongo/operation/command_spec.rb | 2 +- spec/mongo/operation/create/op_msg_spec.rb | 2 +- spec/mongo/operation/create_index_spec.rb | 2 +- spec/mongo/operation/create_user_spec.rb | 2 +- spec/mongo/operation/delete/bulk_spec.rb | 2 +- spec/mongo/operation/delete/op_msg_spec.rb | 2 +- spec/mongo/operation/delete_spec.rb | 2 +- spec/mongo/operation/drop_index_spec.rb | 2 +- .../operation/find/builder/flags_spec.rb | 2 +- .../operation/find/builder/modifiers_spec.rb | 2 +- spec/mongo/operation/indexes_spec.rb | 2 +- spec/mongo/operation/insert/bulk_spec.rb | 2 +- spec/mongo/operation/insert/op_msg_spec.rb | 2 +- spec/mongo/operation/insert_spec.rb | 2 +- spec/mongo/operation/limited_spec.rb | 2 +- spec/mongo/operation/map_reduce_spec.rb | 2 +- .../operation/read_preference_legacy_spec.rb | 2 +- .../operation/read_preference_op_msg_spec.rb | 2 +- spec/mongo/operation/remove_user_spec.rb | 2 +- spec/mongo/operation/result_spec.rb | 2 +- spec/mongo/operation/specifiable_spec.rb | 2 +- spec/mongo/operation/update/bulk_spec.rb | 2 +- spec/mongo/operation/update/op_msg_spec.rb | 2 +- spec/mongo/operation/update_spec.rb | 2 +- spec/mongo/operation/update_user_spec.rb | 2 +- spec/mongo/options/redacted_spec.rb | 4 +- spec/mongo/protocol/caching_hash_spec.rb | 2 +- spec/mongo/protocol/compressed_spec.rb | 2 +- spec/mongo/protocol/get_more_spec.rb | 2 +- spec/mongo/protocol/kill_cursors_spec.rb | 2 +- spec/mongo/protocol/msg_spec.rb | 2 +- spec/mongo/protocol/query_spec.rb | 2 +- spec/mongo/protocol/registry_spec.rb | 2 +- spec/mongo/protocol/reply_spec.rb | 2 +- spec/mongo/query_cache_middleware_spec.rb | 2 +- spec/mongo/query_cache_spec.rb | 2 +- spec/mongo/retryable_spec.rb | 2 +- spec/mongo/semaphore_spec.rb | 2 +- spec/mongo/server/app_metadata_spec.rb | 2 +- spec/mongo/server/connection_auth_spec.rb | 2 +- spec/mongo/server/connection_common_spec.rb | 2 +- .../server/connection_pool/populator_spec.rb | 2 +- spec/mongo/server/connection_pool_spec.rb | 2 +- spec/mongo/server/connection_spec.rb | 2 +- .../mongo/server/description/features_spec.rb | 2 +- .../server/description_query_methods_spec.rb | 2 +- spec/mongo/server/description_spec.rb | 2 +- .../mongo/server/monitor/app_metadata_spec.rb | 2 +- spec/mongo/server/monitor/connection_spec.rb | 2 +- spec/mongo/server/monitor_spec.rb | 2 +- spec/mongo/server/push_monitor_spec.rb | 2 +- .../server/round_trip_time_averager_spec.rb | 2 +- spec/mongo/server_selector/nearest_spec.rb | 2 +- .../server_selector/primary_preferred_spec.rb | 2 +- spec/mongo/server_selector/primary_spec.rb | 2 +- .../secondary_preferred_spec.rb | 2 +- spec/mongo/server_selector/secondary_spec.rb | 2 +- spec/mongo/server_selector_spec.rb | 2 +- spec/mongo/server_spec.rb | 2 +- spec/mongo/session/server_session_spec.rb | 2 +- spec/mongo/session/session_pool_spec.rb | 2 +- spec/mongo/session_spec.rb | 2 +- spec/mongo/session_transaction_spec.rb | 2 +- spec/mongo/socket/ssl_spec.rb | 2 +- spec/mongo/socket/tcp_spec.rb | 2 +- spec/mongo/socket/unix_spec.rb | 2 +- spec/mongo/socket_spec.rb | 2 +- spec/mongo/srv/monitor_spec.rb | 2 +- spec/mongo/srv/result_spec.rb | 2 +- spec/mongo/timeout_spec.rb | 2 +- spec/mongo/tls_context_hooks_spec.rb | 2 +- spec/mongo/uri/options_mapper_spec.rb | 2 +- spec/mongo/uri/srv_protocol_spec.rb | 2 +- spec/mongo/uri_option_parsing_spec.rb | 2 +- spec/mongo/uri_spec.rb | 2 +- spec/mongo/utils_spec.rb | 2 +- spec/mongo/write_concern/acknowledged_spec.rb | 2 +- .../write_concern/unacknowledged_spec.rb | 2 +- spec/mongo/write_concern_spec.rb | 2 +- spec/runners/auth.rb | 2 +- spec/runners/change_streams/outcome.rb | 2 +- spec/runners/change_streams/spec.rb | 2 +- spec/runners/change_streams/test.rb | 2 +- spec/runners/cmap.rb | 2 +- spec/runners/cmap/verifier.rb | 2 +- spec/runners/command_monitoring.rb | 2 +- spec/runners/connection_string.rb | 2 +- spec/runners/crud.rb | 2 +- spec/runners/crud/context.rb | 2 +- spec/runners/crud/operation.rb | 2 +- spec/runners/crud/outcome.rb | 2 +- spec/runners/crud/requirement.rb | 2 +- spec/runners/crud/spec.rb | 2 +- spec/runners/crud/test.rb | 2 +- spec/runners/crud/test_base.rb | 2 +- spec/runners/crud/verifier.rb | 2 +- spec/runners/gridfs.rb | 2 +- spec/runners/read_write_concern_document.rb | 2 +- spec/runners/sdam.rb | 2 +- spec/runners/sdam/verifier.rb | 2 +- spec/runners/server_selection.rb | 2 +- spec/runners/server_selection_rtt.rb | 2 +- spec/runners/transactions.rb | 2 +- spec/runners/transactions/operation.rb | 2 +- spec/runners/transactions/spec.rb | 2 +- spec/runners/transactions/test.rb | 2 +- spec/runners/unified.rb | 2 +- spec/runners/unified/assertions.rb | 2 +- .../unified/change_stream_operations.rb | 2 +- .../client_side_encryption_operations.rb | 2 +- spec/runners/unified/crud_operations.rb | 2 +- spec/runners/unified/ddl_operations.rb | 2 +- spec/runners/unified/entity_map.rb | 2 +- spec/runners/unified/error.rb | 2 +- spec/runners/unified/event_subscriber.rb | 2 +- spec/runners/unified/exceptions.rb | 2 +- spec/runners/unified/grid_fs_operations.rb | 2 +- spec/runners/unified/support_operations.rb | 2 +- spec/runners/unified/test.rb | 2 +- spec/runners/unified/test_group.rb | 2 +- spec/runners/unified/thread_operations.rb | 2 +- spec/solo/clean_exit_spec.rb | 2 +- spec/spec_helper.rb | 2 +- spec/spec_tests/auth_spec.rb | 2 +- .../spec_tests/change_streams_unified_spec.rb | 2 +- .../spec_tests/client_side_encryption_spec.rb | 2 +- .../client_side_encryption_unified_spec.rb | 2 +- spec/spec_tests/cmap_spec.rb | 2 +- spec/spec_tests/collection_management_spec.rb | 2 +- .../command_monitoring_unified_spec.rb | 2 +- spec/spec_tests/connection_string_spec.rb | 2 +- spec/spec_tests/crud_spec.rb | 2 +- spec/spec_tests/crud_unified_spec.rb | 2 +- spec/spec_tests/gridfs_spec.rb | 2 +- spec/spec_tests/gridfs_unified_spec.rb | 2 +- spec/spec_tests/load_balancers_spec.rb | 2 +- spec/spec_tests/max_staleness_spec.rb | 2 +- ...ad_write_concern_connection_string_spec.rb | 2 +- .../read_write_concern_document_spec.rb | 2 +- .../read_write_concern_operaton_spec.rb | 2 +- spec/spec_tests/retryable_reads_spec.rb | 2 +- .../retryable_reads_unified_spec.rb | 2 +- spec/spec_tests/retryable_writes_spec.rb | 2 +- .../retryable_writes_unified_spec.rb | 2 +- spec/spec_tests/sdam_monitoring_spec.rb | 2 +- spec/spec_tests/sdam_spec.rb | 2 +- spec/spec_tests/sdam_unified_spec.rb | 2 +- spec/spec_tests/seed_list_discovery_spec.rb | 2 +- spec/spec_tests/server_selection_rtt_spec.rb | 2 +- spec/spec_tests/server_selection_spec.rb | 2 +- spec/spec_tests/sessions_unified_spec.rb | 2 +- spec/spec_tests/transactions_api_spec.rb | 2 +- spec/spec_tests/transactions_spec.rb | 2 +- spec/spec_tests/transactions_unified_spec.rb | 2 +- spec/spec_tests/unified_spec.rb | 2 +- spec/spec_tests/uri_options_spec.rb | 2 +- spec/spec_tests/versioned_api_spec.rb | 2 +- spec/stress/cleanup_spec.rb | 2 +- spec/stress/connection_pool_stress_spec.rb | 2 +- spec/stress/connection_pool_timing_spec.rb | 2 +- spec/stress/fork_reconnect_stress_spec.rb | 2 +- spec/stress/push_monitor_close_spec.rb | 2 +- spec/support/authorization.rb | 2 +- spec/support/aws_utils.rb | 2 +- spec/support/aws_utils/base.rb | 2 +- spec/support/aws_utils/inspector.rb | 2 +- spec/support/aws_utils/orchestrator.rb | 2 +- spec/support/aws_utils/provisioner.rb | 2 +- spec/support/background_thread_registry.rb | 2 +- spec/support/certificates/retrieve-atlas-cert | 2 + spec/support/client_registry.rb | 2 +- spec/support/client_registry_macros.rb | 2 +- spec/support/cluster_tools.rb | 2 +- spec/support/common_shortcuts.rb | 2 +- spec/support/constraints.rb | 2 +- spec/support/crypt.rb | 2 +- spec/support/dns.rb | 2 +- spec/support/json_ext_formatter.rb | 2 +- spec/support/keyword_struct.rb | 2 +- spec/support/local_resource_registry.rb | 2 +- spec/support/macros.rb | 1 + spec/support/matchers.rb | 2 +- spec/support/mongos_macros.rb | 1 + spec/support/monitoring_ext.rb | 2 +- spec/support/primary_socket.rb | 2 +- spec/support/sdam_formatter_integration.rb | 2 +- spec/support/shared/app_metadata.rb | 2 +- spec/support/shared/auth_context.rb | 1 + spec/support/shared/protocol.rb | 2 +- spec/support/shared/scram_conversation.rb | 2 +- spec/support/shared/server_selector.rb | 2 +- spec/support/shared/session.rb | 2 +- spec/support/spec_config.rb | 2 +- spec/support/spec_setup.rb | 2 +- spec/support/using_hash.rb | 2 +- spec/support/utils.rb | 2 +- 929 files changed, 989 insertions(+), 925 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index abe7183346..9083d289b6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,29 +1,49 @@ +require: + - rubocop-performance + - rubocop-rake + - rubocop-rspec + AllCops: TargetRubyVersion: 2.5 - NewCops: disable - Include: - - 'lib/**/*' + NewCops: enable + Exclude: + - 'spec/shared/**/*' + - 'vendor/**/*' Bundler: - Enabled: false + Enabled: true Gemspec: - Enabled: false + Enabled: true Layout: - Enabled: false + Enabled: true Lint: - Enabled: false + Enabled: true Metrics: - Enabled: false + Enabled: true Naming: - Enabled: false + Enabled: true Security: Enabled: true Style: + Enabled: true + +# -------------------------------------- +# Cops below this line set intentionally +# -------------------------------------- + +Bundler/OrderedGems: + Enabled: false + +Gemspec/OrderedDependencies: Enabled: false + +Style/Documentation: + Exclude: + - 'spec/**/*' diff --git a/Gemfile b/Gemfile index e7110e805d..63293fa36e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,6 @@ +# frozen_string_literal: true +# rubocop:todo all + source 'https://rubygems.org' gemspec diff --git a/Rakefile b/Rakefile index 1ac56caeda..f64a790c12 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,5 @@ -# -*- mode: ruby -*- +# frozen_string_literal: true +# rubocop:todo all require 'bundler' require 'bundler/gem_tasks' diff --git a/bin/mongo_console b/bin/mongo_console index ea2aa22840..ffefb398b7 100755 --- a/bin/mongo_console +++ b/bin/mongo_console @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true +# rubocop:todo all $LOAD_PATH[0, 0] = File.join(File.dirname(__FILE__), '..', 'lib') diff --git a/examples/aggregate.rb b/examples/aggregate.rb index f571ce52e4..471a922720 100644 --- a/examples/aggregate.rb +++ b/examples/aggregate.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Group documents by field and calculate count. diff --git a/examples/create.rb b/examples/create.rb index 2b8c0572f7..c4347b9eb5 100644 --- a/examples/create.rb +++ b/examples/create.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Insert a document diff --git a/examples/delete.rb b/examples/delete.rb index da6d4da082..ac533245cb 100644 --- a/examples/delete.rb +++ b/examples/delete.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Delete all documents matching a condition diff --git a/examples/index.rb b/examples/index.rb index 3ec2b2e831..26ca9cb92d 100644 --- a/examples/index.rb +++ b/examples/index.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Create a single field index diff --git a/examples/query.rb b/examples/query.rb index f63aeed237..c0e10fca11 100644 --- a/examples/query.rb +++ b/examples/query.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Query for all documents in a collection diff --git a/examples/update.rb b/examples/update.rb index c556c33849..0a5e61234b 100644 --- a/examples/update.rb +++ b/examples/update.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Update top-level fields in a single document diff --git a/gemfiles/bson_4-stable.gemfile b/gemfiles/bson_4-stable.gemfile index b200750c4b..c88418332c 100644 --- a/gemfiles/bson_4-stable.gemfile +++ b/gemfiles/bson_4-stable.gemfile @@ -1,3 +1,4 @@ +# rubocop:todo all source "https://rubygems.org" gemspec path: '..' diff --git a/gemfiles/bson_master.gemfile b/gemfiles/bson_master.gemfile index d0ffdea227..bdafb8123d 100644 --- a/gemfiles/bson_master.gemfile +++ b/gemfiles/bson_master.gemfile @@ -1,3 +1,4 @@ +# rubocop:todo all source "https://rubygems.org" gemspec path: '..' diff --git a/gemfiles/bson_min.gemfile b/gemfiles/bson_min.gemfile index d5a9023aac..c58de149a2 100644 --- a/gemfiles/bson_min.gemfile +++ b/gemfiles/bson_min.gemfile @@ -1,3 +1,4 @@ +# rubocop:todo all source "https://rubygems.org" gemspec path: '..' diff --git a/gemfiles/mongo_kerberos.gemfile b/gemfiles/mongo_kerberos.gemfile index c79f8fc99a..07772695f9 100644 --- a/gemfiles/mongo_kerberos.gemfile +++ b/gemfiles/mongo_kerberos.gemfile @@ -1,3 +1,4 @@ +# rubocop:todo all source "https://rubygems.org" gemspec path: '..' diff --git a/gemfiles/snappy_compression.gemfile b/gemfiles/snappy_compression.gemfile index 0c8f3cca67..2936a6de73 100644 --- a/gemfiles/snappy_compression.gemfile +++ b/gemfiles/snappy_compression.gemfile @@ -1,3 +1,4 @@ +# rubocop:todo all source "https://rubygems.org" gemspec path: '..' diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index 095ffeb048..538e7fc2e4 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all def standard_dependencies gem 'yard' @@ -29,8 +29,14 @@ def standard_dependencies gem 'yajl-ruby', platforms: :mri, require: false gem 'celluloid', platforms: :mri, require: false - # for static analysis - gem 'rubocop', '~> 1.27.0' + # for static analysis -- ignore ruby < 2.6 because of rubocop + # version incompatibilities + if RUBY_VERSION > "2.5.99" + gem 'rubocop', '~> 1.45.1' + gem 'rubocop-performance', '~> 1.16.0' + gem 'rubocop-rake', '~> 0.6.0' + gem 'rubocop-rspec', '~> 2.18.1' + end platform :mri do # Debugger for VSCode. @@ -60,7 +66,8 @@ def standard_dependencies gem 'ruby-prof', platforms: :mri gem 'erubi' gem 'tilt' - gem 'solargraph' + # solargraph depends on rbs, which won't build on jruby for some reason + gem 'solargraph', platforms: :mri end if ENV['FLE'] == 'helper' diff --git a/gemfiles/zstd_compression.gemfile b/gemfiles/zstd_compression.gemfile index 740a648c5f..0c9c7d7a0d 100644 --- a/gemfiles/zstd_compression.gemfile +++ b/gemfiles/zstd_compression.gemfile @@ -1,3 +1,4 @@ +# rubocop:todo all source "https://rubygems.org" gemspec path: '..' diff --git a/lib/mongo.rb b/lib/mongo.rb index b9121a7230..0f24833f06 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/active_support.rb b/lib/mongo/active_support.rb index 40c02459d6..5c3f260dec 100644 --- a/lib/mongo/active_support.rb +++ b/lib/mongo/active_support.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/address.rb b/lib/mongo/address.rb index 536eb7507d..90b82dec11 100644 --- a/lib/mongo/address.rb +++ b/lib/mongo/address.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/address/ipv4.rb b/lib/mongo/address/ipv4.rb index e495f173cd..e999fa333d 100644 --- a/lib/mongo/address/ipv4.rb +++ b/lib/mongo/address/ipv4.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/address/ipv6.rb b/lib/mongo/address/ipv6.rb index bd1732caf1..d2766d8f0d 100644 --- a/lib/mongo/address/ipv6.rb +++ b/lib/mongo/address/ipv6.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/address/unix.rb b/lib/mongo/address/unix.rb index 2bf3a65b6e..2506950822 100644 --- a/lib/mongo/address/unix.rb +++ b/lib/mongo/address/unix.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/address/validator.rb b/lib/mongo/address/validator.rb index 8ba840cb16..37f8f0e104 100644 --- a/lib/mongo/address/validator.rb +++ b/lib/mongo/address/validator.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/auth.rb b/lib/mongo/auth.rb index ccdc626b5c..65d77cd89f 100644 --- a/lib/mongo/auth.rb +++ b/lib/mongo/auth.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/aws.rb b/lib/mongo/auth/aws.rb index 4589c029ea..b7d2781799 100644 --- a/lib/mongo/auth/aws.rb +++ b/lib/mongo/auth/aws.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/aws/conversation.rb b/lib/mongo/auth/aws/conversation.rb index bebf55cf5a..24695c37d9 100644 --- a/lib/mongo/auth/aws/conversation.rb +++ b/lib/mongo/auth/aws/conversation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/aws/credentials.rb b/lib/mongo/auth/aws/credentials.rb index a661bb5e09..e4b94a1f44 100644 --- a/lib/mongo/auth/aws/credentials.rb +++ b/lib/mongo/auth/aws/credentials.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all # Copyright (C) 2023-present MongoDB Inc. # diff --git a/lib/mongo/auth/aws/credentials_cache.rb b/lib/mongo/auth/aws/credentials_cache.rb index fa52144380..75ccae794e 100644 --- a/lib/mongo/auth/aws/credentials_cache.rb +++ b/lib/mongo/auth/aws/credentials_cache.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all # Copyright (C) 2023-present MongoDB Inc. # diff --git a/lib/mongo/auth/aws/credentials_retriever.rb b/lib/mongo/auth/aws/credentials_retriever.rb index 1050d99056..fdbbb0e015 100644 --- a/lib/mongo/auth/aws/credentials_retriever.rb +++ b/lib/mongo/auth/aws/credentials_retriever.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/aws/request.rb b/lib/mongo/auth/aws/request.rb index 072ac9c07d..4694816f9b 100644 --- a/lib/mongo/auth/aws/request.rb +++ b/lib/mongo/auth/aws/request.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/base.rb b/lib/mongo/auth/base.rb index 954352a9dd..efcae3c151 100644 --- a/lib/mongo/auth/base.rb +++ b/lib/mongo/auth/base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/conversation_base.rb b/lib/mongo/auth/conversation_base.rb index 79e9054388..ab24d5df76 100644 --- a/lib/mongo/auth/conversation_base.rb +++ b/lib/mongo/auth/conversation_base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/cr.rb b/lib/mongo/auth/cr.rb index ca560b79f0..4f4c8593a3 100644 --- a/lib/mongo/auth/cr.rb +++ b/lib/mongo/auth/cr.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/cr/conversation.rb b/lib/mongo/auth/cr/conversation.rb index 99a12e06a3..74e0b605d0 100644 --- a/lib/mongo/auth/cr/conversation.rb +++ b/lib/mongo/auth/cr/conversation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/credential_cache.rb b/lib/mongo/auth/credential_cache.rb index 403fe9c598..16d7962775 100644 --- a/lib/mongo/auth/credential_cache.rb +++ b/lib/mongo/auth/credential_cache.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/gssapi.rb b/lib/mongo/auth/gssapi.rb index 4589b79d33..4ae66aef59 100644 --- a/lib/mongo/auth/gssapi.rb +++ b/lib/mongo/auth/gssapi.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB, Inc. # diff --git a/lib/mongo/auth/gssapi/conversation.rb b/lib/mongo/auth/gssapi/conversation.rb index a7ad7e63b1..85488732f6 100644 --- a/lib/mongo/auth/gssapi/conversation.rb +++ b/lib/mongo/auth/gssapi/conversation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/ldap.rb b/lib/mongo/auth/ldap.rb index acd3cd573d..bc275a508c 100644 --- a/lib/mongo/auth/ldap.rb +++ b/lib/mongo/auth/ldap.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/ldap/conversation.rb b/lib/mongo/auth/ldap/conversation.rb index f21cf7ce8d..7371d2217b 100644 --- a/lib/mongo/auth/ldap/conversation.rb +++ b/lib/mongo/auth/ldap/conversation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/roles.rb b/lib/mongo/auth/roles.rb index 1713cd5aa0..a8186549fb 100644 --- a/lib/mongo/auth/roles.rb +++ b/lib/mongo/auth/roles.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/sasl_conversation_base.rb b/lib/mongo/auth/sasl_conversation_base.rb index 63af44fd43..41c077be90 100644 --- a/lib/mongo/auth/sasl_conversation_base.rb +++ b/lib/mongo/auth/sasl_conversation_base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/scram.rb b/lib/mongo/auth/scram.rb index 6da85c128f..0a81f8d6ee 100644 --- a/lib/mongo/auth/scram.rb +++ b/lib/mongo/auth/scram.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/scram/conversation.rb b/lib/mongo/auth/scram/conversation.rb index bc974095f3..00ef4849e2 100644 --- a/lib/mongo/auth/scram/conversation.rb +++ b/lib/mongo/auth/scram/conversation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/scram256.rb b/lib/mongo/auth/scram256.rb index e0839555d5..9166a33f7f 100644 --- a/lib/mongo/auth/scram256.rb +++ b/lib/mongo/auth/scram256.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/scram256/conversation.rb b/lib/mongo/auth/scram256/conversation.rb index 0b7ac79fad..8f3647dad4 100644 --- a/lib/mongo/auth/scram256/conversation.rb +++ b/lib/mongo/auth/scram256/conversation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/scram_conversation_base.rb b/lib/mongo/auth/scram_conversation_base.rb index ff47d77360..d47ba49c7b 100644 --- a/lib/mongo/auth/scram_conversation_base.rb +++ b/lib/mongo/auth/scram_conversation_base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/auth/stringprep.rb b/lib/mongo/auth/stringprep.rb index 8d9e2f24de..908b654cf9 100644 --- a/lib/mongo/auth/stringprep.rb +++ b/lib/mongo/auth/stringprep.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/stringprep/profiles/sasl.rb b/lib/mongo/auth/stringprep/profiles/sasl.rb index 72fb060011..3c6f3dde18 100644 --- a/lib/mongo/auth/stringprep/profiles/sasl.rb +++ b/lib/mongo/auth/stringprep/profiles/sasl.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/stringprep/tables.rb b/lib/mongo/auth/stringprep/tables.rb index 2123e3811a..173072da08 100644 --- a/lib/mongo/auth/stringprep/tables.rb +++ b/lib/mongo/auth/stringprep/tables.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb b/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb index 78d3ff5540..65d5dd4b9f 100644 --- a/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +++ b/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright Ayumu Nojima (野島 歩) and Martin J. Dürst (duerst@it.aoyama.ac.jp) diff --git a/lib/mongo/auth/stringprep/unicode_normalize/tables.rb b/lib/mongo/auth/stringprep/unicode_normalize/tables.rb index 8e79f9f705..7e111dff8b 100644 --- a/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +++ b/lib/mongo/auth/stringprep/unicode_normalize/tables.rb @@ -1,5 +1,6 @@ -# frozen_string_literal: true # encoding: us-ascii +# frozen_string_literal: true +# rubocop:todo all # automatically generated by template/unicode_norm_gen.tmpl diff --git a/lib/mongo/auth/user.rb b/lib/mongo/auth/user.rb index 0198a2cabb..3c0df251f2 100644 --- a/lib/mongo/auth/user.rb +++ b/lib/mongo/auth/user.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/user/view.rb b/lib/mongo/auth/user/view.rb index 1612c352be..552a074472 100644 --- a/lib/mongo/auth/user/view.rb +++ b/lib/mongo/auth/user/view.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/x509.rb b/lib/mongo/auth/x509.rb index e3c7d0d485..2880d42fc9 100644 --- a/lib/mongo/auth/x509.rb +++ b/lib/mongo/auth/x509.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/auth/x509/conversation.rb b/lib/mongo/auth/x509/conversation.rb index 936604166b..b2ec2c885a 100644 --- a/lib/mongo/auth/x509/conversation.rb +++ b/lib/mongo/auth/x509/conversation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/background_thread.rb b/lib/mongo/background_thread.rb index b32850ee77..a646e882a3 100644 --- a/lib/mongo/background_thread.rb +++ b/lib/mongo/background_thread.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/bson.rb b/lib/mongo/bson.rb index 0b3bd0819a..9ef96a91b9 100644 --- a/lib/mongo/bson.rb +++ b/lib/mongo/bson.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/bulk_write.rb b/lib/mongo/bulk_write.rb index ad05ef8f5f..336a98396e 100644 --- a/lib/mongo/bulk_write.rb +++ b/lib/mongo/bulk_write.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/bulk_write/combineable.rb b/lib/mongo/bulk_write/combineable.rb index 05b054630d..e0a687f7e0 100644 --- a/lib/mongo/bulk_write/combineable.rb +++ b/lib/mongo/bulk_write/combineable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/bulk_write/ordered_combiner.rb b/lib/mongo/bulk_write/ordered_combiner.rb index bb9a18a71f..c25aae328c 100644 --- a/lib/mongo/bulk_write/ordered_combiner.rb +++ b/lib/mongo/bulk_write/ordered_combiner.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/bulk_write/result.rb b/lib/mongo/bulk_write/result.rb index a3ffb2e6f7..f34e220d05 100644 --- a/lib/mongo/bulk_write/result.rb +++ b/lib/mongo/bulk_write/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/bulk_write/result_combiner.rb b/lib/mongo/bulk_write/result_combiner.rb index e2d6e5c1e9..6127a218d0 100644 --- a/lib/mongo/bulk_write/result_combiner.rb +++ b/lib/mongo/bulk_write/result_combiner.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/bulk_write/transformable.rb b/lib/mongo/bulk_write/transformable.rb index 4efa012268..9643afb63a 100644 --- a/lib/mongo/bulk_write/transformable.rb +++ b/lib/mongo/bulk_write/transformable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/bulk_write/unordered_combiner.rb b/lib/mongo/bulk_write/unordered_combiner.rb index a8ebeeac0e..163df6d970 100644 --- a/lib/mongo/bulk_write/unordered_combiner.rb +++ b/lib/mongo/bulk_write/unordered_combiner.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/bulk_write/validatable.rb b/lib/mongo/bulk_write/validatable.rb index 6d49cc8820..8c2dedcc1d 100644 --- a/lib/mongo/bulk_write/validatable.rb +++ b/lib/mongo/bulk_write/validatable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/caching_cursor.rb b/lib/mongo/caching_cursor.rb index 744f5b9966..acd913b56d 100644 --- a/lib/mongo/caching_cursor.rb +++ b/lib/mongo/caching_cursor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index 92bde662e6..42de028ca5 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/client_encryption.rb b/lib/mongo/client_encryption.rb index 78dc9cbb73..e85e3ccb32 100644 --- a/lib/mongo/client_encryption.rb +++ b/lib/mongo/client_encryption.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster.rb b/lib/mongo/cluster.rb index 3769254263..b5981c7471 100644 --- a/lib/mongo/cluster.rb +++ b/lib/mongo/cluster.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/periodic_executor.rb b/lib/mongo/cluster/periodic_executor.rb index e4b2df6932..3d8cf93f3c 100644 --- a/lib/mongo/cluster/periodic_executor.rb +++ b/lib/mongo/cluster/periodic_executor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/reapers/cursor_reaper.rb b/lib/mongo/cluster/reapers/cursor_reaper.rb index 581768182d..b08bab2017 100644 --- a/lib/mongo/cluster/reapers/cursor_reaper.rb +++ b/lib/mongo/cluster/reapers/cursor_reaper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/reapers/socket_reaper.rb b/lib/mongo/cluster/reapers/socket_reaper.rb index a667e1ed92..2eaa20ff97 100644 --- a/lib/mongo/cluster/reapers/socket_reaper.rb +++ b/lib/mongo/cluster/reapers/socket_reaper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/sdam_flow.rb b/lib/mongo/cluster/sdam_flow.rb index 9a1debee2e..eab2fd88b9 100644 --- a/lib/mongo/cluster/sdam_flow.rb +++ b/lib/mongo/cluster/sdam_flow.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology.rb b/lib/mongo/cluster/topology.rb index 88f92c31c1..5a2c5a3af9 100644 --- a/lib/mongo/cluster/topology.rb +++ b/lib/mongo/cluster/topology.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology/base.rb b/lib/mongo/cluster/topology/base.rb index 971a58b82f..e92181e4fa 100644 --- a/lib/mongo/cluster/topology/base.rb +++ b/lib/mongo/cluster/topology/base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology/load_balanced.rb b/lib/mongo/cluster/topology/load_balanced.rb index 0ad295b13a..7e6d4fcf67 100644 --- a/lib/mongo/cluster/topology/load_balanced.rb +++ b/lib/mongo/cluster/topology/load_balanced.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology/no_replica_set_options.rb b/lib/mongo/cluster/topology/no_replica_set_options.rb index 51125cb232..6267b28b8c 100644 --- a/lib/mongo/cluster/topology/no_replica_set_options.rb +++ b/lib/mongo/cluster/topology/no_replica_set_options.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology/replica_set_no_primary.rb b/lib/mongo/cluster/topology/replica_set_no_primary.rb index 31f90def65..3c81ba0b14 100644 --- a/lib/mongo/cluster/topology/replica_set_no_primary.rb +++ b/lib/mongo/cluster/topology/replica_set_no_primary.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology/replica_set_with_primary.rb b/lib/mongo/cluster/topology/replica_set_with_primary.rb index 90e4998c02..e0f389963f 100644 --- a/lib/mongo/cluster/topology/replica_set_with_primary.rb +++ b/lib/mongo/cluster/topology/replica_set_with_primary.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology/sharded.rb b/lib/mongo/cluster/topology/sharded.rb index 17a7c6a160..94b9bb1047 100644 --- a/lib/mongo/cluster/topology/sharded.rb +++ b/lib/mongo/cluster/topology/sharded.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology/single.rb b/lib/mongo/cluster/topology/single.rb index 845414bb0e..a623a2cf09 100644 --- a/lib/mongo/cluster/topology/single.rb +++ b/lib/mongo/cluster/topology/single.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster/topology/unknown.rb b/lib/mongo/cluster/topology/unknown.rb index 88e1da94d8..352a2de463 100644 --- a/lib/mongo/cluster/topology/unknown.rb +++ b/lib/mongo/cluster/topology/unknown.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/cluster_time.rb b/lib/mongo/cluster_time.rb index 96d76890cb..bf1c839022 100644 --- a/lib/mongo/cluster_time.rb +++ b/lib/mongo/cluster_time.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 734185f90b..bb828ce491 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/helpers.rb b/lib/mongo/collection/helpers.rb index a37101f083..a6feaf9c99 100644 --- a/lib/mongo/collection/helpers.rb +++ b/lib/mongo/collection/helpers.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2022 MongoDB Inc. # diff --git a/lib/mongo/collection/queryable_encryption.rb b/lib/mongo/collection/queryable_encryption.rb index fa8ebb7599..703bffc3dc 100644 --- a/lib/mongo/collection/queryable_encryption.rb +++ b/lib/mongo/collection/queryable_encryption.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2022 MongoDB Inc. # diff --git a/lib/mongo/collection/view.rb b/lib/mongo/collection/view.rb index dd58ef89ca..794e04e5c7 100644 --- a/lib/mongo/collection/view.rb +++ b/lib/mongo/collection/view.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/aggregation.rb b/lib/mongo/collection/view/aggregation.rb index fd1bc6f9b8..396c6eb142 100644 --- a/lib/mongo/collection/view/aggregation.rb +++ b/lib/mongo/collection/view/aggregation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/builder.rb b/lib/mongo/collection/view/builder.rb index 46cc1dff32..37776951a3 100644 --- a/lib/mongo/collection/view/builder.rb +++ b/lib/mongo/collection/view/builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/builder/aggregation.rb b/lib/mongo/collection/view/builder/aggregation.rb index 2f969e6377..964e0b3d48 100644 --- a/lib/mongo/collection/view/builder/aggregation.rb +++ b/lib/mongo/collection/view/builder/aggregation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/builder/map_reduce.rb b/lib/mongo/collection/view/builder/map_reduce.rb index d2ad05ccb1..3ac8cc63c7 100644 --- a/lib/mongo/collection/view/builder/map_reduce.rb +++ b/lib/mongo/collection/view/builder/map_reduce.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/change_stream.rb b/lib/mongo/collection/view/change_stream.rb index 1ed8db2851..ee2d9c23bd 100644 --- a/lib/mongo/collection/view/change_stream.rb +++ b/lib/mongo/collection/view/change_stream.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/change_stream/retryable.rb b/lib/mongo/collection/view/change_stream/retryable.rb index a0e6c41190..fcec2afdac 100644 --- a/lib/mongo/collection/view/change_stream/retryable.rb +++ b/lib/mongo/collection/view/change_stream/retryable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/explainable.rb b/lib/mongo/collection/view/explainable.rb index f25ae90d94..707b906a9f 100644 --- a/lib/mongo/collection/view/explainable.rb +++ b/lib/mongo/collection/view/explainable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/immutable.rb b/lib/mongo/collection/view/immutable.rb index f39f02e1f6..303044db38 100644 --- a/lib/mongo/collection/view/immutable.rb +++ b/lib/mongo/collection/view/immutable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/iterable.rb b/lib/mongo/collection/view/iterable.rb index 943755aed5..f9d7b1603a 100644 --- a/lib/mongo/collection/view/iterable.rb +++ b/lib/mongo/collection/view/iterable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/map_reduce.rb b/lib/mongo/collection/view/map_reduce.rb index fd4d5df489..279ce24409 100644 --- a/lib/mongo/collection/view/map_reduce.rb +++ b/lib/mongo/collection/view/map_reduce.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/readable.rb b/lib/mongo/collection/view/readable.rb index f6022ecc54..412137d40f 100644 --- a/lib/mongo/collection/view/readable.rb +++ b/lib/mongo/collection/view/readable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/collection/view/writable.rb b/lib/mongo/collection/view/writable.rb index 34b35d0478..40bc230725 100644 --- a/lib/mongo/collection/view/writable.rb +++ b/lib/mongo/collection/view/writable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/condition_variable.rb b/lib/mongo/condition_variable.rb index cbf77ccc9b..915e492f95 100644 --- a/lib/mongo/condition_variable.rb +++ b/lib/mongo/condition_variable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/config.rb b/lib/mongo/config.rb index e3049a1695..123a937918 100644 --- a/lib/mongo/config.rb +++ b/lib/mongo/config.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all require "mongo/config/options" require "mongo/config/validators/option" diff --git a/lib/mongo/config/options.rb b/lib/mongo/config/options.rb index 0eff9a8f3d..96191b269e 100644 --- a/lib/mongo/config/options.rb +++ b/lib/mongo/config/options.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all module Mongo module Config diff --git a/lib/mongo/config/validators/option.rb b/lib/mongo/config/validators/option.rb index 0377df27d8..9e9ba9c25a 100644 --- a/lib/mongo/config/validators/option.rb +++ b/lib/mongo/config/validators/option.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all module Mongo module Config diff --git a/lib/mongo/crypt.rb b/lib/mongo/crypt.rb index 612fe0e937..09a78b5d16 100644 --- a/lib/mongo/crypt.rb +++ b/lib/mongo/crypt.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/auto_decryption_context.rb b/lib/mongo/crypt/auto_decryption_context.rb index ee47484562..4de9103107 100644 --- a/lib/mongo/crypt/auto_decryption_context.rb +++ b/lib/mongo/crypt/auto_decryption_context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/auto_encrypter.rb b/lib/mongo/crypt/auto_encrypter.rb index ea431ae2ba..d26db87566 100644 --- a/lib/mongo/crypt/auto_encrypter.rb +++ b/lib/mongo/crypt/auto_encrypter.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/auto_encryption_context.rb b/lib/mongo/crypt/auto_encryption_context.rb index fb47e2391c..16335dd3cf 100644 --- a/lib/mongo/crypt/auto_encryption_context.rb +++ b/lib/mongo/crypt/auto_encryption_context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/binary.rb b/lib/mongo/crypt/binary.rb index 93ca7fd554..be9abe60c7 100644 --- a/lib/mongo/crypt/binary.rb +++ b/lib/mongo/crypt/binary.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/binding.rb b/lib/mongo/crypt/binding.rb index 15eb830c74..f3b8e8415a 100644 --- a/lib/mongo/crypt/binding.rb +++ b/lib/mongo/crypt/binding.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/context.rb b/lib/mongo/crypt/context.rb index ed35eec58a..18de60b5c0 100644 --- a/lib/mongo/crypt/context.rb +++ b/lib/mongo/crypt/context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/data_key_context.rb b/lib/mongo/crypt/data_key_context.rb index f7d0c6e1bf..aae7336fb9 100644 --- a/lib/mongo/crypt/data_key_context.rb +++ b/lib/mongo/crypt/data_key_context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/encryption_io.rb b/lib/mongo/crypt/encryption_io.rb index 2ed63efc79..67dee5731f 100644 --- a/lib/mongo/crypt/encryption_io.rb +++ b/lib/mongo/crypt/encryption_io.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/explicit_decryption_context.rb b/lib/mongo/crypt/explicit_decryption_context.rb index 91e341f5ab..c1d4bc4ca9 100644 --- a/lib/mongo/crypt/explicit_decryption_context.rb +++ b/lib/mongo/crypt/explicit_decryption_context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/explicit_encrypter.rb b/lib/mongo/crypt/explicit_encrypter.rb index a3ed4fc47c..cce570e441 100644 --- a/lib/mongo/crypt/explicit_encrypter.rb +++ b/lib/mongo/crypt/explicit_encrypter.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/explicit_encryption_context.rb b/lib/mongo/crypt/explicit_encryption_context.rb index a7649eecfd..2a737e0471 100644 --- a/lib/mongo/crypt/explicit_encryption_context.rb +++ b/lib/mongo/crypt/explicit_encryption_context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/explicit_encryption_expression_context.rb b/lib/mongo/crypt/explicit_encryption_expression_context.rb index d554649382..5ca9a16510 100644 --- a/lib/mongo/crypt/explicit_encryption_expression_context.rb +++ b/lib/mongo/crypt/explicit_encryption_expression_context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/handle.rb b/lib/mongo/crypt/handle.rb index 512cf10a6e..5716fb054b 100644 --- a/lib/mongo/crypt/handle.rb +++ b/lib/mongo/crypt/handle.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/hooks.rb b/lib/mongo/crypt/hooks.rb index 5d23732901..0f97dafee3 100644 --- a/lib/mongo/crypt/hooks.rb +++ b/lib/mongo/crypt/hooks.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms.rb b/lib/mongo/crypt/kms.rb index 19050535b7..97558b5beb 100644 --- a/lib/mongo/crypt/kms.rb +++ b/lib/mongo/crypt/kms.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/aws.rb b/lib/mongo/crypt/kms/aws.rb index c39c8a9f83..0c0c0003af 100644 --- a/lib/mongo/crypt/kms/aws.rb +++ b/lib/mongo/crypt/kms/aws.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/aws/credentials.rb b/lib/mongo/crypt/kms/aws/credentials.rb index fba9e596f7..da15ed9eb6 100644 --- a/lib/mongo/crypt/kms/aws/credentials.rb +++ b/lib/mongo/crypt/kms/aws/credentials.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/aws/master_document.rb b/lib/mongo/crypt/kms/aws/master_document.rb index 2cd365ac88..f2110092af 100644 --- a/lib/mongo/crypt/kms/aws/master_document.rb +++ b/lib/mongo/crypt/kms/aws/master_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/azure.rb b/lib/mongo/crypt/kms/azure.rb index e6bda28b70..6750e6d029 100644 --- a/lib/mongo/crypt/kms/azure.rb +++ b/lib/mongo/crypt/kms/azure.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/azure/credentials.rb b/lib/mongo/crypt/kms/azure/credentials.rb index d9a266fa03..8f717e9b4a 100644 --- a/lib/mongo/crypt/kms/azure/credentials.rb +++ b/lib/mongo/crypt/kms/azure/credentials.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/azure/master_document.rb b/lib/mongo/crypt/kms/azure/master_document.rb index 60e1f0b06f..a5b4edf274 100644 --- a/lib/mongo/crypt/kms/azure/master_document.rb +++ b/lib/mongo/crypt/kms/azure/master_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/credentials.rb b/lib/mongo/crypt/kms/credentials.rb index d7dd0432a3..e3319d24d4 100644 --- a/lib/mongo/crypt/kms/credentials.rb +++ b/lib/mongo/crypt/kms/credentials.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/gcp.rb b/lib/mongo/crypt/kms/gcp.rb index 43591e3593..2643b87ace 100644 --- a/lib/mongo/crypt/kms/gcp.rb +++ b/lib/mongo/crypt/kms/gcp.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/gcp/credentials.rb b/lib/mongo/crypt/kms/gcp/credentials.rb index 903b15642c..b39a878d97 100644 --- a/lib/mongo/crypt/kms/gcp/credentials.rb +++ b/lib/mongo/crypt/kms/gcp/credentials.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/gcp/credentials_retriever.rb b/lib/mongo/crypt/kms/gcp/credentials_retriever.rb index 9c931f9852..231fccfb40 100644 --- a/lib/mongo/crypt/kms/gcp/credentials_retriever.rb +++ b/lib/mongo/crypt/kms/gcp/credentials_retriever.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/gcp/master_document.rb b/lib/mongo/crypt/kms/gcp/master_document.rb index 369a6e1837..5d8af0cba5 100644 --- a/lib/mongo/crypt/kms/gcp/master_document.rb +++ b/lib/mongo/crypt/kms/gcp/master_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/kmip.rb b/lib/mongo/crypt/kms/kmip.rb index 00d32d2390..9456889314 100644 --- a/lib/mongo/crypt/kms/kmip.rb +++ b/lib/mongo/crypt/kms/kmip.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/kmip/credentials.rb b/lib/mongo/crypt/kms/kmip/credentials.rb index f9dda9a0cf..41ce18cc3b 100644 --- a/lib/mongo/crypt/kms/kmip/credentials.rb +++ b/lib/mongo/crypt/kms/kmip/credentials.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/kmip/master_document.rb b/lib/mongo/crypt/kms/kmip/master_document.rb index d3166008e4..e63284fb1b 100644 --- a/lib/mongo/crypt/kms/kmip/master_document.rb +++ b/lib/mongo/crypt/kms/kmip/master_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/local.rb b/lib/mongo/crypt/kms/local.rb index bab5f02bbd..eb3c68dcb4 100644 --- a/lib/mongo/crypt/kms/local.rb +++ b/lib/mongo/crypt/kms/local.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/local/credentials.rb b/lib/mongo/crypt/kms/local/credentials.rb index 56beb2a70f..c5d47777e1 100644 --- a/lib/mongo/crypt/kms/local/credentials.rb +++ b/lib/mongo/crypt/kms/local/credentials.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/local/master_document.rb b/lib/mongo/crypt/kms/local/master_document.rb index ce34688d11..937ad07e5e 100644 --- a/lib/mongo/crypt/kms/local/master_document.rb +++ b/lib/mongo/crypt/kms/local/master_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms/master_key_document.rb b/lib/mongo/crypt/kms/master_key_document.rb index 07780e30de..6bd5b7a8af 100644 --- a/lib/mongo/crypt/kms/master_key_document.rb +++ b/lib/mongo/crypt/kms/master_key_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/lib/mongo/crypt/kms_context.rb b/lib/mongo/crypt/kms_context.rb index 4304d4f5e6..844c5c2131 100644 --- a/lib/mongo/crypt/kms_context.rb +++ b/lib/mongo/crypt/kms_context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/rewrap_many_data_key_context.rb b/lib/mongo/crypt/rewrap_many_data_key_context.rb index 947638abd4..9e88e6b592 100644 --- a/lib/mongo/crypt/rewrap_many_data_key_context.rb +++ b/lib/mongo/crypt/rewrap_many_data_key_context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/crypt/rewrap_many_data_key_result.rb b/lib/mongo/crypt/rewrap_many_data_key_result.rb index 5e1b6b999a..39c1b2e164 100644 --- a/lib/mongo/crypt/rewrap_many_data_key_result.rb +++ b/lib/mongo/crypt/rewrap_many_data_key_result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2022 MongoDB Inc. # diff --git a/lib/mongo/crypt/status.rb b/lib/mongo/crypt/status.rb index b5ac6d08c4..834f668b5e 100644 --- a/lib/mongo/crypt/status.rb +++ b/lib/mongo/crypt/status.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all ## Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index 29e5fce84c..d24e137b5b 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/cursor/kill_spec.rb b/lib/mongo/cursor/kill_spec.rb index 561ee1c1bd..117f1b50fa 100644 --- a/lib/mongo/cursor/kill_spec.rb +++ b/lib/mongo/cursor/kill_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/database.rb b/lib/mongo/database.rb index 0bdd2148fc..9b656c5b99 100644 --- a/lib/mongo/database.rb +++ b/lib/mongo/database.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/database/view.rb b/lib/mongo/database/view.rb index a7f16f3213..4e8195fb67 100644 --- a/lib/mongo/database/view.rb +++ b/lib/mongo/database/view.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/dbref.rb b/lib/mongo/dbref.rb index 5dc9514474..e71635f2f5 100644 --- a/lib/mongo/dbref.rb +++ b/lib/mongo/dbref.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/distinguishing_semaphore.rb b/lib/mongo/distinguishing_semaphore.rb index 71a436b528..16cba0ab26 100644 --- a/lib/mongo/distinguishing_semaphore.rb +++ b/lib/mongo/distinguishing_semaphore.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error.rb b/lib/mongo/error.rb index bf5df68851..883fbee9a5 100644 --- a/lib/mongo/error.rb +++ b/lib/mongo/error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/auth_error.rb b/lib/mongo/error/auth_error.rb index 01771f2219..1b8a0a6e77 100644 --- a/lib/mongo/error/auth_error.rb +++ b/lib/mongo/error/auth_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/error/bad_load_balancer_target.rb b/lib/mongo/error/bad_load_balancer_target.rb index a37a929f6f..e7644ae5fa 100644 --- a/lib/mongo/error/bad_load_balancer_target.rb +++ b/lib/mongo/error/bad_load_balancer_target.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/error/bulk_write_error.rb b/lib/mongo/error/bulk_write_error.rb index a5b64c25a0..90d9f6c72e 100644 --- a/lib/mongo/error/bulk_write_error.rb +++ b/lib/mongo/error/bulk_write_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/change_stream_resumable.rb b/lib/mongo/error/change_stream_resumable.rb index 512e6939c6..db9c932a6e 100644 --- a/lib/mongo/error/change_stream_resumable.rb +++ b/lib/mongo/error/change_stream_resumable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/error/client_closed.rb b/lib/mongo/error/client_closed.rb index 535a89b811..06a665bd49 100644 --- a/lib/mongo/error/client_closed.rb +++ b/lib/mongo/error/client_closed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2022 MongoDB Inc. # diff --git a/lib/mongo/error/closed_stream.rb b/lib/mongo/error/closed_stream.rb index 1062fdc478..9e2472a998 100644 --- a/lib/mongo/error/closed_stream.rb +++ b/lib/mongo/error/closed_stream.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/connection_check_out_timeout.rb b/lib/mongo/error/connection_check_out_timeout.rb index e1d863493d..aa07c415ab 100644 --- a/lib/mongo/error/connection_check_out_timeout.rb +++ b/lib/mongo/error/connection_check_out_timeout.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/connection_perished.rb b/lib/mongo/error/connection_perished.rb index a99ba26d37..7eae368fc8 100644 --- a/lib/mongo/error/connection_perished.rb +++ b/lib/mongo/error/connection_perished.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/connection_unavailable.rb b/lib/mongo/error/connection_unavailable.rb index 7713fed0e7..652fe33ae9 100644 --- a/lib/mongo/error/connection_unavailable.rb +++ b/lib/mongo/error/connection_unavailable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2022 MongoDB Inc. # diff --git a/lib/mongo/error/credential_check_error.rb b/lib/mongo/error/credential_check_error.rb index 438cb5a98c..35b89b30fa 100644 --- a/lib/mongo/error/credential_check_error.rb +++ b/lib/mongo/error/credential_check_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/crypt_error.rb b/lib/mongo/error/crypt_error.rb index ca0afb0119..9bc6a48191 100644 --- a/lib/mongo/error/crypt_error.rb +++ b/lib/mongo/error/crypt_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/extra_file_chunk.rb b/lib/mongo/error/extra_file_chunk.rb index 5cd3517c41..9f7e79ced0 100644 --- a/lib/mongo/error/extra_file_chunk.rb +++ b/lib/mongo/error/extra_file_chunk.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/failed_string_prep_validation.rb b/lib/mongo/error/failed_string_prep_validation.rb index 005d3a398d..d737a41dde 100644 --- a/lib/mongo/error/failed_string_prep_validation.rb +++ b/lib/mongo/error/failed_string_prep_validation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo class Error diff --git a/lib/mongo/error/file_not_found.rb b/lib/mongo/error/file_not_found.rb index 246df00a5a..6210ae4a0d 100644 --- a/lib/mongo/error/file_not_found.rb +++ b/lib/mongo/error/file_not_found.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/handshake_error.rb b/lib/mongo/error/handshake_error.rb index d4f1f7ccd6..73f9ef4fb4 100644 --- a/lib/mongo/error/handshake_error.rb +++ b/lib/mongo/error/handshake_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/error/insufficient_iteration_count.rb b/lib/mongo/error/insufficient_iteration_count.rb index 01e69ad337..a3cf42a87a 100644 --- a/lib/mongo/error/insufficient_iteration_count.rb +++ b/lib/mongo/error/insufficient_iteration_count.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/internal_driver_error.rb b/lib/mongo/error/internal_driver_error.rb index 32cc93b535..13305d5455 100644 --- a/lib/mongo/error/internal_driver_error.rb +++ b/lib/mongo/error/internal_driver_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_address.rb b/lib/mongo/error/invalid_address.rb index 8bb6ac58e6..fd8dde2479 100644 --- a/lib/mongo/error/invalid_address.rb +++ b/lib/mongo/error/invalid_address.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_application_name.rb b/lib/mongo/error/invalid_application_name.rb index c970b30022..4ba0e51ff1 100644 --- a/lib/mongo/error/invalid_application_name.rb +++ b/lib/mongo/error/invalid_application_name.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_bulk_operation.rb b/lib/mongo/error/invalid_bulk_operation.rb index 55a4f98c3f..8e35bff5d2 100644 --- a/lib/mongo/error/invalid_bulk_operation.rb +++ b/lib/mongo/error/invalid_bulk_operation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_bulk_operation_type.rb b/lib/mongo/error/invalid_bulk_operation_type.rb index c90a52b681..a4b5ac8668 100644 --- a/lib/mongo/error/invalid_bulk_operation_type.rb +++ b/lib/mongo/error/invalid_bulk_operation_type.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_collection_name.rb b/lib/mongo/error/invalid_collection_name.rb index e23c8fc87e..238dbd3f27 100644 --- a/lib/mongo/error/invalid_collection_name.rb +++ b/lib/mongo/error/invalid_collection_name.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_config_option.rb b/lib/mongo/error/invalid_config_option.rb index 2bc7150db1..a208ba6fd6 100644 --- a/lib/mongo/error/invalid_config_option.rb +++ b/lib/mongo/error/invalid_config_option.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all module Mongo class Error diff --git a/lib/mongo/error/invalid_cursor_operation.rb b/lib/mongo/error/invalid_cursor_operation.rb index 1d63bbd349..9c7d9886d8 100644 --- a/lib/mongo/error/invalid_cursor_operation.rb +++ b/lib/mongo/error/invalid_cursor_operation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_database_name.rb b/lib/mongo/error/invalid_database_name.rb index ac81303304..30b27e454b 100644 --- a/lib/mongo/error/invalid_database_name.rb +++ b/lib/mongo/error/invalid_database_name.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_document.rb b/lib/mongo/error/invalid_document.rb index 3529932e93..a1d8e5c9dd 100644 --- a/lib/mongo/error/invalid_document.rb +++ b/lib/mongo/error/invalid_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_file.rb b/lib/mongo/error/invalid_file.rb index ad9091eb8f..8522b10f2d 100644 --- a/lib/mongo/error/invalid_file.rb +++ b/lib/mongo/error/invalid_file.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_file_revision.rb b/lib/mongo/error/invalid_file_revision.rb index e3581d9835..3d2c8e6bcb 100644 --- a/lib/mongo/error/invalid_file_revision.rb +++ b/lib/mongo/error/invalid_file_revision.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_min_pool_size.rb b/lib/mongo/error/invalid_min_pool_size.rb index d4e93f8e47..b97e7058d2 100644 --- a/lib/mongo/error/invalid_min_pool_size.rb +++ b/lib/mongo/error/invalid_min_pool_size.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_nonce.rb b/lib/mongo/error/invalid_nonce.rb index cddb222ce5..5155daee39 100644 --- a/lib/mongo/error/invalid_nonce.rb +++ b/lib/mongo/error/invalid_nonce.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_read_concern.rb b/lib/mongo/error/invalid_read_concern.rb index 2067d69e6a..141c66bdb6 100644 --- a/lib/mongo/error/invalid_read_concern.rb +++ b/lib/mongo/error/invalid_read_concern.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_read_option.rb b/lib/mongo/error/invalid_read_option.rb index f1115d1b2a..c378511c31 100644 --- a/lib/mongo/error/invalid_read_option.rb +++ b/lib/mongo/error/invalid_read_option.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_replacement_document.rb b/lib/mongo/error/invalid_replacement_document.rb index a09c1cd52c..fd431bdefe 100644 --- a/lib/mongo/error/invalid_replacement_document.rb +++ b/lib/mongo/error/invalid_replacement_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_server_auth_host.rb b/lib/mongo/error/invalid_server_auth_host.rb index ef09c19e10..7c272621b1 100644 --- a/lib/mongo/error/invalid_server_auth_host.rb +++ b/lib/mongo/error/invalid_server_auth_host.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_server_auth_response.rb b/lib/mongo/error/invalid_server_auth_response.rb index 09b2ab6c77..96d09451ed 100644 --- a/lib/mongo/error/invalid_server_auth_response.rb +++ b/lib/mongo/error/invalid_server_auth_response.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_server_preference.rb b/lib/mongo/error/invalid_server_preference.rb index 694e66fad7..7833bb4490 100644 --- a/lib/mongo/error/invalid_server_preference.rb +++ b/lib/mongo/error/invalid_server_preference.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_session.rb b/lib/mongo/error/invalid_session.rb index 1392f4af63..02af75fb74 100644 --- a/lib/mongo/error/invalid_session.rb +++ b/lib/mongo/error/invalid_session.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_signature.rb b/lib/mongo/error/invalid_signature.rb index 36906c9e30..635b46fba9 100644 --- a/lib/mongo/error/invalid_signature.rb +++ b/lib/mongo/error/invalid_signature.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_transaction_operation.rb b/lib/mongo/error/invalid_transaction_operation.rb index ac3dae2da3..d9fada982c 100644 --- a/lib/mongo/error/invalid_transaction_operation.rb +++ b/lib/mongo/error/invalid_transaction_operation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_txt_record.rb b/lib/mongo/error/invalid_txt_record.rb index f064b61fb9..522bdb5d05 100644 --- a/lib/mongo/error/invalid_txt_record.rb +++ b/lib/mongo/error/invalid_txt_record.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_update_document.rb b/lib/mongo/error/invalid_update_document.rb index 5d15ef105f..0ebdf64acd 100644 --- a/lib/mongo/error/invalid_update_document.rb +++ b/lib/mongo/error/invalid_update_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_uri.rb b/lib/mongo/error/invalid_uri.rb index b10dfb93c7..f54e00c4aa 100644 --- a/lib/mongo/error/invalid_uri.rb +++ b/lib/mongo/error/invalid_uri.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/invalid_write_concern.rb b/lib/mongo/error/invalid_write_concern.rb index e004c5f62f..a4f7912084 100644 --- a/lib/mongo/error/invalid_write_concern.rb +++ b/lib/mongo/error/invalid_write_concern.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/kms_error.rb b/lib/mongo/error/kms_error.rb index 0f0c34ba8c..98978d5301 100644 --- a/lib/mongo/error/kms_error.rb +++ b/lib/mongo/error/kms_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/labelable.rb b/lib/mongo/error/labelable.rb index cd6f52cb48..48d3fe1a2b 100644 --- a/lib/mongo/error/labelable.rb +++ b/lib/mongo/error/labelable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2022 MongoDB Inc. # diff --git a/lib/mongo/error/lint_error.rb b/lib/mongo/error/lint_error.rb index 89c9b0cc70..f656ca9796 100644 --- a/lib/mongo/error/lint_error.rb +++ b/lib/mongo/error/lint_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/error/max_bson_size.rb b/lib/mongo/error/max_bson_size.rb index 96e2411daa..c59e6233e3 100644 --- a/lib/mongo/error/max_bson_size.rb +++ b/lib/mongo/error/max_bson_size.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/max_message_size.rb b/lib/mongo/error/max_message_size.rb index 7346fc3a43..8c23184833 100644 --- a/lib/mongo/error/max_message_size.rb +++ b/lib/mongo/error/max_message_size.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/mismatched_domain.rb b/lib/mongo/error/mismatched_domain.rb index 407b38c167..bb2e9d236d 100644 --- a/lib/mongo/error/mismatched_domain.rb +++ b/lib/mongo/error/mismatched_domain.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/error/missing_connection.rb b/lib/mongo/error/missing_connection.rb index 2056d7be08..5ee90cfa1d 100644 --- a/lib/mongo/error/missing_connection.rb +++ b/lib/mongo/error/missing_connection.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2022 MongoDB Inc. # diff --git a/lib/mongo/error/missing_file_chunk.rb b/lib/mongo/error/missing_file_chunk.rb index 78bc8f7fdd..1abeea380e 100644 --- a/lib/mongo/error/missing_file_chunk.rb +++ b/lib/mongo/error/missing_file_chunk.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/missing_password.rb b/lib/mongo/error/missing_password.rb index 1ed4983983..aeacde19b2 100644 --- a/lib/mongo/error/missing_password.rb +++ b/lib/mongo/error/missing_password.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/missing_resume_token.rb b/lib/mongo/error/missing_resume_token.rb index 2555675dd9..0288004027 100644 --- a/lib/mongo/error/missing_resume_token.rb +++ b/lib/mongo/error/missing_resume_token.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/error/missing_scram_server_signature.rb b/lib/mongo/error/missing_scram_server_signature.rb index f64e59a013..b11f34cf9b 100644 --- a/lib/mongo/error/missing_scram_server_signature.rb +++ b/lib/mongo/error/missing_scram_server_signature.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/missing_service_id.rb b/lib/mongo/error/missing_service_id.rb index 837358b3f7..7a373cf185 100644 --- a/lib/mongo/error/missing_service_id.rb +++ b/lib/mongo/error/missing_service_id.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/error/mongocryptd_spawn_error.rb b/lib/mongo/error/mongocryptd_spawn_error.rb index 4ec7881914..be64e92aa3 100644 --- a/lib/mongo/error/mongocryptd_spawn_error.rb +++ b/lib/mongo/error/mongocryptd_spawn_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/multi_index_drop.rb b/lib/mongo/error/multi_index_drop.rb index f80886c3fe..db08e093a1 100644 --- a/lib/mongo/error/multi_index_drop.rb +++ b/lib/mongo/error/multi_index_drop.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/error/need_primary_server.rb b/lib/mongo/error/need_primary_server.rb index 2676fa3cc9..ef4a2c4fcc 100644 --- a/lib/mongo/error/need_primary_server.rb +++ b/lib/mongo/error/need_primary_server.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/no_server_available.rb b/lib/mongo/error/no_server_available.rb index 1d56ca3e13..a78ece6956 100644 --- a/lib/mongo/error/no_server_available.rb +++ b/lib/mongo/error/no_server_available.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/no_service_connection_available.rb b/lib/mongo/error/no_service_connection_available.rb index 8a7513b6b5..da6d834167 100644 --- a/lib/mongo/error/no_service_connection_available.rb +++ b/lib/mongo/error/no_service_connection_available.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/error/no_srv_records.rb b/lib/mongo/error/no_srv_records.rb index 41dd707b71..0eca7cdd3a 100644 --- a/lib/mongo/error/no_srv_records.rb +++ b/lib/mongo/error/no_srv_records.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/error/notable.rb b/lib/mongo/error/notable.rb index 3bde21a655..b090017d16 100644 --- a/lib/mongo/error/notable.rb +++ b/lib/mongo/error/notable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/operation_failure.rb b/lib/mongo/error/operation_failure.rb index 8a33812448..1cc47f520b 100644 --- a/lib/mongo/error/operation_failure.rb +++ b/lib/mongo/error/operation_failure.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/error/parser.rb b/lib/mongo/error/parser.rb index d94c50388d..c566de9cc4 100644 --- a/lib/mongo/error/parser.rb +++ b/lib/mongo/error/parser.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/error/pool_cleared_error.rb b/lib/mongo/error/pool_cleared_error.rb index 00de42b7af..6f8b493e1a 100644 --- a/lib/mongo/error/pool_cleared_error.rb +++ b/lib/mongo/error/pool_cleared_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-present MongoDB Inc. # diff --git a/lib/mongo/error/pool_closed_error.rb b/lib/mongo/error/pool_closed_error.rb index 775d5e7d42..e5eb44e6d0 100644 --- a/lib/mongo/error/pool_closed_error.rb +++ b/lib/mongo/error/pool_closed_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/pool_error.rb b/lib/mongo/error/pool_error.rb index 83e39f714c..446dbd2e7a 100644 --- a/lib/mongo/error/pool_error.rb +++ b/lib/mongo/error/pool_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/pool_paused_error.rb b/lib/mongo/error/pool_paused_error.rb index 3f3d71b5b8..6f4cd28024 100644 --- a/lib/mongo/error/pool_paused_error.rb +++ b/lib/mongo/error/pool_paused_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/raise_original_error.rb b/lib/mongo/error/raise_original_error.rb index 7721d2bb67..5b3e399121 100644 --- a/lib/mongo/error/raise_original_error.rb +++ b/lib/mongo/error/raise_original_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/read_write_retryable.rb b/lib/mongo/error/read_write_retryable.rb index cf07cf5913..b7ead1116b 100644 --- a/lib/mongo/error/read_write_retryable.rb +++ b/lib/mongo/error/read_write_retryable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2022 MongoDB Inc. # diff --git a/lib/mongo/error/sdam_error_detection.rb b/lib/mongo/error/sdam_error_detection.rb index 567695224d..a0e10d8d3e 100644 --- a/lib/mongo/error/sdam_error_detection.rb +++ b/lib/mongo/error/sdam_error_detection.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo class Error diff --git a/lib/mongo/error/server_api_conflict.rb b/lib/mongo/error/server_api_conflict.rb index 9dc83a3d5b..1b893c4247 100644 --- a/lib/mongo/error/server_api_conflict.rb +++ b/lib/mongo/error/server_api_conflict.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/error/server_api_not_supported.rb b/lib/mongo/error/server_api_not_supported.rb index 376b1dfd53..1b84aa7f45 100644 --- a/lib/mongo/error/server_api_not_supported.rb +++ b/lib/mongo/error/server_api_not_supported.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/error/server_certificate_revoked.rb b/lib/mongo/error/server_certificate_revoked.rb index 40479a1cc7..429020c712 100644 --- a/lib/mongo/error/server_certificate_revoked.rb +++ b/lib/mongo/error/server_certificate_revoked.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/server_not_usable.rb b/lib/mongo/error/server_not_usable.rb index b1ef3e8738..77af027b1e 100644 --- a/lib/mongo/error/server_not_usable.rb +++ b/lib/mongo/error/server_not_usable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/session_ended.rb b/lib/mongo/error/session_ended.rb index 29c9d9dd0a..15d57e07d0 100644 --- a/lib/mongo/error/session_ended.rb +++ b/lib/mongo/error/session_ended.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/error/session_not_materialized.rb b/lib/mongo/error/session_not_materialized.rb index 8f2ec14750..63cf48e1de 100644 --- a/lib/mongo/error/session_not_materialized.rb +++ b/lib/mongo/error/session_not_materialized.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2022 MongoDB Inc. # diff --git a/lib/mongo/error/sessions_not_supported.rb b/lib/mongo/error/sessions_not_supported.rb index 2fce6c6acb..9c179094e0 100644 --- a/lib/mongo/error/sessions_not_supported.rb +++ b/lib/mongo/error/sessions_not_supported.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/snapshot_session_invalid_server_version.rb b/lib/mongo/error/snapshot_session_invalid_server_version.rb index e8a14c2d62..f64dafc152 100644 --- a/lib/mongo/error/snapshot_session_invalid_server_version.rb +++ b/lib/mongo/error/snapshot_session_invalid_server_version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/error/snapshot_session_transaction_prohibited.rb b/lib/mongo/error/snapshot_session_transaction_prohibited.rb index 93fd5e6a1f..d1ca1b1040 100644 --- a/lib/mongo/error/snapshot_session_transaction_prohibited.rb +++ b/lib/mongo/error/snapshot_session_transaction_prohibited.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/error/socket_error.rb b/lib/mongo/error/socket_error.rb index 9171d09fd0..84821bb39a 100644 --- a/lib/mongo/error/socket_error.rb +++ b/lib/mongo/error/socket_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/error/socket_timeout_error.rb b/lib/mongo/error/socket_timeout_error.rb index 5955b86fd3..25b5980fd8 100644 --- a/lib/mongo/error/socket_timeout_error.rb +++ b/lib/mongo/error/socket_timeout_error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unchangeable_collection_option.rb b/lib/mongo/error/unchangeable_collection_option.rb index dbdc48d84a..20a8d306e2 100644 --- a/lib/mongo/error/unchangeable_collection_option.rb +++ b/lib/mongo/error/unchangeable_collection_option.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unexpected_chunk_length.rb b/lib/mongo/error/unexpected_chunk_length.rb index 8c4b7a470d..08a5eb63c0 100644 --- a/lib/mongo/error/unexpected_chunk_length.rb +++ b/lib/mongo/error/unexpected_chunk_length.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unexpected_response.rb b/lib/mongo/error/unexpected_response.rb index 622f4d98d0..a1a638a627 100644 --- a/lib/mongo/error/unexpected_response.rb +++ b/lib/mongo/error/unexpected_response.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unknown_payload_type.rb b/lib/mongo/error/unknown_payload_type.rb index 5796edb72a..60d6195380 100644 --- a/lib/mongo/error/unknown_payload_type.rb +++ b/lib/mongo/error/unknown_payload_type.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unmet_dependency.rb b/lib/mongo/error/unmet_dependency.rb index ed19be4d1f..542871f9d8 100644 --- a/lib/mongo/error/unmet_dependency.rb +++ b/lib/mongo/error/unmet_dependency.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/unsupported_array_filters.rb b/lib/mongo/error/unsupported_array_filters.rb index 1d61489a0a..0fa0f193ed 100644 --- a/lib/mongo/error/unsupported_array_filters.rb +++ b/lib/mongo/error/unsupported_array_filters.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unsupported_collation.rb b/lib/mongo/error/unsupported_collation.rb index 34a1188409..c6288c814b 100644 --- a/lib/mongo/error/unsupported_collation.rb +++ b/lib/mongo/error/unsupported_collation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unsupported_features.rb b/lib/mongo/error/unsupported_features.rb index a310aa2e45..4f7f91f9ce 100644 --- a/lib/mongo/error/unsupported_features.rb +++ b/lib/mongo/error/unsupported_features.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unsupported_message_type.rb b/lib/mongo/error/unsupported_message_type.rb index 34d81b2121..576daf0b29 100644 --- a/lib/mongo/error/unsupported_message_type.rb +++ b/lib/mongo/error/unsupported_message_type.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/error/unsupported_option.rb b/lib/mongo/error/unsupported_option.rb index 853038f12e..04a71b11e4 100644 --- a/lib/mongo/error/unsupported_option.rb +++ b/lib/mongo/error/unsupported_option.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/error/write_retryable.rb b/lib/mongo/error/write_retryable.rb index 7935b80b91..0e9c130b64 100644 --- a/lib/mongo/error/write_retryable.rb +++ b/lib/mongo/error/write_retryable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/event.rb b/lib/mongo/event.rb index 1ab8f8ad0a..7d90fe0577 100644 --- a/lib/mongo/event.rb +++ b/lib/mongo/event.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/event/base.rb b/lib/mongo/event/base.rb index 66bf35c527..ca0fca22f8 100644 --- a/lib/mongo/event/base.rb +++ b/lib/mongo/event/base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/event/listeners.rb b/lib/mongo/event/listeners.rb index c83bf32da8..143b36ed21 100644 --- a/lib/mongo/event/listeners.rb +++ b/lib/mongo/event/listeners.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/event/publisher.rb b/lib/mongo/event/publisher.rb index 121239403d..a55625994c 100644 --- a/lib/mongo/event/publisher.rb +++ b/lib/mongo/event/publisher.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/event/subscriber.rb b/lib/mongo/event/subscriber.rb index af0dbd7072..f8d1e6cdc8 100644 --- a/lib/mongo/event/subscriber.rb +++ b/lib/mongo/event/subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/grid.rb b/lib/mongo/grid.rb index 83f2f95349..22434f6325 100644 --- a/lib/mongo/grid.rb +++ b/lib/mongo/grid.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/grid/file.rb b/lib/mongo/grid/file.rb index 507a4d113e..e9f3616b5d 100644 --- a/lib/mongo/grid/file.rb +++ b/lib/mongo/grid/file.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/grid/file/chunk.rb b/lib/mongo/grid/file/chunk.rb index c9a5d418e4..34002b691b 100644 --- a/lib/mongo/grid/file/chunk.rb +++ b/lib/mongo/grid/file/chunk.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/grid/file/info.rb b/lib/mongo/grid/file/info.rb index 3a6890be77..0ed36fae26 100644 --- a/lib/mongo/grid/file/info.rb +++ b/lib/mongo/grid/file/info.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/grid/fs_bucket.rb b/lib/mongo/grid/fs_bucket.rb index 6007a5fa7a..6cb732efc2 100644 --- a/lib/mongo/grid/fs_bucket.rb +++ b/lib/mongo/grid/fs_bucket.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/grid/stream.rb b/lib/mongo/grid/stream.rb index c6db4bddf9..6a4045b31a 100644 --- a/lib/mongo/grid/stream.rb +++ b/lib/mongo/grid/stream.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/grid/stream/read.rb b/lib/mongo/grid/stream/read.rb index 4d77786a8a..f33b4a5126 100644 --- a/lib/mongo/grid/stream/read.rb +++ b/lib/mongo/grid/stream/read.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/grid/stream/write.rb b/lib/mongo/grid/stream/write.rb index 071db17f33..75ef68c56b 100644 --- a/lib/mongo/grid/stream/write.rb +++ b/lib/mongo/grid/stream/write.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/id.rb b/lib/mongo/id.rb index 1cdbf7bb9c..8c6622cccd 100644 --- a/lib/mongo/id.rb +++ b/lib/mongo/id.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/index.rb b/lib/mongo/index.rb index 03e86d9824..52088e9ae8 100644 --- a/lib/mongo/index.rb +++ b/lib/mongo/index.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/index/view.rb b/lib/mongo/index/view.rb index bee13b603d..3a45842899 100644 --- a/lib/mongo/index/view.rb +++ b/lib/mongo/index/view.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/lint.rb b/lib/mongo/lint.rb index 5c549d1aa7..b7823d5016 100644 --- a/lib/mongo/lint.rb +++ b/lib/mongo/lint.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo diff --git a/lib/mongo/loggable.rb b/lib/mongo/loggable.rb index f0193af68e..fd2f47e8d2 100644 --- a/lib/mongo/loggable.rb +++ b/lib/mongo/loggable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/logger.rb b/lib/mongo/logger.rb index b979d94d99..927d96d549 100644 --- a/lib/mongo/logger.rb +++ b/lib/mongo/logger.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring.rb b/lib/mongo/monitoring.rb index 145584a92c..8c0be2c8fa 100644 --- a/lib/mongo/monitoring.rb +++ b/lib/mongo/monitoring.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/cmap_log_subscriber.rb b/lib/mongo/monitoring/cmap_log_subscriber.rb index 6630b7fc36..e1ba66dbfd 100644 --- a/lib/mongo/monitoring/cmap_log_subscriber.rb +++ b/lib/mongo/monitoring/cmap_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/command_log_subscriber.rb b/lib/mongo/monitoring/command_log_subscriber.rb index 49765d526d..2a55b398db 100644 --- a/lib/mongo/monitoring/command_log_subscriber.rb +++ b/lib/mongo/monitoring/command_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event.rb b/lib/mongo/monitoring/event.rb index f7904601f7..f78fa4e0a9 100644 --- a/lib/mongo/monitoring/event.rb +++ b/lib/mongo/monitoring/event.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap.rb b/lib/mongo/monitoring/event/cmap.rb index 7028eea5e2..fb69dbb76a 100644 --- a/lib/mongo/monitoring/event/cmap.rb +++ b/lib/mongo/monitoring/event/cmap.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/base.rb b/lib/mongo/monitoring/event/cmap/base.rb index 861524ee51..5b290d5365 100644 --- a/lib/mongo/monitoring/event/cmap/base.rb +++ b/lib/mongo/monitoring/event/cmap/base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb b/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb index 64c2e8b4a1..85e9a23c21 100644 --- a/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +++ b/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb b/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb index 23fd027eaf..83e9b705a7 100644 --- a/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +++ b/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/connection_checked_in.rb b/lib/mongo/monitoring/event/cmap/connection_checked_in.rb index 5a524aaf8e..cd6be17992 100644 --- a/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +++ b/lib/mongo/monitoring/event/cmap/connection_checked_in.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/connection_checked_out.rb b/lib/mongo/monitoring/event/cmap/connection_checked_out.rb index 81b0ad3cd8..c55b448f8d 100644 --- a/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +++ b/lib/mongo/monitoring/event/cmap/connection_checked_out.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/connection_closed.rb b/lib/mongo/monitoring/event/cmap/connection_closed.rb index d2ec9855ca..895177bccd 100644 --- a/lib/mongo/monitoring/event/cmap/connection_closed.rb +++ b/lib/mongo/monitoring/event/cmap/connection_closed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/connection_created.rb b/lib/mongo/monitoring/event/cmap/connection_created.rb index d3f90ef1a3..49033df0e3 100644 --- a/lib/mongo/monitoring/event/cmap/connection_created.rb +++ b/lib/mongo/monitoring/event/cmap/connection_created.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/connection_ready.rb b/lib/mongo/monitoring/event/cmap/connection_ready.rb index de4befe276..18b23cc167 100644 --- a/lib/mongo/monitoring/event/cmap/connection_ready.rb +++ b/lib/mongo/monitoring/event/cmap/connection_ready.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/pool_cleared.rb b/lib/mongo/monitoring/event/cmap/pool_cleared.rb index 6725d0cc35..608bc7352d 100644 --- a/lib/mongo/monitoring/event/cmap/pool_cleared.rb +++ b/lib/mongo/monitoring/event/cmap/pool_cleared.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/pool_closed.rb b/lib/mongo/monitoring/event/cmap/pool_closed.rb index 375f483691..b8c980d057 100644 --- a/lib/mongo/monitoring/event/cmap/pool_closed.rb +++ b/lib/mongo/monitoring/event/cmap/pool_closed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/pool_created.rb b/lib/mongo/monitoring/event/cmap/pool_created.rb index ea04cf82b8..a705702c10 100644 --- a/lib/mongo/monitoring/event/cmap/pool_created.rb +++ b/lib/mongo/monitoring/event/cmap/pool_created.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/cmap/pool_ready.rb b/lib/mongo/monitoring/event/cmap/pool_ready.rb index 494de397aa..b3cedde8b1 100644 --- a/lib/mongo/monitoring/event/cmap/pool_ready.rb +++ b/lib/mongo/monitoring/event/cmap/pool_ready.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-present MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/command_failed.rb b/lib/mongo/monitoring/event/command_failed.rb index 08179474a4..2beb4cdaf0 100644 --- a/lib/mongo/monitoring/event/command_failed.rb +++ b/lib/mongo/monitoring/event/command_failed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/command_started.rb b/lib/mongo/monitoring/event/command_started.rb index 1e6ebba96b..a338cf1ea4 100644 --- a/lib/mongo/monitoring/event/command_started.rb +++ b/lib/mongo/monitoring/event/command_started.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/command_succeeded.rb b/lib/mongo/monitoring/event/command_succeeded.rb index bd8f47b96a..33522c3bc1 100644 --- a/lib/mongo/monitoring/event/command_succeeded.rb +++ b/lib/mongo/monitoring/event/command_succeeded.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/secure.rb b/lib/mongo/monitoring/event/secure.rb index 04b8b2a980..79abfd5a1b 100644 --- a/lib/mongo/monitoring/event/secure.rb +++ b/lib/mongo/monitoring/event/secure.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/server_closed.rb b/lib/mongo/monitoring/event/server_closed.rb index 35a6f34ce8..ace0bca135 100644 --- a/lib/mongo/monitoring/event/server_closed.rb +++ b/lib/mongo/monitoring/event/server_closed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/server_description_changed.rb b/lib/mongo/monitoring/event/server_description_changed.rb index 0c52bca6d1..31a4f04c8a 100644 --- a/lib/mongo/monitoring/event/server_description_changed.rb +++ b/lib/mongo/monitoring/event/server_description_changed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/server_heartbeat_failed.rb b/lib/mongo/monitoring/event/server_heartbeat_failed.rb index 10681a483a..8e9e9b7779 100644 --- a/lib/mongo/monitoring/event/server_heartbeat_failed.rb +++ b/lib/mongo/monitoring/event/server_heartbeat_failed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/server_heartbeat_started.rb b/lib/mongo/monitoring/event/server_heartbeat_started.rb index 16fa8c7404..f5ad5317b4 100644 --- a/lib/mongo/monitoring/event/server_heartbeat_started.rb +++ b/lib/mongo/monitoring/event/server_heartbeat_started.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb b/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb index 58a0a95819..71384f610e 100644 --- a/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +++ b/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/server_opening.rb b/lib/mongo/monitoring/event/server_opening.rb index 5ddbfa560d..7b6205b653 100644 --- a/lib/mongo/monitoring/event/server_opening.rb +++ b/lib/mongo/monitoring/event/server_opening.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/topology_changed.rb b/lib/mongo/monitoring/event/topology_changed.rb index 9effb3dd01..9d3c48e77b 100644 --- a/lib/mongo/monitoring/event/topology_changed.rb +++ b/lib/mongo/monitoring/event/topology_changed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/topology_closed.rb b/lib/mongo/monitoring/event/topology_closed.rb index f3211da6b4..6979c39787 100644 --- a/lib/mongo/monitoring/event/topology_closed.rb +++ b/lib/mongo/monitoring/event/topology_closed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/event/topology_opening.rb b/lib/mongo/monitoring/event/topology_opening.rb index 8b36ae264e..cfdf1f7f96 100644 --- a/lib/mongo/monitoring/event/topology_opening.rb +++ b/lib/mongo/monitoring/event/topology_opening.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/publishable.rb b/lib/mongo/monitoring/publishable.rb index 71397b7e9c..166baa9c6d 100644 --- a/lib/mongo/monitoring/publishable.rb +++ b/lib/mongo/monitoring/publishable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/sdam_log_subscriber.rb b/lib/mongo/monitoring/sdam_log_subscriber.rb index b5328e671b..3684906d7c 100644 --- a/lib/mongo/monitoring/sdam_log_subscriber.rb +++ b/lib/mongo/monitoring/sdam_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/server_closed_log_subscriber.rb b/lib/mongo/monitoring/server_closed_log_subscriber.rb index 05ab7c2caf..0ad2758519 100644 --- a/lib/mongo/monitoring/server_closed_log_subscriber.rb +++ b/lib/mongo/monitoring/server_closed_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/server_description_changed_log_subscriber.rb b/lib/mongo/monitoring/server_description_changed_log_subscriber.rb index 52eb15bec7..cf58fcfa51 100644 --- a/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +++ b/lib/mongo/monitoring/server_description_changed_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/server_opening_log_subscriber.rb b/lib/mongo/monitoring/server_opening_log_subscriber.rb index 53caa38e74..0162c41fa5 100644 --- a/lib/mongo/monitoring/server_opening_log_subscriber.rb +++ b/lib/mongo/monitoring/server_opening_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/topology_changed_log_subscriber.rb b/lib/mongo/monitoring/topology_changed_log_subscriber.rb index 491b969075..13804e464c 100644 --- a/lib/mongo/monitoring/topology_changed_log_subscriber.rb +++ b/lib/mongo/monitoring/topology_changed_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/topology_closed_log_subscriber.rb b/lib/mongo/monitoring/topology_closed_log_subscriber.rb index 269d88f8bc..6982946638 100644 --- a/lib/mongo/monitoring/topology_closed_log_subscriber.rb +++ b/lib/mongo/monitoring/topology_closed_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/topology_opening_log_subscriber.rb b/lib/mongo/monitoring/topology_opening_log_subscriber.rb index 0fbbf4d40d..cbff5067ad 100644 --- a/lib/mongo/monitoring/topology_opening_log_subscriber.rb +++ b/lib/mongo/monitoring/topology_opening_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/monitoring/unified_sdam_log_subscriber.rb b/lib/mongo/monitoring/unified_sdam_log_subscriber.rb index 511e56dd29..a43510db22 100644 --- a/lib/mongo/monitoring/unified_sdam_log_subscriber.rb +++ b/lib/mongo/monitoring/unified_sdam_log_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/operation.rb b/lib/mongo/operation.rb index 2c494bffba..b9b24968ab 100644 --- a/lib/mongo/operation.rb +++ b/lib/mongo/operation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo/operation/context' require 'mongo/operation/result' diff --git a/lib/mongo/operation/aggregate.rb b/lib/mongo/operation/aggregate.rb index 98084df678..1caf54650b 100644 --- a/lib/mongo/operation/aggregate.rb +++ b/lib/mongo/operation/aggregate.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/aggregate/op_msg.rb b/lib/mongo/operation/aggregate/op_msg.rb index 8bd13d1476..67574e18a8 100644 --- a/lib/mongo/operation/aggregate/op_msg.rb +++ b/lib/mongo/operation/aggregate/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/aggregate/result.rb b/lib/mongo/operation/aggregate/result.rb index 2acc3f084c..72d1a56412 100644 --- a/lib/mongo/operation/aggregate/result.rb +++ b/lib/mongo/operation/aggregate/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/collections_info.rb b/lib/mongo/operation/collections_info.rb index 370334f2a7..52c16259b5 100644 --- a/lib/mongo/operation/collections_info.rb +++ b/lib/mongo/operation/collections_info.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/collections_info/result.rb b/lib/mongo/operation/collections_info/result.rb index 0b9946fa40..4617dce1f3 100644 --- a/lib/mongo/operation/collections_info/result.rb +++ b/lib/mongo/operation/collections_info/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/command.rb b/lib/mongo/operation/command.rb index 998f58899b..3836f1849b 100644 --- a/lib/mongo/operation/command.rb +++ b/lib/mongo/operation/command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/command/op_msg.rb b/lib/mongo/operation/command/op_msg.rb index 5d53955552..1cfa17a4a4 100644 --- a/lib/mongo/operation/command/op_msg.rb +++ b/lib/mongo/operation/command/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/context.rb b/lib/mongo/operation/context.rb index 39654385a7..b7cae91d5a 100644 --- a/lib/mongo/operation/context.rb +++ b/lib/mongo/operation/context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/operation/count.rb b/lib/mongo/operation/count.rb index 92601c8086..27ba7a7253 100644 --- a/lib/mongo/operation/count.rb +++ b/lib/mongo/operation/count.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/count/op_msg.rb b/lib/mongo/operation/count/op_msg.rb index 241b3dfd38..23dd3cdd3d 100644 --- a/lib/mongo/operation/count/op_msg.rb +++ b/lib/mongo/operation/count/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/create.rb b/lib/mongo/operation/create.rb index af266f268a..efd10e1f7d 100644 --- a/lib/mongo/operation/create.rb +++ b/lib/mongo/operation/create.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/create/op_msg.rb b/lib/mongo/operation/create/op_msg.rb index c095cac602..bfb84e4818 100644 --- a/lib/mongo/operation/create/op_msg.rb +++ b/lib/mongo/operation/create/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/create_index.rb b/lib/mongo/operation/create_index.rb index 900f3025d1..aff12f1575 100644 --- a/lib/mongo/operation/create_index.rb +++ b/lib/mongo/operation/create_index.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/create_index/op_msg.rb b/lib/mongo/operation/create_index/op_msg.rb index 949caf7bfc..7debb16c53 100644 --- a/lib/mongo/operation/create_index/op_msg.rb +++ b/lib/mongo/operation/create_index/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/create_user.rb b/lib/mongo/operation/create_user.rb index e27ec9d61e..e0ff9b9551 100644 --- a/lib/mongo/operation/create_user.rb +++ b/lib/mongo/operation/create_user.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/create_user/op_msg.rb b/lib/mongo/operation/create_user/op_msg.rb index 6c5a4eb9c9..45608a6649 100644 --- a/lib/mongo/operation/create_user/op_msg.rb +++ b/lib/mongo/operation/create_user/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/delete.rb b/lib/mongo/operation/delete.rb index dc2c6f9e02..7d79c23665 100644 --- a/lib/mongo/operation/delete.rb +++ b/lib/mongo/operation/delete.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/delete/bulk_result.rb b/lib/mongo/operation/delete/bulk_result.rb index 496ada2870..ca919bbd6a 100644 --- a/lib/mongo/operation/delete/bulk_result.rb +++ b/lib/mongo/operation/delete/bulk_result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/delete/op_msg.rb b/lib/mongo/operation/delete/op_msg.rb index faca1c3475..f1435f9564 100644 --- a/lib/mongo/operation/delete/op_msg.rb +++ b/lib/mongo/operation/delete/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/delete/result.rb b/lib/mongo/operation/delete/result.rb index dc2d3c3817..e8668ce6ef 100644 --- a/lib/mongo/operation/delete/result.rb +++ b/lib/mongo/operation/delete/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/distinct.rb b/lib/mongo/operation/distinct.rb index 46995b4ef3..e852f85aa8 100644 --- a/lib/mongo/operation/distinct.rb +++ b/lib/mongo/operation/distinct.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/distinct/op_msg.rb b/lib/mongo/operation/distinct/op_msg.rb index 661dd7dda6..c16a6d11b4 100644 --- a/lib/mongo/operation/distinct/op_msg.rb +++ b/lib/mongo/operation/distinct/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/drop.rb b/lib/mongo/operation/drop.rb index b5b29a26b3..f7c2246fc7 100644 --- a/lib/mongo/operation/drop.rb +++ b/lib/mongo/operation/drop.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/drop/op_msg.rb b/lib/mongo/operation/drop/op_msg.rb index a2f2a521d4..503d766037 100644 --- a/lib/mongo/operation/drop/op_msg.rb +++ b/lib/mongo/operation/drop/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/drop_database.rb b/lib/mongo/operation/drop_database.rb index db00a738c3..c40106e806 100644 --- a/lib/mongo/operation/drop_database.rb +++ b/lib/mongo/operation/drop_database.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/drop_database/op_msg.rb b/lib/mongo/operation/drop_database/op_msg.rb index 2a13e93e2f..215c63e0c3 100644 --- a/lib/mongo/operation/drop_database/op_msg.rb +++ b/lib/mongo/operation/drop_database/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/drop_index.rb b/lib/mongo/operation/drop_index.rb index 493a911f38..8facef3014 100644 --- a/lib/mongo/operation/drop_index.rb +++ b/lib/mongo/operation/drop_index.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/drop_index/op_msg.rb b/lib/mongo/operation/drop_index/op_msg.rb index 112215435c..d29193340c 100644 --- a/lib/mongo/operation/drop_index/op_msg.rb +++ b/lib/mongo/operation/drop_index/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/explain.rb b/lib/mongo/operation/explain.rb index 50a61f2fcf..a91e043eb2 100644 --- a/lib/mongo/operation/explain.rb +++ b/lib/mongo/operation/explain.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/explain/op_msg.rb b/lib/mongo/operation/explain/op_msg.rb index 1500045e20..682f771c9f 100644 --- a/lib/mongo/operation/explain/op_msg.rb +++ b/lib/mongo/operation/explain/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/explain/result.rb b/lib/mongo/operation/explain/result.rb index 2ec7d9cbe7..c90bb2b35e 100644 --- a/lib/mongo/operation/explain/result.rb +++ b/lib/mongo/operation/explain/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/find.rb b/lib/mongo/operation/find.rb index 533a6e00a6..00c429d1b0 100644 --- a/lib/mongo/operation/find.rb +++ b/lib/mongo/operation/find.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/find/builder.rb b/lib/mongo/operation/find/builder.rb index 552a93d35e..9c526f04ad 100644 --- a/lib/mongo/operation/find/builder.rb +++ b/lib/mongo/operation/find/builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/find/builder/command.rb b/lib/mongo/operation/find/builder/command.rb index 1f4494c92e..ce8eb2358a 100644 --- a/lib/mongo/operation/find/builder/command.rb +++ b/lib/mongo/operation/find/builder/command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/find/builder/flags.rb b/lib/mongo/operation/find/builder/flags.rb index c7820d61f9..115c45faab 100644 --- a/lib/mongo/operation/find/builder/flags.rb +++ b/lib/mongo/operation/find/builder/flags.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/find/builder/modifiers.rb b/lib/mongo/operation/find/builder/modifiers.rb index 4aec9389f5..c70a5f5730 100644 --- a/lib/mongo/operation/find/builder/modifiers.rb +++ b/lib/mongo/operation/find/builder/modifiers.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/find/op_msg.rb b/lib/mongo/operation/find/op_msg.rb index 9ebe85c60e..f29ec4686b 100644 --- a/lib/mongo/operation/find/op_msg.rb +++ b/lib/mongo/operation/find/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/find/result.rb b/lib/mongo/operation/find/result.rb index d22df75f42..9bae6d6914 100644 --- a/lib/mongo/operation/find/result.rb +++ b/lib/mongo/operation/find/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/get_more.rb b/lib/mongo/operation/get_more.rb index b7ae7ae5c7..005ab181a4 100644 --- a/lib/mongo/operation/get_more.rb +++ b/lib/mongo/operation/get_more.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/get_more/command_builder.rb b/lib/mongo/operation/get_more/command_builder.rb index 932f43f30b..5b14c6fdc3 100644 --- a/lib/mongo/operation/get_more/command_builder.rb +++ b/lib/mongo/operation/get_more/command_builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/operation/get_more/op_msg.rb b/lib/mongo/operation/get_more/op_msg.rb index c0a4dda3d4..cad0ab3bbe 100644 --- a/lib/mongo/operation/get_more/op_msg.rb +++ b/lib/mongo/operation/get_more/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/get_more/result.rb b/lib/mongo/operation/get_more/result.rb index 3f014c7cfe..df84c458e6 100644 --- a/lib/mongo/operation/get_more/result.rb +++ b/lib/mongo/operation/get_more/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/indexes.rb b/lib/mongo/operation/indexes.rb index 779bee85aa..3e8a1e763c 100644 --- a/lib/mongo/operation/indexes.rb +++ b/lib/mongo/operation/indexes.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/indexes/op_msg.rb b/lib/mongo/operation/indexes/op_msg.rb index 9b55b31561..3e471e0f20 100644 --- a/lib/mongo/operation/indexes/op_msg.rb +++ b/lib/mongo/operation/indexes/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/indexes/result.rb b/lib/mongo/operation/indexes/result.rb index 0e62d0f119..6211f6b560 100644 --- a/lib/mongo/operation/indexes/result.rb +++ b/lib/mongo/operation/indexes/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/insert.rb b/lib/mongo/operation/insert.rb index 948351e5b0..5f62e58ce9 100644 --- a/lib/mongo/operation/insert.rb +++ b/lib/mongo/operation/insert.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/insert/bulk_result.rb b/lib/mongo/operation/insert/bulk_result.rb index e7faccaf36..b2bf932497 100644 --- a/lib/mongo/operation/insert/bulk_result.rb +++ b/lib/mongo/operation/insert/bulk_result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/insert/op_msg.rb b/lib/mongo/operation/insert/op_msg.rb index f866ee56ed..7ab863e6af 100644 --- a/lib/mongo/operation/insert/op_msg.rb +++ b/lib/mongo/operation/insert/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/insert/result.rb b/lib/mongo/operation/insert/result.rb index 6600fb2ca2..05995de079 100644 --- a/lib/mongo/operation/insert/result.rb +++ b/lib/mongo/operation/insert/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/kill_cursors.rb b/lib/mongo/operation/kill_cursors.rb index 17c5b1742c..b131a5b104 100644 --- a/lib/mongo/operation/kill_cursors.rb +++ b/lib/mongo/operation/kill_cursors.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/kill_cursors/command_builder.rb b/lib/mongo/operation/kill_cursors/command_builder.rb index 25e8356d8a..6899fb1df5 100644 --- a/lib/mongo/operation/kill_cursors/command_builder.rb +++ b/lib/mongo/operation/kill_cursors/command_builder.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/operation/kill_cursors/op_msg.rb b/lib/mongo/operation/kill_cursors/op_msg.rb index 5744c72d45..bbe5df1a08 100644 --- a/lib/mongo/operation/kill_cursors/op_msg.rb +++ b/lib/mongo/operation/kill_cursors/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/list_collections.rb b/lib/mongo/operation/list_collections.rb index 9a2b81c440..d2c7456ae3 100644 --- a/lib/mongo/operation/list_collections.rb +++ b/lib/mongo/operation/list_collections.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/list_collections/op_msg.rb b/lib/mongo/operation/list_collections/op_msg.rb index 20b174f5f4..a65491324d 100644 --- a/lib/mongo/operation/list_collections/op_msg.rb +++ b/lib/mongo/operation/list_collections/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/list_collections/result.rb b/lib/mongo/operation/list_collections/result.rb index 1ecacb054b..9b45e8b4fb 100644 --- a/lib/mongo/operation/list_collections/result.rb +++ b/lib/mongo/operation/list_collections/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/map_reduce.rb b/lib/mongo/operation/map_reduce.rb index 90f2af74f6..85b4028577 100644 --- a/lib/mongo/operation/map_reduce.rb +++ b/lib/mongo/operation/map_reduce.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/map_reduce/op_msg.rb b/lib/mongo/operation/map_reduce/op_msg.rb index 80698032c4..a331c20b93 100644 --- a/lib/mongo/operation/map_reduce/op_msg.rb +++ b/lib/mongo/operation/map_reduce/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/map_reduce/result.rb b/lib/mongo/operation/map_reduce/result.rb index 0417ae7f7b..8959a99d15 100644 --- a/lib/mongo/operation/map_reduce/result.rb +++ b/lib/mongo/operation/map_reduce/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/op_msg_base.rb b/lib/mongo/operation/op_msg_base.rb index 938a473f10..5f00d42aec 100644 --- a/lib/mongo/operation/op_msg_base.rb +++ b/lib/mongo/operation/op_msg_base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/parallel_scan.rb b/lib/mongo/operation/parallel_scan.rb index 3e9aea4106..a3fe479f10 100644 --- a/lib/mongo/operation/parallel_scan.rb +++ b/lib/mongo/operation/parallel_scan.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/parallel_scan/op_msg.rb b/lib/mongo/operation/parallel_scan/op_msg.rb index 672f69907a..a0702549f6 100644 --- a/lib/mongo/operation/parallel_scan/op_msg.rb +++ b/lib/mongo/operation/parallel_scan/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/parallel_scan/result.rb b/lib/mongo/operation/parallel_scan/result.rb index bbd1323fc8..ffbc703f12 100644 --- a/lib/mongo/operation/parallel_scan/result.rb +++ b/lib/mongo/operation/parallel_scan/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/remove_user.rb b/lib/mongo/operation/remove_user.rb index e704a02ed8..b8ef17ca7e 100644 --- a/lib/mongo/operation/remove_user.rb +++ b/lib/mongo/operation/remove_user.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/remove_user/op_msg.rb b/lib/mongo/operation/remove_user/op_msg.rb index f85aa2c7a9..bfb1e544bc 100644 --- a/lib/mongo/operation/remove_user/op_msg.rb +++ b/lib/mongo/operation/remove_user/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/result.rb b/lib/mongo/operation/result.rb index 1394a19cb9..d6989444b8 100644 --- a/lib/mongo/operation/result.rb +++ b/lib/mongo/operation/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/bypass_document_validation.rb b/lib/mongo/operation/shared/bypass_document_validation.rb index 0a2a8466eb..7bdfe4eb50 100644 --- a/lib/mongo/operation/shared/bypass_document_validation.rb +++ b/lib/mongo/operation/shared/bypass_document_validation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/causal_consistency_supported.rb b/lib/mongo/operation/shared/causal_consistency_supported.rb index 9e6dcf379a..6485f5cf66 100644 --- a/lib/mongo/operation/shared/causal_consistency_supported.rb +++ b/lib/mongo/operation/shared/causal_consistency_supported.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/executable.rb b/lib/mongo/operation/shared/executable.rb index b8dc64b1ad..229479e24b 100644 --- a/lib/mongo/operation/shared/executable.rb +++ b/lib/mongo/operation/shared/executable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/executable_no_validate.rb b/lib/mongo/operation/shared/executable_no_validate.rb index 7a27dba65d..8609fa827a 100644 --- a/lib/mongo/operation/shared/executable_no_validate.rb +++ b/lib/mongo/operation/shared/executable_no_validate.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/executable_transaction_label.rb b/lib/mongo/operation/shared/executable_transaction_label.rb index f62f29b7cf..42c7f4df5c 100644 --- a/lib/mongo/operation/shared/executable_transaction_label.rb +++ b/lib/mongo/operation/shared/executable_transaction_label.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/idable.rb b/lib/mongo/operation/shared/idable.rb index 7aac498ebc..eaf570b042 100644 --- a/lib/mongo/operation/shared/idable.rb +++ b/lib/mongo/operation/shared/idable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/limited.rb b/lib/mongo/operation/shared/limited.rb index 67303de299..a666ff6fe3 100644 --- a/lib/mongo/operation/shared/limited.rb +++ b/lib/mongo/operation/shared/limited.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/object_id_generator.rb b/lib/mongo/operation/shared/object_id_generator.rb index 98faa999ca..930f8e2922 100644 --- a/lib/mongo/operation/shared/object_id_generator.rb +++ b/lib/mongo/operation/shared/object_id_generator.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/op_msg_executable.rb b/lib/mongo/operation/shared/op_msg_executable.rb index 345e905f13..a97b8fd48d 100644 --- a/lib/mongo/operation/shared/op_msg_executable.rb +++ b/lib/mongo/operation/shared/op_msg_executable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/polymorphic_lookup.rb b/lib/mongo/operation/shared/polymorphic_lookup.rb index 0aa41fd2fd..52c4cc8413 100644 --- a/lib/mongo/operation/shared/polymorphic_lookup.rb +++ b/lib/mongo/operation/shared/polymorphic_lookup.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/polymorphic_result.rb b/lib/mongo/operation/shared/polymorphic_result.rb index dcce54eb34..62598241a5 100644 --- a/lib/mongo/operation/shared/polymorphic_result.rb +++ b/lib/mongo/operation/shared/polymorphic_result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/read_preference_supported.rb b/lib/mongo/operation/shared/read_preference_supported.rb index ae3af1574f..dbd8c4ad23 100644 --- a/lib/mongo/operation/shared/read_preference_supported.rb +++ b/lib/mongo/operation/shared/read_preference_supported.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/response_handling.rb b/lib/mongo/operation/shared/response_handling.rb index 9ca4349833..af4c83ca75 100644 --- a/lib/mongo/operation/shared/response_handling.rb +++ b/lib/mongo/operation/shared/response_handling.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/result/aggregatable.rb b/lib/mongo/operation/shared/result/aggregatable.rb index 21062c4bf7..eb292a99e1 100644 --- a/lib/mongo/operation/shared/result/aggregatable.rb +++ b/lib/mongo/operation/shared/result/aggregatable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/result/use_legacy_error_parser.rb b/lib/mongo/operation/shared/result/use_legacy_error_parser.rb index a16e69911c..a1208a3caa 100644 --- a/lib/mongo/operation/shared/result/use_legacy_error_parser.rb +++ b/lib/mongo/operation/shared/result/use_legacy_error_parser.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/sessions_supported.rb b/lib/mongo/operation/shared/sessions_supported.rb index de40835643..e8d59c23d4 100644 --- a/lib/mongo/operation/shared/sessions_supported.rb +++ b/lib/mongo/operation/shared/sessions_supported.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/specifiable.rb b/lib/mongo/operation/shared/specifiable.rb index 7c358d82ee..0a19dba1f6 100644 --- a/lib/mongo/operation/shared/specifiable.rb +++ b/lib/mongo/operation/shared/specifiable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/validatable.rb b/lib/mongo/operation/shared/validatable.rb index 70b5557353..866de6dfb0 100644 --- a/lib/mongo/operation/shared/validatable.rb +++ b/lib/mongo/operation/shared/validatable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/write.rb b/lib/mongo/operation/shared/write.rb index 09985989ec..68214fac36 100644 --- a/lib/mongo/operation/shared/write.rb +++ b/lib/mongo/operation/shared/write.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/shared/write_concern_supported.rb b/lib/mongo/operation/shared/write_concern_supported.rb index 735b32e5d9..34acd10c27 100644 --- a/lib/mongo/operation/shared/write_concern_supported.rb +++ b/lib/mongo/operation/shared/write_concern_supported.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/update.rb b/lib/mongo/operation/update.rb index 9b4aa12602..70fe5861b9 100644 --- a/lib/mongo/operation/update.rb +++ b/lib/mongo/operation/update.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/update/bulk_result.rb b/lib/mongo/operation/update/bulk_result.rb index 27c2679cdb..58ce8e3974 100644 --- a/lib/mongo/operation/update/bulk_result.rb +++ b/lib/mongo/operation/update/bulk_result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/update/op_msg.rb b/lib/mongo/operation/update/op_msg.rb index 37a83491a2..23ac1f4b4b 100644 --- a/lib/mongo/operation/update/op_msg.rb +++ b/lib/mongo/operation/update/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/update/result.rb b/lib/mongo/operation/update/result.rb index d6b8a4f62a..da8b09fe49 100644 --- a/lib/mongo/operation/update/result.rb +++ b/lib/mongo/operation/update/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/update_user.rb b/lib/mongo/operation/update_user.rb index 598f1f1552..993a2d6ef3 100644 --- a/lib/mongo/operation/update_user.rb +++ b/lib/mongo/operation/update_user.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/update_user/op_msg.rb b/lib/mongo/operation/update_user/op_msg.rb index 38da22728c..c388897827 100644 --- a/lib/mongo/operation/update_user/op_msg.rb +++ b/lib/mongo/operation/update_user/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/users_info.rb b/lib/mongo/operation/users_info.rb index 3a1e63422c..11fbf3c76c 100644 --- a/lib/mongo/operation/users_info.rb +++ b/lib/mongo/operation/users_info.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/users_info/op_msg.rb b/lib/mongo/operation/users_info/op_msg.rb index cac7291cfb..afad1762d8 100644 --- a/lib/mongo/operation/users_info/op_msg.rb +++ b/lib/mongo/operation/users_info/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/users_info/result.rb b/lib/mongo/operation/users_info/result.rb index ca5186160b..69939601de 100644 --- a/lib/mongo/operation/users_info/result.rb +++ b/lib/mongo/operation/users_info/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/operation/write_command.rb b/lib/mongo/operation/write_command.rb index 6e322e4d94..60b644a66f 100644 --- a/lib/mongo/operation/write_command.rb +++ b/lib/mongo/operation/write_command.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/operation/write_command/op_msg.rb b/lib/mongo/operation/write_command/op_msg.rb index bfb0d3f5dc..f158398c4f 100644 --- a/lib/mongo/operation/write_command/op_msg.rb +++ b/lib/mongo/operation/write_command/op_msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/options.rb b/lib/mongo/options.rb index 41e46e8751..28bad13681 100644 --- a/lib/mongo/options.rb +++ b/lib/mongo/options.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/options/mapper.rb b/lib/mongo/options/mapper.rb index 1c05a5af32..3f01861e77 100644 --- a/lib/mongo/options/mapper.rb +++ b/lib/mongo/options/mapper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/options/redacted.rb b/lib/mongo/options/redacted.rb index a2b1ba2dc7..add2650cf5 100644 --- a/lib/mongo/options/redacted.rb +++ b/lib/mongo/options/redacted.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol.rb b/lib/mongo/protocol.rb index 4289a38835..a9503589e9 100644 --- a/lib/mongo/protocol.rb +++ b/lib/mongo/protocol.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Wire Protocol Base require 'mongo/protocol/serializers' diff --git a/lib/mongo/protocol/bit_vector.rb b/lib/mongo/protocol/bit_vector.rb index 998d3d1e42..d6835a953a 100644 --- a/lib/mongo/protocol/bit_vector.rb +++ b/lib/mongo/protocol/bit_vector.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/caching_hash.rb b/lib/mongo/protocol/caching_hash.rb index 2087d2df5e..375b719c8b 100644 --- a/lib/mongo/protocol/caching_hash.rb +++ b/lib/mongo/protocol/caching_hash.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2022 MongoDB Inc. # diff --git a/lib/mongo/protocol/compressed.rb b/lib/mongo/protocol/compressed.rb index a7dced3c7b..fddeeb0a76 100644 --- a/lib/mongo/protocol/compressed.rb +++ b/lib/mongo/protocol/compressed.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/get_more.rb b/lib/mongo/protocol/get_more.rb index c802c6a391..9c5d4055ec 100644 --- a/lib/mongo/protocol/get_more.rb +++ b/lib/mongo/protocol/get_more.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/kill_cursors.rb b/lib/mongo/protocol/kill_cursors.rb index 8a9f311a0f..71d758a916 100644 --- a/lib/mongo/protocol/kill_cursors.rb +++ b/lib/mongo/protocol/kill_cursors.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/message.rb b/lib/mongo/protocol/message.rb index 438f740454..e0baf5ed5a 100644 --- a/lib/mongo/protocol/message.rb +++ b/lib/mongo/protocol/message.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/msg.rb b/lib/mongo/protocol/msg.rb index 194de8ea6f..73df7a31b6 100644 --- a/lib/mongo/protocol/msg.rb +++ b/lib/mongo/protocol/msg.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/query.rb b/lib/mongo/protocol/query.rb index 49988cb903..e4be261c16 100644 --- a/lib/mongo/protocol/query.rb +++ b/lib/mongo/protocol/query.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/registry.rb b/lib/mongo/protocol/registry.rb index 6b23d5b6a3..475906ea75 100644 --- a/lib/mongo/protocol/registry.rb +++ b/lib/mongo/protocol/registry.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2009-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/reply.rb b/lib/mongo/protocol/reply.rb index 77cb9cb5f7..68c76a143e 100644 --- a/lib/mongo/protocol/reply.rb +++ b/lib/mongo/protocol/reply.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/protocol/serializers.rb b/lib/mongo/protocol/serializers.rb index cc1c40bd1b..648c469c10 100644 --- a/lib/mongo/protocol/serializers.rb +++ b/lib/mongo/protocol/serializers.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/query_cache.rb b/lib/mongo/query_cache.rb index 07eea1055d..079fde51b2 100644 --- a/lib/mongo/query_cache.rb +++ b/lib/mongo/query_cache.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB, Inc. # diff --git a/lib/mongo/retryable.rb b/lib/mongo/retryable.rb index 3da64c1d79..c6330bf486 100644 --- a/lib/mongo/retryable.rb +++ b/lib/mongo/retryable.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/retryable/base_worker.rb b/lib/mongo/retryable/base_worker.rb index 59ac37db06..d3f775abe8 100644 --- a/lib/mongo/retryable/base_worker.rb +++ b/lib/mongo/retryable/base_worker.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2023 MongoDB Inc. # diff --git a/lib/mongo/retryable/read_worker.rb b/lib/mongo/retryable/read_worker.rb index 7036bf4b88..763fa0f75b 100644 --- a/lib/mongo/retryable/read_worker.rb +++ b/lib/mongo/retryable/read_worker.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2023 MongoDB Inc. # diff --git a/lib/mongo/retryable/write_worker.rb b/lib/mongo/retryable/write_worker.rb index cf0d915185..c21265ed56 100644 --- a/lib/mongo/retryable/write_worker.rb +++ b/lib/mongo/retryable/write_worker.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2023 MongoDB Inc. # diff --git a/lib/mongo/semaphore.rb b/lib/mongo/semaphore.rb index e3e99f0f75..2aec6c05bc 100644 --- a/lib/mongo/semaphore.rb +++ b/lib/mongo/semaphore.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. # diff --git a/lib/mongo/server.rb b/lib/mongo/server.rb index 297ed4859d..d87e70a4f5 100644 --- a/lib/mongo/server.rb +++ b/lib/mongo/server.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server/app_metadata.rb b/lib/mongo/server/app_metadata.rb index 8d81dd1f2d..5d47a1d438 100644 --- a/lib/mongo/server/app_metadata.rb +++ b/lib/mongo/server/app_metadata.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # diff --git a/lib/mongo/server/connection.rb b/lib/mongo/server/connection.rb index b8159b3263..349948b61d 100644 --- a/lib/mongo/server/connection.rb +++ b/lib/mongo/server/connection.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server/connection_base.rb b/lib/mongo/server/connection_base.rb index f6bb844a50..709f1e920d 100644 --- a/lib/mongo/server/connection_base.rb +++ b/lib/mongo/server/connection_base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/server/connection_common.rb b/lib/mongo/server/connection_common.rb index 7b98bf9ba8..82bc91d75e 100644 --- a/lib/mongo/server/connection_common.rb +++ b/lib/mongo/server/connection_common.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index df924262da..543ac6abde 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server/connection_pool/generation_manager.rb b/lib/mongo/server/connection_pool/generation_manager.rb index 578e87b4c7..b3314158ef 100644 --- a/lib/mongo/server/connection_pool/generation_manager.rb +++ b/lib/mongo/server/connection_pool/generation_manager.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/server/connection_pool/populator.rb b/lib/mongo/server/connection_pool/populator.rb index c1892abe69..465ac79632 100644 --- a/lib/mongo/server/connection_pool/populator.rb +++ b/lib/mongo/server/connection_pool/populator.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/server/description.rb b/lib/mongo/server/description.rb index aa66799a47..bfd7905ad9 100644 --- a/lib/mongo/server/description.rb +++ b/lib/mongo/server/description.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server/description/features.rb b/lib/mongo/server/description/features.rb index 0f1deafca4..f3bafc308b 100644 --- a/lib/mongo/server/description/features.rb +++ b/lib/mongo/server/description/features.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server/description/load_balancer.rb b/lib/mongo/server/description/load_balancer.rb index dc7c90d3e7..df94da15f8 100644 --- a/lib/mongo/server/description/load_balancer.rb +++ b/lib/mongo/server/description/load_balancer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2021 MongoDB Inc. # diff --git a/lib/mongo/server/monitor.rb b/lib/mongo/server/monitor.rb index f753eb7ff1..c2ae278f1e 100644 --- a/lib/mongo/server/monitor.rb +++ b/lib/mongo/server/monitor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server/monitor/app_metadata.rb b/lib/mongo/server/monitor/app_metadata.rb index 7591fa3e21..4be98d6be9 100644 --- a/lib/mongo/server/monitor/app_metadata.rb +++ b/lib/mongo/server/monitor/app_metadata.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. diff --git a/lib/mongo/server/monitor/connection.rb b/lib/mongo/server/monitor/connection.rb index 98823b7a48..2e59e6d7f7 100644 --- a/lib/mongo/server/monitor/connection.rb +++ b/lib/mongo/server/monitor/connection.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/server/pending_connection.rb b/lib/mongo/server/pending_connection.rb index 8f3c0700e8..3bb53f4bec 100644 --- a/lib/mongo/server/pending_connection.rb +++ b/lib/mongo/server/pending_connection.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/server/push_monitor.rb b/lib/mongo/server/push_monitor.rb index b4cdd3361b..8af6453e93 100644 --- a/lib/mongo/server/push_monitor.rb +++ b/lib/mongo/server/push_monitor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/server/push_monitor/connection.rb b/lib/mongo/server/push_monitor/connection.rb index c9b3c4fa89..e8cd47b318 100644 --- a/lib/mongo/server/push_monitor/connection.rb +++ b/lib/mongo/server/push_monitor/connection.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/server/round_trip_time_averager.rb b/lib/mongo/server/round_trip_time_averager.rb index 724cb786cb..1634691d54 100644 --- a/lib/mongo/server/round_trip_time_averager.rb +++ b/lib/mongo/server/round_trip_time_averager.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2018-2020 MongoDB Inc. diff --git a/lib/mongo/server_selector.rb b/lib/mongo/server_selector.rb index 45216c6f30..7e07468d8c 100644 --- a/lib/mongo/server_selector.rb +++ b/lib/mongo/server_selector.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server_selector/base.rb b/lib/mongo/server_selector/base.rb index 794c354732..b883785129 100644 --- a/lib/mongo/server_selector/base.rb +++ b/lib/mongo/server_selector/base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/server_selector/nearest.rb b/lib/mongo/server_selector/nearest.rb index c5b8336826..c4db1e1061 100644 --- a/lib/mongo/server_selector/nearest.rb +++ b/lib/mongo/server_selector/nearest.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server_selector/primary.rb b/lib/mongo/server_selector/primary.rb index 499df273f5..5ece9024ca 100644 --- a/lib/mongo/server_selector/primary.rb +++ b/lib/mongo/server_selector/primary.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server_selector/primary_preferred.rb b/lib/mongo/server_selector/primary_preferred.rb index 154d4f083a..28e7c59756 100644 --- a/lib/mongo/server_selector/primary_preferred.rb +++ b/lib/mongo/server_selector/primary_preferred.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server_selector/secondary.rb b/lib/mongo/server_selector/secondary.rb index ffb40f649c..310bbd597b 100644 --- a/lib/mongo/server_selector/secondary.rb +++ b/lib/mongo/server_selector/secondary.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/server_selector/secondary_preferred.rb b/lib/mongo/server_selector/secondary_preferred.rb index ca29984ca0..8b14eaddd9 100644 --- a/lib/mongo/server_selector/secondary_preferred.rb +++ b/lib/mongo/server_selector/secondary_preferred.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/session.rb b/lib/mongo/session.rb index 8b6d31ec35..bc12896af0 100644 --- a/lib/mongo/session.rb +++ b/lib/mongo/session.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/session/server_session.rb b/lib/mongo/session/server_session.rb index f7323e6b02..6d0410903a 100644 --- a/lib/mongo/session/server_session.rb +++ b/lib/mongo/session/server_session.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/session/session_pool.rb b/lib/mongo/session/session_pool.rb index 8211aa88a0..e89d634c31 100644 --- a/lib/mongo/session/session_pool.rb +++ b/lib/mongo/session/session_pool.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/socket.rb b/lib/mongo/socket.rb index acca1a0e00..62e3ef464e 100644 --- a/lib/mongo/socket.rb +++ b/lib/mongo/socket.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/socket/ocsp_cache.rb b/lib/mongo/socket/ocsp_cache.rb index d1279be54a..893e55150e 100644 --- a/lib/mongo/socket/ocsp_cache.rb +++ b/lib/mongo/socket/ocsp_cache.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/socket/ocsp_verifier.rb b/lib/mongo/socket/ocsp_verifier.rb index 6327c5c298..bae8936b9a 100644 --- a/lib/mongo/socket/ocsp_verifier.rb +++ b/lib/mongo/socket/ocsp_verifier.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/socket/ssl.rb b/lib/mongo/socket/ssl.rb index 0da6127956..aef137a5a4 100644 --- a/lib/mongo/socket/ssl.rb +++ b/lib/mongo/socket/ssl.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/socket/tcp.rb b/lib/mongo/socket/tcp.rb index 1456a4dc44..155c4ffc4a 100644 --- a/lib/mongo/socket/tcp.rb +++ b/lib/mongo/socket/tcp.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/socket/unix.rb b/lib/mongo/socket/unix.rb index 1e294558e7..1fa5f27ff0 100644 --- a/lib/mongo/socket/unix.rb +++ b/lib/mongo/socket/unix.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/srv.rb b/lib/mongo/srv.rb index 56acbfa60b..063347cdc8 100644 --- a/lib/mongo/srv.rb +++ b/lib/mongo/srv.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/lib/mongo/srv/monitor.rb b/lib/mongo/srv/monitor.rb index 69a9a98942..71911acdb2 100644 --- a/lib/mongo/srv/monitor.rb +++ b/lib/mongo/srv/monitor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/srv/resolver.rb b/lib/mongo/srv/resolver.rb index 69c3d39c13..b53ce56fcd 100644 --- a/lib/mongo/srv/resolver.rb +++ b/lib/mongo/srv/resolver.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/srv/result.rb b/lib/mongo/srv/result.rb index 252c84c4b2..318011914f 100644 --- a/lib/mongo/srv/result.rb +++ b/lib/mongo/srv/result.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/timeout.rb b/lib/mongo/timeout.rb index 6871dbadb7..86a7cfe557 100644 --- a/lib/mongo/timeout.rb +++ b/lib/mongo/timeout.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/topology_version.rb b/lib/mongo/topology_version.rb index b067ac4b63..bef1ea2304 100644 --- a/lib/mongo/topology_version.rb +++ b/lib/mongo/topology_version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/uri.rb b/lib/mongo/uri.rb index c386b26bfa..899ec94d0a 100644 --- a/lib/mongo/uri.rb +++ b/lib/mongo/uri.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/uri/options_mapper.rb b/lib/mongo/uri/options_mapper.rb index 1f63ec49f2..b32149f84b 100644 --- a/lib/mongo/uri/options_mapper.rb +++ b/lib/mongo/uri/options_mapper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/uri/srv_protocol.rb b/lib/mongo/uri/srv_protocol.rb index d7e2691032..164bbf4210 100644 --- a/lib/mongo/uri/srv_protocol.rb +++ b/lib/mongo/uri/srv_protocol.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2017-2020 MongoDB Inc. # diff --git a/lib/mongo/utils.rb b/lib/mongo/utils.rb index 55e0729810..8e884cc0c7 100644 --- a/lib/mongo/utils.rb +++ b/lib/mongo/utils.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # diff --git a/lib/mongo/version.rb b/lib/mongo/version.rb index c32b2a09b3..49641f39a3 100644 --- a/lib/mongo/version.rb +++ b/lib/mongo/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/write_concern.rb b/lib/mongo/write_concern.rb index f2990ae2c7..7f5841ece0 100644 --- a/lib/mongo/write_concern.rb +++ b/lib/mongo/write_concern.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/lib/mongo/write_concern/acknowledged.rb b/lib/mongo/write_concern/acknowledged.rb index af536d6994..fc10c5ba2a 100644 --- a/lib/mongo/write_concern/acknowledged.rb +++ b/lib/mongo/write_concern/acknowledged.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/write_concern/base.rb b/lib/mongo/write_concern/base.rb index 4178f74b15..e4da7fa3e8 100644 --- a/lib/mongo/write_concern/base.rb +++ b/lib/mongo/write_concern/base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/lib/mongo/write_concern/unacknowledged.rb b/lib/mongo/write_concern/unacknowledged.rb index 722b7685b7..e0c32685d4 100644 --- a/lib/mongo/write_concern/unacknowledged.rb +++ b/lib/mongo/write_concern/unacknowledged.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/mongo.gemspec b/mongo.gemspec index 0b22890c09..0226011936 100644 --- a/mongo.gemspec +++ b/mongo.gemspec @@ -1,3 +1,6 @@ +# frozen_string_literal: true +# rubocop:todo all + lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'mongo/version' diff --git a/profile/benchmark_21.rb b/profile/benchmark_21.rb index 05dbad5279..e03c16d565 100644 --- a/profile/benchmark_21.rb +++ b/profile/benchmark_21.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'benchmark' diff --git a/profile/benchmarking.rb b/profile/benchmarking.rb index 5c991ea926..a054b3906b 100644 --- a/profile/benchmarking.rb +++ b/profile/benchmarking.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/profile/benchmarking/helper.rb b/profile/benchmarking/helper.rb index a9b31158be..11d9f670ab 100644 --- a/profile/benchmarking/helper.rb +++ b/profile/benchmarking/helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo diff --git a/profile/benchmarking/micro.rb b/profile/benchmarking/micro.rb index f2aa0877a9..2f560bb310 100644 --- a/profile/benchmarking/micro.rb +++ b/profile/benchmarking/micro.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/profile/benchmarking/multi_doc.rb b/profile/benchmarking/multi_doc.rb index 6d59fdcdb7..545d5691d1 100644 --- a/profile/benchmarking/multi_doc.rb +++ b/profile/benchmarking/multi_doc.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/profile/benchmarking/parallel.rb b/profile/benchmarking/parallel.rb index c4dd71920b..92a92132ae 100644 --- a/profile/benchmarking/parallel.rb +++ b/profile/benchmarking/parallel.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/profile/benchmarking/single_doc.rb b/profile/benchmarking/single_doc.rb index 6f09558b40..a66635c542 100644 --- a/profile/benchmarking/single_doc.rb +++ b/profile/benchmarking/single_doc.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # diff --git a/profile/profile.rb b/profile/profile.rb index 7d1056c7a7..26bf770752 100644 --- a/profile/profile.rb +++ b/profile/profile.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'ruby-prof' require 'mongo' diff --git a/spec/atlas/atlas_connectivity_spec.rb b/spec/atlas/atlas_connectivity_spec.rb index 78ae835b81..9c519dacda 100644 --- a/spec/atlas/atlas_connectivity_spec.rb +++ b/spec/atlas/atlas_connectivity_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/atlas/operations_spec.rb b/spec/atlas/operations_spec.rb index 080d75dfd7..8ac9495e67 100644 --- a/spec/atlas/operations_spec.rb +++ b/spec/atlas/operations_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/integration/auth_spec.rb b/spec/integration/auth_spec.rb index 8217ccc6e7..a04358e650 100644 --- a/spec/integration/auth_spec.rb +++ b/spec/integration/auth_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/awaited_ismaster_spec.rb b/spec/integration/awaited_ismaster_spec.rb index 5b90fd00f4..440a390545 100644 --- a/spec/integration/awaited_ismaster_spec.rb +++ b/spec/integration/awaited_ismaster_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/aws_auth_credentials_cache_spec.rb b/spec/integration/aws_auth_credentials_cache_spec.rb index 78e521e0d3..2756ab07d2 100644 --- a/spec/integration/aws_auth_credentials_cache_spec.rb +++ b/spec/integration/aws_auth_credentials_cache_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/aws_auth_request_spec.rb b/spec/integration/aws_auth_request_spec.rb index 13241d97d1..258bd74489 100644 --- a/spec/integration/aws_auth_request_spec.rb +++ b/spec/integration/aws_auth_request_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'net/http' diff --git a/spec/integration/aws_credentials_retriever_spec.rb b/spec/integration/aws_credentials_retriever_spec.rb index 0557da5f99..f891456b48 100644 --- a/spec/integration/aws_credentials_retriever_spec.rb +++ b/spec/integration/aws_credentials_retriever_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/aws_utils' diff --git a/spec/integration/aws_lambda_examples_spec.rb b/spec/integration/aws_lambda_examples_spec.rb index 9a129b1ed0..d31e43b129 100644 --- a/spec/integration/aws_lambda_examples_spec.rb +++ b/spec/integration/aws_lambda_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require "spec_helper" diff --git a/spec/integration/bson_symbol_spec.rb b/spec/integration/bson_symbol_spec.rb index 8bbcd3dfb1..6612383e9a 100644 --- a/spec/integration/bson_symbol_spec.rb +++ b/spec/integration/bson_symbol_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/bulk_insert_spec.rb b/spec/integration/bulk_insert_spec.rb index cc7b4d9251..9d2147d89d 100644 --- a/spec/integration/bulk_insert_spec.rb +++ b/spec/integration/bulk_insert_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/bulk_write_error_message_spec.rb b/spec/integration/bulk_write_error_message_spec.rb index 8c93dc2643..7f00f20774 100644 --- a/spec/integration/bulk_write_error_message_spec.rb +++ b/spec/integration/bulk_write_error_message_spec.rb @@ -1,3 +1,4 @@ +# rubocop:todo all require 'spec_helper' describe 'BulkWriteError message' do diff --git a/spec/integration/bulk_write_spec.rb b/spec/integration/bulk_write_spec.rb index 7406a88497..9ec16e9ea9 100644 --- a/spec/integration/bulk_write_spec.rb +++ b/spec/integration/bulk_write_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/change_stream_examples_spec.rb b/spec/integration/change_stream_examples_spec.rb index c880eeab26..8a383d3b40 100644 --- a/spec/integration/change_stream_examples_spec.rb +++ b/spec/integration/change_stream_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/change_stream_spec.rb b/spec/integration/change_stream_spec.rb index baefd1fa7b..da828b495c 100644 --- a/spec/integration/change_stream_spec.rb +++ b/spec/integration/change_stream_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/check_clean_slate_spec.rb b/spec/integration/check_clean_slate_spec.rb index bab4a9c43f..47b5427db0 100644 --- a/spec/integration/check_clean_slate_spec.rb +++ b/spec/integration/check_clean_slate_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/integration/client_authentication_options_spec.rb b/spec/integration/client_authentication_options_spec.rb index 3a6f8f85de..8971d12a4b 100644 --- a/spec/integration/client_authentication_options_spec.rb +++ b/spec/integration/client_authentication_options_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/integration/client_connectivity_spec.rb b/spec/integration/client_connectivity_spec.rb index bf841af546..3d4003797e 100644 --- a/spec/integration/client_connectivity_spec.rb +++ b/spec/integration/client_connectivity_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_construction_aws_auth_spec.rb b/spec/integration/client_construction_aws_auth_spec.rb index 75c2979e31..ef1adacb92 100644 --- a/spec/integration/client_construction_aws_auth_spec.rb +++ b/spec/integration/client_construction_aws_auth_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_construction_spec.rb b/spec/integration/client_construction_spec.rb index 124655c853..2f3096c677 100644 --- a/spec/integration/client_construction_spec.rb +++ b/spec/integration/client_construction_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb b/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb index 6c378d8f48..7fcaee1734 100644 --- a/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/auto_encryption_command_monitoring_spec.rb b/spec/integration/client_side_encryption/auto_encryption_command_monitoring_spec.rb index d25c7520cf..ab16db3d34 100644 --- a/spec/integration/client_side_encryption/auto_encryption_command_monitoring_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_command_monitoring_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/auto_encryption_mongocryptd_spawn_spec.rb b/spec/integration/client_side_encryption/auto_encryption_mongocryptd_spawn_spec.rb index 3956657bc3..791c10d2f4 100644 --- a/spec/integration/client_side_encryption/auto_encryption_mongocryptd_spawn_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_mongocryptd_spawn_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/auto_encryption_old_wire_version_spec.rb b/spec/integration/client_side_encryption/auto_encryption_old_wire_version_spec.rb index 87a0f8fd30..8cd1134ad5 100644 --- a/spec/integration/client_side_encryption/auto_encryption_old_wire_version_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_old_wire_version_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb b/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb index 1fc1f95989..970f991d59 100644 --- a/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_reconnect_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/auto_encryption_spec.rb b/spec/integration/client_side_encryption/auto_encryption_spec.rb index df6fb5b31f..636915c4df 100644 --- a/spec/integration/client_side_encryption/auto_encryption_spec.rb +++ b/spec/integration/client_side_encryption/auto_encryption_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' require 'bson' diff --git a/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb b/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb index 7fbb73fdff..3792e0113b 100644 --- a/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb +++ b/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/bson_size_limit_spec.rb b/spec/integration/client_side_encryption/bson_size_limit_spec.rb index 53da632432..3fba872ae2 100644 --- a/spec/integration/client_side_encryption/bson_size_limit_spec.rb +++ b/spec/integration/client_side_encryption/bson_size_limit_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/bypass_mongocryptd_spawn_spec.rb b/spec/integration/client_side_encryption/bypass_mongocryptd_spawn_spec.rb index 8c49414092..b11aa03a50 100644 --- a/spec/integration/client_side_encryption/bypass_mongocryptd_spawn_spec.rb +++ b/spec/integration/client_side_encryption/bypass_mongocryptd_spawn_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/client_close_spec.rb b/spec/integration/client_side_encryption/client_close_spec.rb index e69916dec7..989679d649 100644 --- a/spec/integration/client_side_encryption/client_close_spec.rb +++ b/spec/integration/client_side_encryption/client_close_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/corpus_spec.rb b/spec/integration/client_side_encryption/corpus_spec.rb index 315fcd7d03..2ecc8e40df 100644 --- a/spec/integration/client_side_encryption/corpus_spec.rb +++ b/spec/integration/client_side_encryption/corpus_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/custom_endpoint_spec.rb b/spec/integration/client_side_encryption/custom_endpoint_spec.rb index 6106d5f5e1..e810f3ece4 100644 --- a/spec/integration/client_side_encryption/custom_endpoint_spec.rb +++ b/spec/integration/client_side_encryption/custom_endpoint_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/data_key_spec.rb b/spec/integration/client_side_encryption/data_key_spec.rb index 153396b73a..d5c6a92f47 100644 --- a/spec/integration/client_side_encryption/data_key_spec.rb +++ b/spec/integration/client_side_encryption/data_key_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/decryption_events_prose_spec.rb b/spec/integration/client_side_encryption/decryption_events_prose_spec.rb index f6589b18c2..8274d5a1e5 100644 --- a/spec/integration/client_side_encryption/decryption_events_prose_spec.rb +++ b/spec/integration/client_side_encryption/decryption_events_prose_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/explicit_encryption_spec.rb b/spec/integration/client_side_encryption/explicit_encryption_spec.rb index 8c802f7ca9..3d97641786 100644 --- a/spec/integration/client_side_encryption/explicit_encryption_spec.rb +++ b/spec/integration/client_side_encryption/explicit_encryption_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb b/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb index 14d883bfb4..f9c5e3e6e3 100644 --- a/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb +++ b/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/external_key_vault_spec.rb b/spec/integration/client_side_encryption/external_key_vault_spec.rb index 0ed3948c09..0d87df3bd1 100644 --- a/spec/integration/client_side_encryption/external_key_vault_spec.rb +++ b/spec/integration/client_side_encryption/external_key_vault_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/kms_tls_options_spec.rb b/spec/integration/client_side_encryption/kms_tls_options_spec.rb index 5d1a87618d..0766662670 100644 --- a/spec/integration/client_side_encryption/kms_tls_options_spec.rb +++ b/spec/integration/client_side_encryption/kms_tls_options_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/kms_tls_spec.rb b/spec/integration/client_side_encryption/kms_tls_spec.rb index 0ff21d6582..aada25d89a 100644 --- a/spec/integration/client_side_encryption/kms_tls_spec.rb +++ b/spec/integration/client_side_encryption/kms_tls_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb index 7b34b0e70a..029140955f 100644 --- a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb +++ b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb b/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb index 94423e0b3e..0aa2bd0b2a 100644 --- a/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb +++ b/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb b/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb index 5fa7b55317..1c4aac80b8 100644 --- a/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb +++ b/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/queryable_encryption_examples_spec.rb b/spec/integration/client_side_encryption/queryable_encryption_examples_spec.rb index 92670efa08..c2035b28b5 100644 --- a/spec/integration/client_side_encryption/queryable_encryption_examples_spec.rb +++ b/spec/integration/client_side_encryption/queryable_encryption_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb index 551c20ad3a..0cc8a9da3b 100644 --- a/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb +++ b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_side_encryption/rewrap_prose_spec.rb b/spec/integration/client_side_encryption/rewrap_prose_spec.rb index 351ae2c269..fada5ee6e7 100644 --- a/spec/integration/client_side_encryption/rewrap_prose_spec.rb +++ b/spec/integration/client_side_encryption/rewrap_prose_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require "spec_helper" diff --git a/spec/integration/client_side_encryption/unique_index_on_key_alt_names_prose_spec.rb b/spec/integration/client_side_encryption/unique_index_on_key_alt_names_prose_spec.rb index 9c5814f1c2..a6689d3b53 100644 --- a/spec/integration/client_side_encryption/unique_index_on_key_alt_names_prose_spec.rb +++ b/spec/integration/client_side_encryption/unique_index_on_key_alt_names_prose_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require "spec_helper" diff --git a/spec/integration/client_side_encryption/views_spec.rb b/spec/integration/client_side_encryption/views_spec.rb index 4e0139f825..2f05c41e1b 100644 --- a/spec/integration/client_side_encryption/views_spec.rb +++ b/spec/integration/client_side_encryption/views_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_spec.rb b/spec/integration/client_spec.rb index 2dc71caa80..6b9ca241b2 100644 --- a/spec/integration/client_spec.rb +++ b/spec/integration/client_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/client_update_spec.rb b/spec/integration/client_update_spec.rb index 87fc4bc5c8..76d099174e 100644 --- a/spec/integration/client_update_spec.rb +++ b/spec/integration/client_update_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/collection_indexes_prose_spec.rb b/spec/integration/collection_indexes_prose_spec.rb index 70ec61965e..f9981617c0 100644 --- a/spec/integration/collection_indexes_prose_spec.rb +++ b/spec/integration/collection_indexes_prose_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/command_monitoring_spec.rb b/spec/integration/command_monitoring_spec.rb index 7f0ec3578b..00aa6b59a4 100644 --- a/spec/integration/command_monitoring_spec.rb +++ b/spec/integration/command_monitoring_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/command_spec.rb b/spec/integration/command_spec.rb index 8f78137823..8b5eba9318 100644 --- a/spec/integration/command_spec.rb +++ b/spec/integration/command_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/connect_single_rs_name_spec.rb b/spec/integration/connect_single_rs_name_spec.rb index d14cb3f8d4..815068eaf6 100644 --- a/spec/integration/connect_single_rs_name_spec.rb +++ b/spec/integration/connect_single_rs_name_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/connection_pool_populator_spec.rb b/spec/integration/connection_pool_populator_spec.rb index 49cd254e97..55d22865d2 100644 --- a/spec/integration/connection_pool_populator_spec.rb +++ b/spec/integration/connection_pool_populator_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/connection_spec.rb b/spec/integration/connection_spec.rb index 6cea7563fa..281d024a5d 100644 --- a/spec/integration/connection_spec.rb +++ b/spec/integration/connection_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/crud_spec.rb b/spec/integration/crud_spec.rb index 28384388cf..83ef5bb99f 100644 --- a/spec/integration/crud_spec.rb +++ b/spec/integration/crud_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/cursor_pinning_spec.rb b/spec/integration/cursor_pinning_spec.rb index 44f1b9d37a..b20c11d2a8 100644 --- a/spec/integration/cursor_pinning_spec.rb +++ b/spec/integration/cursor_pinning_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/cursor_reaping_spec.rb b/spec/integration/cursor_reaping_spec.rb index e465434e91..876ae46a91 100644 --- a/spec/integration/cursor_reaping_spec.rb +++ b/spec/integration/cursor_reaping_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/docs_examples_spec.rb b/spec/integration/docs_examples_spec.rb index 82fb02a7c5..01609bc353 100644 --- a/spec/integration/docs_examples_spec.rb +++ b/spec/integration/docs_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/error_detection_spec.rb b/spec/integration/error_detection_spec.rb index 1e69fe78b9..509db7ab4c 100644 --- a/spec/integration/error_detection_spec.rb +++ b/spec/integration/error_detection_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/fork_reconnect_spec.rb b/spec/integration/fork_reconnect_spec.rb index 1b573ea9fe..fe95c874e0 100644 --- a/spec/integration/fork_reconnect_spec.rb +++ b/spec/integration/fork_reconnect_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/get_more_spec.rb b/spec/integration/get_more_spec.rb index 6f53a141f5..57d5854861 100644 --- a/spec/integration/get_more_spec.rb +++ b/spec/integration/get_more_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/grid_fs_bucket_spec.rb b/spec/integration/grid_fs_bucket_spec.rb index a5730ffc60..6dc58a169b 100644 --- a/spec/integration/grid_fs_bucket_spec.rb +++ b/spec/integration/grid_fs_bucket_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/heartbeat_events_spec.rb b/spec/integration/heartbeat_events_spec.rb index 08da6baf43..73947eeb00 100644 --- a/spec/integration/heartbeat_events_spec.rb +++ b/spec/integration/heartbeat_events_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/map_reduce_spec.rb b/spec/integration/map_reduce_spec.rb index 16feb1ddb5..d566f1b1ce 100644 --- a/spec/integration/map_reduce_spec.rb +++ b/spec/integration/map_reduce_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/mmapv1_spec.rb b/spec/integration/mmapv1_spec.rb index f5b3a268ba..5d3fb57b77 100644 --- a/spec/integration/mmapv1_spec.rb +++ b/spec/integration/mmapv1_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/mongos_pinning_spec.rb b/spec/integration/mongos_pinning_spec.rb index d7bc0b5e73..f555711a5f 100644 --- a/spec/integration/mongos_pinning_spec.rb +++ b/spec/integration/mongos_pinning_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/ocsp_connectivity_spec.rb b/spec/integration/ocsp_connectivity_spec.rb index a7ccb4e039..41fd5d3889 100644 --- a/spec/integration/ocsp_connectivity_spec.rb +++ b/spec/integration/ocsp_connectivity_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/integration/ocsp_verifier_cache_spec.rb b/spec/integration/ocsp_verifier_cache_spec.rb index 81167795ed..11dcfa2838 100644 --- a/spec/integration/ocsp_verifier_cache_spec.rb +++ b/spec/integration/ocsp_verifier_cache_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'webrick' diff --git a/spec/integration/ocsp_verifier_spec.rb b/spec/integration/ocsp_verifier_spec.rb index d70b702fee..da9666adf9 100644 --- a/spec/integration/ocsp_verifier_spec.rb +++ b/spec/integration/ocsp_verifier_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'webrick' diff --git a/spec/integration/operation_failure_code_spec.rb b/spec/integration/operation_failure_code_spec.rb index 7ad224cb50..bd124f710d 100644 --- a/spec/integration/operation_failure_code_spec.rb +++ b/spec/integration/operation_failure_code_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/operation_failure_message_spec.rb b/spec/integration/operation_failure_message_spec.rb index 8f2366a68a..d865c2d981 100644 --- a/spec/integration/operation_failure_message_spec.rb +++ b/spec/integration/operation_failure_message_spec.rb @@ -1,3 +1,4 @@ +# rubocop:todo all require 'spec_helper' describe 'OperationFailure message' do diff --git a/spec/integration/query_cache_spec.rb b/spec/integration/query_cache_spec.rb index efe3156677..3145dc2fa3 100644 --- a/spec/integration/query_cache_spec.rb +++ b/spec/integration/query_cache_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/query_cache_transactions_spec.rb b/spec/integration/query_cache_transactions_spec.rb index aae9016644..465dc98260 100644 --- a/spec/integration/query_cache_transactions_spec.rb +++ b/spec/integration/query_cache_transactions_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/read_concern_spec.rb b/spec/integration/read_concern_spec.rb index bf313dfd50..be3ffff3b8 100644 --- a/spec/integration/read_concern_spec.rb +++ b/spec/integration/read_concern_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/read_preference_spec.rb b/spec/integration/read_preference_spec.rb index 2ba3960a71..42b77b49ea 100644 --- a/spec/integration/read_preference_spec.rb +++ b/spec/integration/read_preference_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/reconnect_spec.rb b/spec/integration/reconnect_spec.rb index 56b5f8e45e..f478e233dd 100644 --- a/spec/integration/reconnect_spec.rb +++ b/spec/integration/reconnect_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/retryable_errors_spec.rb b/spec/integration/retryable_errors_spec.rb index 1cbf979919..ea1aa960a9 100644 --- a/spec/integration/retryable_errors_spec.rb +++ b/spec/integration/retryable_errors_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/retryable_reads_errors_spec.rb b/spec/integration/retryable_reads_errors_spec.rb index aafff54c15..4d662c4bf5 100644 --- a/spec/integration/retryable_reads_errors_spec.rb +++ b/spec/integration/retryable_reads_errors_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/retryable_writes/retryable_writes_36_and_older_spec.rb b/spec/integration/retryable_writes/retryable_writes_36_and_older_spec.rb index 7b2220c03b..dcfccefd87 100644 --- a/spec/integration/retryable_writes/retryable_writes_36_and_older_spec.rb +++ b/spec/integration/retryable_writes/retryable_writes_36_and_older_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb b/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb index 63a3bac136..07e3041593 100644 --- a/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb +++ b/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/retryable_writes/shared/adds_diagnostics.rb b/spec/integration/retryable_writes/shared/adds_diagnostics.rb index 57edcf1c66..6cb2f23186 100644 --- a/spec/integration/retryable_writes/shared/adds_diagnostics.rb +++ b/spec/integration/retryable_writes/shared/adds_diagnostics.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module AddsDiagnostics shared_examples 'it adds diagnostics' do diff --git a/spec/integration/retryable_writes/shared/does_not_support_retries.rb b/spec/integration/retryable_writes/shared/does_not_support_retries.rb index f3dfa0e37d..9d4ce2422c 100644 --- a/spec/integration/retryable_writes/shared/does_not_support_retries.rb +++ b/spec/integration/retryable_writes/shared/does_not_support_retries.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require_relative './performs_no_retries' diff --git a/spec/integration/retryable_writes/shared/only_supports_legacy_retries.rb b/spec/integration/retryable_writes/shared/only_supports_legacy_retries.rb index 1afafc0652..f347f9bd04 100644 --- a/spec/integration/retryable_writes/shared/only_supports_legacy_retries.rb +++ b/spec/integration/retryable_writes/shared/only_supports_legacy_retries.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require_relative './performs_no_retries' require_relative './performs_legacy_retries' diff --git a/spec/integration/retryable_writes/shared/performs_legacy_retries.rb b/spec/integration/retryable_writes/shared/performs_legacy_retries.rb index 9f3199f55d..d7b0fa8db0 100644 --- a/spec/integration/retryable_writes/shared/performs_legacy_retries.rb +++ b/spec/integration/retryable_writes/shared/performs_legacy_retries.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require_relative './adds_diagnostics' diff --git a/spec/integration/retryable_writes/shared/performs_modern_retries.rb b/spec/integration/retryable_writes/shared/performs_modern_retries.rb index 17fbf7b433..28a81a93c5 100644 --- a/spec/integration/retryable_writes/shared/performs_modern_retries.rb +++ b/spec/integration/retryable_writes/shared/performs_modern_retries.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require_relative './adds_diagnostics' diff --git a/spec/integration/retryable_writes/shared/performs_no_retries.rb b/spec/integration/retryable_writes/shared/performs_no_retries.rb index b86e34ba97..aa3160dffb 100644 --- a/spec/integration/retryable_writes/shared/performs_no_retries.rb +++ b/spec/integration/retryable_writes/shared/performs_no_retries.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module PerformsNoRetries shared_examples 'it performs no retries' do diff --git a/spec/integration/retryable_writes/shared/supports_legacy_retries.rb b/spec/integration/retryable_writes/shared/supports_legacy_retries.rb index a342206d77..574bda80d6 100644 --- a/spec/integration/retryable_writes/shared/supports_legacy_retries.rb +++ b/spec/integration/retryable_writes/shared/supports_legacy_retries.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require_relative './performs_legacy_retries' diff --git a/spec/integration/retryable_writes/shared/supports_modern_retries.rb b/spec/integration/retryable_writes/shared/supports_modern_retries.rb index f3d98180ab..d4e2b5b8b0 100644 --- a/spec/integration/retryable_writes/shared/supports_modern_retries.rb +++ b/spec/integration/retryable_writes/shared/supports_modern_retries.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require_relative './performs_modern_retries' require_relative './performs_no_retries' diff --git a/spec/integration/retryable_writes/shared/supports_retries.rb b/spec/integration/retryable_writes/shared/supports_retries.rb index 73d2eca022..7bd05b3717 100644 --- a/spec/integration/retryable_writes/shared/supports_retries.rb +++ b/spec/integration/retryable_writes/shared/supports_retries.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require_relative './supports_modern_retries' require_relative './supports_legacy_retries' diff --git a/spec/integration/retryable_writes_errors_spec.rb b/spec/integration/retryable_writes_errors_spec.rb index 93342f212a..1c149e080e 100644 --- a/spec/integration/retryable_writes_errors_spec.rb +++ b/spec/integration/retryable_writes_errors_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/sdam_error_handling_spec.rb b/spec/integration/sdam_error_handling_spec.rb index 73a6bdc4ac..57884a5904 100644 --- a/spec/integration/sdam_error_handling_spec.rb +++ b/spec/integration/sdam_error_handling_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/sdam_events_spec.rb b/spec/integration/sdam_events_spec.rb index f91e3bcf3b..d02ae1ed62 100644 --- a/spec/integration/sdam_events_spec.rb +++ b/spec/integration/sdam_events_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/sdam_prose_spec.rb b/spec/integration/sdam_prose_spec.rb index 70ef60d311..9cf0c33ac6 100644 --- a/spec/integration/sdam_prose_spec.rb +++ b/spec/integration/sdam_prose_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/secondary_reads_spec.rb b/spec/integration/secondary_reads_spec.rb index 0fad857532..313193b382 100644 --- a/spec/integration/secondary_reads_spec.rb +++ b/spec/integration/secondary_reads_spec.rb @@ -1,3 +1,4 @@ +# rubocop:todo all require 'spec_helper' describe 'Secondary reads' do diff --git a/spec/integration/server_description_spec.rb b/spec/integration/server_description_spec.rb index 7c0ba035b5..6f6252d484 100644 --- a/spec/integration/server_description_spec.rb +++ b/spec/integration/server_description_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/server_monitor_spec.rb b/spec/integration/server_monitor_spec.rb index a8d9b3a21b..1b90ff42f3 100644 --- a/spec/integration/server_monitor_spec.rb +++ b/spec/integration/server_monitor_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/server_selection_spec.rb b/spec/integration/server_selection_spec.rb index 931ab7f176..f9ab23ad08 100644 --- a/spec/integration/server_selection_spec.rb +++ b/spec/integration/server_selection_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/server_selector_spec.rb b/spec/integration/server_selector_spec.rb index fa15623643..f853f30c86 100644 --- a/spec/integration/server_selector_spec.rb +++ b/spec/integration/server_selector_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/server_spec.rb b/spec/integration/server_spec.rb index 6c0ef60c34..44be905173 100644 --- a/spec/integration/server_spec.rb +++ b/spec/integration/server_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/shell_examples_spec.rb b/spec/integration/shell_examples_spec.rb index 40ecea1293..3b3c2361f3 100644 --- a/spec/integration/shell_examples_spec.rb +++ b/spec/integration/shell_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/size_limit_spec.rb b/spec/integration/size_limit_spec.rb index 48be8dc53f..2ca8c81355 100644 --- a/spec/integration/size_limit_spec.rb +++ b/spec/integration/size_limit_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/snappy_compression_spec.rb b/spec/integration/snappy_compression_spec.rb index 0531e9f356..be102b54fb 100644 --- a/spec/integration/snappy_compression_spec.rb +++ b/spec/integration/snappy_compression_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/snapshot_query_examples_spec.rb b/spec/integration/snapshot_query_examples_spec.rb index a1c9eeed9b..075bfc49fd 100644 --- a/spec/integration/snapshot_query_examples_spec.rb +++ b/spec/integration/snapshot_query_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/srv_monitoring_spec.rb b/spec/integration/srv_monitoring_spec.rb index 0303ecdebd..6d7da1f730 100644 --- a/spec/integration/srv_monitoring_spec.rb +++ b/spec/integration/srv_monitoring_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/srv_spec.rb b/spec/integration/srv_spec.rb index c5b323f126..da3d529339 100644 --- a/spec/integration/srv_spec.rb +++ b/spec/integration/srv_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/ssl_uri_options_spec.rb b/spec/integration/ssl_uri_options_spec.rb index 06fe6647cb..e1a93c88df 100644 --- a/spec/integration/ssl_uri_options_spec.rb +++ b/spec/integration/ssl_uri_options_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/step_down_spec.rb b/spec/integration/step_down_spec.rb index 19f15b09c2..a7d03825a1 100644 --- a/spec/integration/step_down_spec.rb +++ b/spec/integration/step_down_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/time_zone_querying_spec.rb b/spec/integration/time_zone_querying_spec.rb index ea6f217f8a..804fb2c17b 100644 --- a/spec/integration/time_zone_querying_spec.rb +++ b/spec/integration/time_zone_querying_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/transaction_pinning_spec.rb b/spec/integration/transaction_pinning_spec.rb index 87645c0af6..31d5e534b5 100644 --- a/spec/integration/transaction_pinning_spec.rb +++ b/spec/integration/transaction_pinning_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/transactions_api_examples_spec.rb b/spec/integration/transactions_api_examples_spec.rb index 219daecf40..daec64dc99 100644 --- a/spec/integration/transactions_api_examples_spec.rb +++ b/spec/integration/transactions_api_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/transactions_examples_spec.rb b/spec/integration/transactions_examples_spec.rb index 335bfba5f5..b60cddf21c 100644 --- a/spec/integration/transactions_examples_spec.rb +++ b/spec/integration/transactions_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/truncated_utf8_spec.rb b/spec/integration/truncated_utf8_spec.rb index 9bda156a4c..1d6bfb91af 100644 --- a/spec/integration/truncated_utf8_spec.rb +++ b/spec/integration/truncated_utf8_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/versioned_api_examples_spec.rb b/spec/integration/versioned_api_examples_spec.rb index ffcc90d6cb..6e6651530d 100644 --- a/spec/integration/versioned_api_examples_spec.rb +++ b/spec/integration/versioned_api_examples_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/x509_auth_spec.rb b/spec/integration/x509_auth_spec.rb index c6c164b668..b871291d71 100644 --- a/spec/integration/x509_auth_spec.rb +++ b/spec/integration/x509_auth_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/zlib_compression_spec.rb b/spec/integration/zlib_compression_spec.rb index 4c8bb26414..9065a1294e 100644 --- a/spec/integration/zlib_compression_spec.rb +++ b/spec/integration/zlib_compression_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/integration/zstd_compression_spec.rb b/spec/integration/zstd_compression_spec.rb index de5975c554..f721f5fb63 100644 --- a/spec/integration/zstd_compression_spec.rb +++ b/spec/integration/zstd_compression_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/kerberos/kerberos_spec.rb b/spec/kerberos/kerberos_spec.rb index c748971456..86d09f50e4 100644 --- a/spec/kerberos/kerberos_spec.rb +++ b/spec/kerberos/kerberos_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/lite_spec_helper.rb b/spec/lite_spec_helper.rb index e6a63ecfbe..22a98d7b8c 100644 --- a/spec/lite_spec_helper.rb +++ b/spec/lite_spec_helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "shared", "lib")) diff --git a/spec/mongo/address/ipv4_spec.rb b/spec/mongo/address/ipv4_spec.rb index d3b665583d..2d2b1101ce 100644 --- a/spec/mongo/address/ipv4_spec.rb +++ b/spec/mongo/address/ipv4_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/address/ipv6_spec.rb b/spec/mongo/address/ipv6_spec.rb index 88cb705a0c..b9923c0b22 100644 --- a/spec/mongo/address/ipv6_spec.rb +++ b/spec/mongo/address/ipv6_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/address/unix_spec.rb b/spec/mongo/address/unix_spec.rb index dbe3c5ec83..165a5e725e 100644 --- a/spec/mongo/address/unix_spec.rb +++ b/spec/mongo/address/unix_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/address/validator_spec.rb b/spec/mongo/address/validator_spec.rb index c0c885ee34..2880d2b027 100644 --- a/spec/mongo/address/validator_spec.rb +++ b/spec/mongo/address/validator_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/address_spec.rb b/spec/mongo/address_spec.rb index d710ff05b8..97e68d76c6 100644 --- a/spec/mongo/address_spec.rb +++ b/spec/mongo/address_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/aws/credential_cache_spec.rb b/spec/mongo/auth/aws/credential_cache_spec.rb index 694e42452f..415b1a6a3c 100644 --- a/spec/mongo/auth/aws/credential_cache_spec.rb +++ b/spec/mongo/auth/aws/credential_cache_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/auth/aws/credentials_retriever_spec.rb b/spec/mongo/auth/aws/credentials_retriever_spec.rb index 041a2f9e46..5a83adeb9c 100644 --- a/spec/mongo/auth/aws/credentials_retriever_spec.rb +++ b/spec/mongo/auth/aws/credentials_retriever_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/auth/aws/credentials_spec.rb b/spec/mongo/auth/aws/credentials_spec.rb index 4b57584519..3c0f2cb87a 100644 --- a/spec/mongo/auth/aws/credentials_spec.rb +++ b/spec/mongo/auth/aws/credentials_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/auth/aws/request_region_spec.rb b/spec/mongo/auth/aws/request_region_spec.rb index 63d555d4d9..9f5a387f57 100644 --- a/spec/mongo/auth/aws/request_region_spec.rb +++ b/spec/mongo/auth/aws/request_region_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/auth/aws/request_spec.rb b/spec/mongo/auth/aws/request_spec.rb index 3881a9da16..fa2ac18af5 100644 --- a/spec/mongo/auth/aws/request_spec.rb +++ b/spec/mongo/auth/aws/request_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/cr_spec.rb b/spec/mongo/auth/cr_spec.rb index 2eaf93a2b0..ef6c1f158e 100644 --- a/spec/mongo/auth/cr_spec.rb +++ b/spec/mongo/auth/cr_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' require 'support/shared/auth_context' diff --git a/spec/mongo/auth/gssapi/conversation_spec.rb b/spec/mongo/auth/gssapi/conversation_spec.rb index 1365bf4122..d30bab43f5 100644 --- a/spec/mongo/auth/gssapi/conversation_spec.rb +++ b/spec/mongo/auth/gssapi/conversation_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/invalid_mechanism_spec.rb b/spec/mongo/auth/invalid_mechanism_spec.rb index ba5946870d..ca82146b02 100644 --- a/spec/mongo/auth/invalid_mechanism_spec.rb +++ b/spec/mongo/auth/invalid_mechanism_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/auth/ldap/conversation_spec.rb b/spec/mongo/auth/ldap/conversation_spec.rb index a707b5edc0..6afa26df68 100644 --- a/spec/mongo/auth/ldap/conversation_spec.rb +++ b/spec/mongo/auth/ldap/conversation_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/ldap_spec.rb b/spec/mongo/auth/ldap_spec.rb index d087b8e86b..31dc35ee5d 100644 --- a/spec/mongo/auth/ldap_spec.rb +++ b/spec/mongo/auth/ldap_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' require 'support/shared/auth_context' diff --git a/spec/mongo/auth/scram/conversation_spec.rb b/spec/mongo/auth/scram/conversation_spec.rb index 26dc04ea4e..db82bcf2b2 100644 --- a/spec/mongo/auth/scram/conversation_spec.rb +++ b/spec/mongo/auth/scram/conversation_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/scram_conversation' diff --git a/spec/mongo/auth/scram256/conversation_spec.rb b/spec/mongo/auth/scram256/conversation_spec.rb index 8b48a2318e..47cc6bf32c 100644 --- a/spec/mongo/auth/scram256/conversation_spec.rb +++ b/spec/mongo/auth/scram256/conversation_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/scram_conversation' diff --git a/spec/mongo/auth/scram_negotiation_spec.rb b/spec/mongo/auth/scram_negotiation_spec.rb index a840a0b7d7..fb4f25c00e 100644 --- a/spec/mongo/auth/scram_negotiation_spec.rb +++ b/spec/mongo/auth/scram_negotiation_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/scram_spec.rb b/spec/mongo/auth/scram_spec.rb index c475f43339..64b5878aa4 100644 --- a/spec/mongo/auth/scram_spec.rb +++ b/spec/mongo/auth/scram_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' require 'support/shared/auth_context' diff --git a/spec/mongo/auth/stringprep/profiles/sasl_spec.rb b/spec/mongo/auth/stringprep/profiles/sasl_spec.rb index 041c72a243..4c620723ab 100644 --- a/spec/mongo/auth/stringprep/profiles/sasl_spec.rb +++ b/spec/mongo/auth/stringprep/profiles/sasl_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/stringprep_spec.rb b/spec/mongo/auth/stringprep_spec.rb index 40f4111838..acd4380ffe 100644 --- a/spec/mongo/auth/stringprep_spec.rb +++ b/spec/mongo/auth/stringprep_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/user/view_spec.rb b/spec/mongo/auth/user/view_spec.rb index 4ffb2cee89..4986754855 100644 --- a/spec/mongo/auth/user/view_spec.rb +++ b/spec/mongo/auth/user/view_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/user_spec.rb b/spec/mongo/auth/user_spec.rb index 58a45141ce..5ccc1d3584 100644 --- a/spec/mongo/auth/user_spec.rb +++ b/spec/mongo/auth/user_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/x509/conversation_spec.rb b/spec/mongo/auth/x509/conversation_spec.rb index aaa07e96e4..777467a958 100644 --- a/spec/mongo/auth/x509/conversation_spec.rb +++ b/spec/mongo/auth/x509/conversation_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/auth/x509_spec.rb b/spec/mongo/auth/x509_spec.rb index b92990f792..3f7fe688b6 100644 --- a/spec/mongo/auth/x509_spec.rb +++ b/spec/mongo/auth/x509_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' require 'support/shared/auth_context' diff --git a/spec/mongo/auth_spec.rb b/spec/mongo/auth_spec.rb index 6fb80d4f74..a770aacdd6 100644 --- a/spec/mongo/auth_spec.rb +++ b/spec/mongo/auth_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/bson_spec.rb b/spec/mongo/bson_spec.rb index 4ba63f4952..4b65744381 100644 --- a/spec/mongo/bson_spec.rb +++ b/spec/mongo/bson_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/bulk_write/ordered_combiner_spec.rb b/spec/mongo/bulk_write/ordered_combiner_spec.rb index 094956ff04..f544a8cfe4 100644 --- a/spec/mongo/bulk_write/ordered_combiner_spec.rb +++ b/spec/mongo/bulk_write/ordered_combiner_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/bulk_write/result_spec.rb b/spec/mongo/bulk_write/result_spec.rb index 8b27c37393..3bc7ca169e 100644 --- a/spec/mongo/bulk_write/result_spec.rb +++ b/spec/mongo/bulk_write/result_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/bulk_write/unordered_combiner_spec.rb b/spec/mongo/bulk_write/unordered_combiner_spec.rb index 24edf085b9..a3ea99e73f 100644 --- a/spec/mongo/bulk_write/unordered_combiner_spec.rb +++ b/spec/mongo/bulk_write/unordered_combiner_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/bulk_write_spec.rb b/spec/mongo/bulk_write_spec.rb index a604ae7721..1a840b944c 100644 --- a/spec/mongo/bulk_write_spec.rb +++ b/spec/mongo/bulk_write_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/caching_cursor_spec.rb b/spec/mongo/caching_cursor_spec.rb index 41d2b7d085..e5b55fe910 100644 --- a/spec/mongo/caching_cursor_spec.rb +++ b/spec/mongo/caching_cursor_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index a63a333708..254cc9001a 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/client_encryption_spec.rb b/spec/mongo/client_encryption_spec.rb index e1ff89134e..ca0e6cd99b 100644 --- a/spec/mongo/client_encryption_spec.rb +++ b/spec/mongo/client_encryption_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/client_spec.rb b/spec/mongo/client_spec.rb index 10c2f04fc2..5427d1f72f 100644 --- a/spec/mongo/client_spec.rb +++ b/spec/mongo/client_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster/cursor_reaper_spec.rb b/spec/mongo/cluster/cursor_reaper_spec.rb index ba6d86bc2e..34950e66c6 100644 --- a/spec/mongo/cluster/cursor_reaper_spec.rb +++ b/spec/mongo/cluster/cursor_reaper_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster/periodic_executor_spec.rb b/spec/mongo/cluster/periodic_executor_spec.rb index 3ab888050a..c02f569cc3 100644 --- a/spec/mongo/cluster/periodic_executor_spec.rb +++ b/spec/mongo/cluster/periodic_executor_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster/socket_reaper_spec.rb b/spec/mongo/cluster/socket_reaper_spec.rb index a6c550a653..ef66eeeda7 100644 --- a/spec/mongo/cluster/socket_reaper_spec.rb +++ b/spec/mongo/cluster/socket_reaper_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster/topology/replica_set_spec.rb b/spec/mongo/cluster/topology/replica_set_spec.rb index 295cce70c8..a92f6bb6a8 100644 --- a/spec/mongo/cluster/topology/replica_set_spec.rb +++ b/spec/mongo/cluster/topology/replica_set_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster/topology/sharded_spec.rb b/spec/mongo/cluster/topology/sharded_spec.rb index 84fbd1d06e..187d755b00 100644 --- a/spec/mongo/cluster/topology/sharded_spec.rb +++ b/spec/mongo/cluster/topology/sharded_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster/topology/single_spec.rb b/spec/mongo/cluster/topology/single_spec.rb index b29ce5773a..a59d381d3a 100644 --- a/spec/mongo/cluster/topology/single_spec.rb +++ b/spec/mongo/cluster/topology/single_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster/topology/unknown_spec.rb b/spec/mongo/cluster/topology/unknown_spec.rb index a13b48d449..6de59c2923 100644 --- a/spec/mongo/cluster/topology/unknown_spec.rb +++ b/spec/mongo/cluster/topology/unknown_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster/topology_spec.rb b/spec/mongo/cluster/topology_spec.rb index b59fc3bc4d..93a86262a2 100644 --- a/spec/mongo/cluster/topology_spec.rb +++ b/spec/mongo/cluster/topology_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/cluster_spec.rb b/spec/mongo/cluster_spec.rb index 6dccf89b06..f51436c253 100644 --- a/spec/mongo/cluster_spec.rb +++ b/spec/mongo/cluster_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/cluster_time_spec.rb b/spec/mongo/cluster_time_spec.rb index cec8644666..723cec2c9f 100644 --- a/spec/mongo/cluster_time_spec.rb +++ b/spec/mongo/cluster_time_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/collection/view/aggregation_spec.rb b/spec/mongo/collection/view/aggregation_spec.rb index 60bb8ddd85..7657460697 100644 --- a/spec/mongo/collection/view/aggregation_spec.rb +++ b/spec/mongo/collection/view/aggregation_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view/builder/find_command_spec.rb b/spec/mongo/collection/view/builder/find_command_spec.rb index 74cc23a9a3..f4ef7aff26 100644 --- a/spec/mongo/collection/view/builder/find_command_spec.rb +++ b/spec/mongo/collection/view/builder/find_command_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # TODO convert, move or delete these tests as part of RUBY-2706. diff --git a/spec/mongo/collection/view/builder/op_query_spec.rb b/spec/mongo/collection/view/builder/op_query_spec.rb index 6e4b67c8d0..783e926ce9 100644 --- a/spec/mongo/collection/view/builder/op_query_spec.rb +++ b/spec/mongo/collection/view/builder/op_query_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # TODO convert, move or delete these tests as part of RUBY-2706. diff --git a/spec/mongo/collection/view/change_stream_resume_spec.rb b/spec/mongo/collection/view/change_stream_resume_spec.rb index c45f028325..328cbd7b1f 100644 --- a/spec/mongo/collection/view/change_stream_resume_spec.rb +++ b/spec/mongo/collection/view/change_stream_resume_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view/change_stream_spec.rb b/spec/mongo/collection/view/change_stream_spec.rb index 6fcdc06e9e..ad25ad65a8 100644 --- a/spec/mongo/collection/view/change_stream_spec.rb +++ b/spec/mongo/collection/view/change_stream_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view/explainable_spec.rb b/spec/mongo/collection/view/explainable_spec.rb index 2cb57b5099..644b865f88 100644 --- a/spec/mongo/collection/view/explainable_spec.rb +++ b/spec/mongo/collection/view/explainable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view/immutable_spec.rb b/spec/mongo/collection/view/immutable_spec.rb index 06dc9e4ccb..22e2f118df 100644 --- a/spec/mongo/collection/view/immutable_spec.rb +++ b/spec/mongo/collection/view/immutable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view/iterable_spec.rb b/spec/mongo/collection/view/iterable_spec.rb index ded2d3e9eb..c8e1f97956 100644 --- a/spec/mongo/collection/view/iterable_spec.rb +++ b/spec/mongo/collection/view/iterable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view/map_reduce_spec.rb b/spec/mongo/collection/view/map_reduce_spec.rb index b6e65ff436..f479973173 100644 --- a/spec/mongo/collection/view/map_reduce_spec.rb +++ b/spec/mongo/collection/view/map_reduce_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view/readable_spec.rb b/spec/mongo/collection/view/readable_spec.rb index d83bed8351..a5d23514b0 100644 --- a/spec/mongo/collection/view/readable_spec.rb +++ b/spec/mongo/collection/view/readable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view/writable_spec.rb b/spec/mongo/collection/view/writable_spec.rb index 48f880e46a..d6442ce05f 100644 --- a/spec/mongo/collection/view/writable_spec.rb +++ b/spec/mongo/collection/view/writable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection/view_spec.rb b/spec/mongo/collection/view_spec.rb index 8507902d71..bc832055cb 100644 --- a/spec/mongo/collection/view_spec.rb +++ b/spec/mongo/collection/view_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection_crud_spec.rb b/spec/mongo/collection_crud_spec.rb index 411b6bd4a0..2f26179ffc 100644 --- a/spec/mongo/collection_crud_spec.rb +++ b/spec/mongo/collection_crud_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection_ddl_spec.rb b/spec/mongo/collection_ddl_spec.rb index b0c39831db..4599133925 100644 --- a/spec/mongo/collection_ddl_spec.rb +++ b/spec/mongo/collection_ddl_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/collection_spec.rb b/spec/mongo/collection_spec.rb index 8291b6522c..bd050bb339 100644 --- a/spec/mongo/collection_spec.rb +++ b/spec/mongo/collection_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/condition_variable_spec.rb b/spec/mongo/condition_variable_spec.rb index 54ebb2590b..c6f8e8f11b 100644 --- a/spec/mongo/condition_variable_spec.rb +++ b/spec/mongo/condition_variable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/config/options_spec.rb b/spec/mongo/config/options_spec.rb index ba41169fe2..311ca09652 100644 --- a/spec/mongo/config/options_spec.rb +++ b/spec/mongo/config/options_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all require "spec_helper" diff --git a/spec/mongo/config_spec.rb b/spec/mongo/config_spec.rb index 7fdadf309c..9c0425a7f8 100644 --- a/spec/mongo/config_spec.rb +++ b/spec/mongo/config_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all require "spec_helper" @@ -70,4 +71,4 @@ end end end -end \ No newline at end of file +end diff --git a/spec/mongo/crypt/auto_decryption_context_spec.rb b/spec/mongo/crypt/auto_decryption_context_spec.rb index 140f17a2ab..8d6abdaaad 100644 --- a/spec/mongo/crypt/auto_decryption_context_spec.rb +++ b/spec/mongo/crypt/auto_decryption_context_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'lite_spec_helper' diff --git a/spec/mongo/crypt/auto_encrypter_spec.rb b/spec/mongo/crypt/auto_encrypter_spec.rb index eab89e96b8..0c9aeb7922 100644 --- a/spec/mongo/crypt/auto_encrypter_spec.rb +++ b/spec/mongo/crypt/auto_encrypter_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' require 'tempfile' diff --git a/spec/mongo/crypt/auto_encryption_context_spec.rb b/spec/mongo/crypt/auto_encryption_context_spec.rb index 1cff225ac7..93dee5903f 100644 --- a/spec/mongo/crypt/auto_encryption_context_spec.rb +++ b/spec/mongo/crypt/auto_encryption_context_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'lite_spec_helper' diff --git a/spec/mongo/crypt/binary_spec.rb b/spec/mongo/crypt/binary_spec.rb index ccde653af9..864e1f2256 100644 --- a/spec/mongo/crypt/binary_spec.rb +++ b/spec/mongo/crypt/binary_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/crypt/binding/binary_spec.rb b/spec/mongo/crypt/binding/binary_spec.rb index e19b27ace5..066e39c2d3 100644 --- a/spec/mongo/crypt/binding/binary_spec.rb +++ b/spec/mongo/crypt/binding/binary_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/crypt/binding/context_spec.rb b/spec/mongo/crypt/binding/context_spec.rb index 43122300bc..bda07b8f7d 100644 --- a/spec/mongo/crypt/binding/context_spec.rb +++ b/spec/mongo/crypt/binding/context_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require_relative '../helpers/mongo_crypt_spec_helper' diff --git a/spec/mongo/crypt/binding/helpers_spec.rb b/spec/mongo/crypt/binding/helpers_spec.rb index 711a571190..1e2cf4c70b 100644 --- a/spec/mongo/crypt/binding/helpers_spec.rb +++ b/spec/mongo/crypt/binding/helpers_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/crypt/binding/mongocrypt_spec.rb b/spec/mongo/crypt/binding/mongocrypt_spec.rb index f8ca80071e..10bd981ae8 100644 --- a/spec/mongo/crypt/binding/mongocrypt_spec.rb +++ b/spec/mongo/crypt/binding/mongocrypt_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require_relative '../helpers/mongo_crypt_spec_helper' diff --git a/spec/mongo/crypt/binding/status_spec.rb b/spec/mongo/crypt/binding/status_spec.rb index f5375a1b1a..5e804f3848 100644 --- a/spec/mongo/crypt/binding/status_spec.rb +++ b/spec/mongo/crypt/binding/status_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/crypt/binding/version_spec.rb b/spec/mongo/crypt/binding/version_spec.rb index ffc0ac0ced..fb55a2e09e 100644 --- a/spec/mongo/crypt/binding/version_spec.rb +++ b/spec/mongo/crypt/binding/version_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/crypt/binding_unloaded_spec.rb b/spec/mongo/crypt/binding_unloaded_spec.rb index bd36eb0397..8c949d0db2 100644 --- a/spec/mongo/crypt/binding_unloaded_spec.rb +++ b/spec/mongo/crypt/binding_unloaded_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/crypt/data_key_context_spec.rb b/spec/mongo/crypt/data_key_context_spec.rb index 99c89e791e..babcfac43c 100644 --- a/spec/mongo/crypt/data_key_context_spec.rb +++ b/spec/mongo/crypt/data_key_context_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'base64' diff --git a/spec/mongo/crypt/encryption_io_spec.rb b/spec/mongo/crypt/encryption_io_spec.rb index 8031bde862..b9568f31a9 100644 --- a/spec/mongo/crypt/encryption_io_spec.rb +++ b/spec/mongo/crypt/encryption_io_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'spec_helper' diff --git a/spec/mongo/crypt/explicit_decryption_context_spec.rb b/spec/mongo/crypt/explicit_decryption_context_spec.rb index 337d55f5c1..644d0a7db9 100644 --- a/spec/mongo/crypt/explicit_decryption_context_spec.rb +++ b/spec/mongo/crypt/explicit_decryption_context_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'lite_spec_helper' diff --git a/spec/mongo/crypt/explicit_encryption_context_spec.rb b/spec/mongo/crypt/explicit_encryption_context_spec.rb index 4e209499ee..c19d0fa715 100644 --- a/spec/mongo/crypt/explicit_encryption_context_spec.rb +++ b/spec/mongo/crypt/explicit_encryption_context_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'lite_spec_helper' diff --git a/spec/mongo/crypt/handle_spec.rb b/spec/mongo/crypt/handle_spec.rb index be5331e858..a7af8e3c83 100644 --- a/spec/mongo/crypt/handle_spec.rb +++ b/spec/mongo/crypt/handle_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'base64' diff --git a/spec/mongo/crypt/helpers/mongo_crypt_spec_helper.rb b/spec/mongo/crypt/helpers/mongo_crypt_spec_helper.rb index 754ac7bf7f..6723c13477 100644 --- a/spec/mongo/crypt/helpers/mongo_crypt_spec_helper.rb +++ b/spec/mongo/crypt/helpers/mongo_crypt_spec_helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module MongoCryptSpecHelper def bind_crypto_hooks(mongocrypt) diff --git a/spec/mongo/crypt/hooks_spec.rb b/spec/mongo/crypt/hooks_spec.rb index 2c0a849614..c97d2beb2d 100644 --- a/spec/mongo/crypt/hooks_spec.rb +++ b/spec/mongo/crypt/hooks_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'base64' @@ -27,4 +27,4 @@ ).to eq(signature) end end -end \ No newline at end of file +end diff --git a/spec/mongo/crypt/kms/credentials_spec.rb b/spec/mongo/crypt/kms/credentials_spec.rb index 4e2c08df42..b87712fd4a 100644 --- a/spec/mongo/crypt/kms/credentials_spec.rb +++ b/spec/mongo/crypt/kms/credentials_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'lite_spec_helper' diff --git a/spec/mongo/crypt/kms_spec.rb b/spec/mongo/crypt/kms_spec.rb index f84ebadeb2..8e7b777d41 100644 --- a/spec/mongo/crypt/kms_spec.rb +++ b/spec/mongo/crypt/kms_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' require 'lite_spec_helper' diff --git a/spec/mongo/crypt/status_spec.rb b/spec/mongo/crypt/status_spec.rb index 271a253aa8..a2efc97f3f 100644 --- a/spec/mongo/crypt/status_spec.rb +++ b/spec/mongo/crypt/status_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/cursor/builder/get_more_command_spec.rb b/spec/mongo/cursor/builder/get_more_command_spec.rb index bdffd7656e..96460681d7 100644 --- a/spec/mongo/cursor/builder/get_more_command_spec.rb +++ b/spec/mongo/cursor/builder/get_more_command_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # TODO convert, move or delete these tests as part of RUBY-2706. diff --git a/spec/mongo/cursor/builder/op_get_more_spec.rb b/spec/mongo/cursor/builder/op_get_more_spec.rb index 7f4531d62a..13601b6d1b 100644 --- a/spec/mongo/cursor/builder/op_get_more_spec.rb +++ b/spec/mongo/cursor/builder/op_get_more_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # TODO convert, move or delete these tests as part of RUBY-2706. diff --git a/spec/mongo/cursor_spec.rb b/spec/mongo/cursor_spec.rb index 24efb8cf9e..bab8c632f6 100644 --- a/spec/mongo/cursor_spec.rb +++ b/spec/mongo/cursor_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/database_spec.rb b/spec/mongo/database_spec.rb index 9cfd5f1479..1b617aa227 100644 --- a/spec/mongo/database_spec.rb +++ b/spec/mongo/database_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/distinguishing_semaphore_spec.rb b/spec/mongo/distinguishing_semaphore_spec.rb index 9fc4b31984..8b77445489 100644 --- a/spec/mongo/distinguishing_semaphore_spec.rb +++ b/spec/mongo/distinguishing_semaphore_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/error/bulk_write_error_spec.rb b/spec/mongo/error/bulk_write_error_spec.rb index cf9231196d..6bc275e4c9 100644 --- a/spec/mongo/error/bulk_write_error_spec.rb +++ b/spec/mongo/error/bulk_write_error_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/error/crypt_error_spec.rb b/spec/mongo/error/crypt_error_spec.rb index 177665640f..8b965ea53d 100644 --- a/spec/mongo/error/crypt_error_spec.rb +++ b/spec/mongo/error/crypt_error_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/error/max_bson_size_spec.rb b/spec/mongo/error/max_bson_size_spec.rb index 73507ad86d..c9bf797d90 100644 --- a/spec/mongo/error/max_bson_size_spec.rb +++ b/spec/mongo/error/max_bson_size_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/error/no_server_available_spec.rb b/spec/mongo/error/no_server_available_spec.rb index 31c7307c15..ab35da08e0 100644 --- a/spec/mongo/error/no_server_available_spec.rb +++ b/spec/mongo/error/no_server_available_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/error/notable_spec.rb b/spec/mongo/error/notable_spec.rb index da91cb89f1..bb6312a345 100644 --- a/spec/mongo/error/notable_spec.rb +++ b/spec/mongo/error/notable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/error/operation_failure_heavy_spec.rb b/spec/mongo/error/operation_failure_heavy_spec.rb index 6888e26b64..e18373992e 100644 --- a/spec/mongo/error/operation_failure_heavy_spec.rb +++ b/spec/mongo/error/operation_failure_heavy_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/error/operation_failure_spec.rb b/spec/mongo/error/operation_failure_spec.rb index e95688bf31..509771b132 100644 --- a/spec/mongo/error/operation_failure_spec.rb +++ b/spec/mongo/error/operation_failure_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/error/parser_spec.rb b/spec/mongo/error/parser_spec.rb index 1c9c72b91e..8fad537b7b 100644 --- a/spec/mongo/error/parser_spec.rb +++ b/spec/mongo/error/parser_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/error/unsupported_option_spec.rb b/spec/mongo/error/unsupported_option_spec.rb index 44d315c058..1486c83263 100644 --- a/spec/mongo/error/unsupported_option_spec.rb +++ b/spec/mongo/error/unsupported_option_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/event/publisher_spec.rb b/spec/mongo/event/publisher_spec.rb index 11afccd687..6bc3bb1b7f 100644 --- a/spec/mongo/event/publisher_spec.rb +++ b/spec/mongo/event/publisher_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/event/subscriber_spec.rb b/spec/mongo/event/subscriber_spec.rb index 29910a30d9..cad7cce815 100644 --- a/spec/mongo/event/subscriber_spec.rb +++ b/spec/mongo/event/subscriber_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/grid/file/chunk_spec.rb b/spec/mongo/grid/file/chunk_spec.rb index 161bc1ea44..8b5ef36e6c 100644 --- a/spec/mongo/grid/file/chunk_spec.rb +++ b/spec/mongo/grid/file/chunk_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' require 'stringio' diff --git a/spec/mongo/grid/file/info_spec.rb b/spec/mongo/grid/file/info_spec.rb index 9199b5108c..247bec74c4 100644 --- a/spec/mongo/grid/file/info_spec.rb +++ b/spec/mongo/grid/file/info_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/grid/file_spec.rb b/spec/mongo/grid/file_spec.rb index c4eb5ca347..17e48a533a 100644 --- a/spec/mongo/grid/file_spec.rb +++ b/spec/mongo/grid/file_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/grid/fs_bucket_spec.rb b/spec/mongo/grid/fs_bucket_spec.rb index 692b2d5341..045a8e20dd 100644 --- a/spec/mongo/grid/fs_bucket_spec.rb +++ b/spec/mongo/grid/fs_bucket_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/grid/stream/read_spec.rb b/spec/mongo/grid/stream/read_spec.rb index d2e4c6c96d..ccdd619f1c 100644 --- a/spec/mongo/grid/stream/read_spec.rb +++ b/spec/mongo/grid/stream/read_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/grid/stream/write_spec.rb b/spec/mongo/grid/stream/write_spec.rb index 3bd558fb1c..ba03025f91 100644 --- a/spec/mongo/grid/stream/write_spec.rb +++ b/spec/mongo/grid/stream/write_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/grid/stream_spec.rb b/spec/mongo/grid/stream_spec.rb index c5d3e01fa2..55ce7d0b8e 100644 --- a/spec/mongo/grid/stream_spec.rb +++ b/spec/mongo/grid/stream_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/id_spec.rb b/spec/mongo/id_spec.rb index a038ce6d6c..1413214304 100644 --- a/spec/mongo/id_spec.rb +++ b/spec/mongo/id_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' @@ -35,4 +35,4 @@ class IdD expect(IdC.next_id).to eq(2) expect(IdD.next_id).to eq(2) end -end \ No newline at end of file +end diff --git a/spec/mongo/index/view_spec.rb b/spec/mongo/index/view_spec.rb index d1c874e37d..24cf8897f6 100644 --- a/spec/mongo/index/view_spec.rb +++ b/spec/mongo/index/view_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/lint_spec.rb b/spec/mongo/lint_spec.rb index 9f8aee723d..807d1dbc8d 100644 --- a/spec/mongo/lint_spec.rb +++ b/spec/mongo/lint_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/logger_spec.rb b/spec/mongo/logger_spec.rb index 284cfc94a6..351072c262 100644 --- a/spec/mongo/logger_spec.rb +++ b/spec/mongo/logger_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/command_log_subscriber_spec.rb b/spec/mongo/monitoring/command_log_subscriber_spec.rb index 338861d09d..af492a21f0 100644 --- a/spec/mongo/monitoring/command_log_subscriber_spec.rb +++ b/spec/mongo/monitoring/command_log_subscriber_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/monitoring/event/cmap/connection_check_out_failed_spec.rb b/spec/mongo/monitoring/event/cmap/connection_check_out_failed_spec.rb index 641164fa35..4a3f41f18e 100644 --- a/spec/mongo/monitoring/event/cmap/connection_check_out_failed_spec.rb +++ b/spec/mongo/monitoring/event/cmap/connection_check_out_failed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' @@ -23,4 +23,4 @@ expect(event.summary).to eq('#') end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/connection_check_out_started_spec.rb b/spec/mongo/monitoring/event/cmap/connection_check_out_started_spec.rb index 2dac30efb2..6e26076e0e 100644 --- a/spec/mongo/monitoring/event/cmap/connection_check_out_started_spec.rb +++ b/spec/mongo/monitoring/event/cmap/connection_check_out_started_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' @@ -19,4 +19,4 @@ expect(event.summary).to eq('#') end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/connection_checked_in_spec.rb b/spec/mongo/monitoring/event/cmap/connection_checked_in_spec.rb index d0607aded4..b95351e72b 100644 --- a/spec/mongo/monitoring/event/cmap/connection_checked_in_spec.rb +++ b/spec/mongo/monitoring/event/cmap/connection_checked_in_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' @@ -30,4 +30,4 @@ expect(event.summary).to eq("#") end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/connection_checked_out_spec.rb b/spec/mongo/monitoring/event/cmap/connection_checked_out_spec.rb index cb5dbb5921..bc432d1524 100644 --- a/spec/mongo/monitoring/event/cmap/connection_checked_out_spec.rb +++ b/spec/mongo/monitoring/event/cmap/connection_checked_out_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' @@ -30,4 +30,4 @@ expect(event.summary).to eq("#") end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/connection_closed_spec.rb b/spec/mongo/monitoring/event/cmap/connection_closed_spec.rb index aeb2323e9a..f6b313fb7d 100644 --- a/spec/mongo/monitoring/event/cmap/connection_closed_spec.rb +++ b/spec/mongo/monitoring/event/cmap/connection_closed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' @@ -27,4 +27,4 @@ expect(event.summary).to eq('#') end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/connection_created_spec.rb b/spec/mongo/monitoring/event/cmap/connection_created_spec.rb index 987b25f412..19c25d1f10 100644 --- a/spec/mongo/monitoring/event/cmap/connection_created_spec.rb +++ b/spec/mongo/monitoring/event/cmap/connection_created_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' @@ -24,4 +24,4 @@ expect(event.summary).to eq('#') end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/connection_ready_spec.rb b/spec/mongo/monitoring/event/cmap/connection_ready_spec.rb index 2feace7e06..608b0fa4c6 100644 --- a/spec/mongo/monitoring/event/cmap/connection_ready_spec.rb +++ b/spec/mongo/monitoring/event/cmap/connection_ready_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' @@ -24,4 +24,4 @@ expect(event.summary).to eq('#') end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/pool_cleared_spec.rb b/spec/mongo/monitoring/event/cmap/pool_cleared_spec.rb index 7530c4c36b..d4e8dd753e 100644 --- a/spec/mongo/monitoring/event/cmap/pool_cleared_spec.rb +++ b/spec/mongo/monitoring/event/cmap/pool_cleared_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' @@ -19,4 +19,4 @@ expect(event.summary).to eq('#') end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/pool_closed_spec.rb b/spec/mongo/monitoring/event/cmap/pool_closed_spec.rb index 1c6ee48eb0..a9b1868c39 100644 --- a/spec/mongo/monitoring/event/cmap/pool_closed_spec.rb +++ b/spec/mongo/monitoring/event/cmap/pool_closed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' @@ -26,4 +26,4 @@ expect(event.summary).to eq("#") end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/cmap/pool_created_spec.rb b/spec/mongo/monitoring/event/cmap/pool_created_spec.rb index 7a4bdc055c..637c46ba18 100644 --- a/spec/mongo/monitoring/event/cmap/pool_created_spec.rb +++ b/spec/mongo/monitoring/event/cmap/pool_created_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' @@ -33,4 +33,4 @@ expect(event.summary).to eq("#3, :min_pool_size=>5} pool=0x#{pool.object_id}>") end end -end \ No newline at end of file +end diff --git a/spec/mongo/monitoring/event/command_failed_spec.rb b/spec/mongo/monitoring/event/command_failed_spec.rb index ba91b06e0c..87542e7ee7 100644 --- a/spec/mongo/monitoring/event/command_failed_spec.rb +++ b/spec/mongo/monitoring/event/command_failed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/command_started_spec.rb b/spec/mongo/monitoring/event/command_started_spec.rb index 850550f634..842262c76b 100644 --- a/spec/mongo/monitoring/event/command_started_spec.rb +++ b/spec/mongo/monitoring/event/command_started_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/command_succeeded_spec.rb b/spec/mongo/monitoring/event/command_succeeded_spec.rb index 8b2231cff5..8ae2bafa7a 100644 --- a/spec/mongo/monitoring/event/command_succeeded_spec.rb +++ b/spec/mongo/monitoring/event/command_succeeded_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/secure_spec.rb b/spec/mongo/monitoring/event/secure_spec.rb index debfd4ceb5..b1fb5d0306 100644 --- a/spec/mongo/monitoring/event/secure_spec.rb +++ b/spec/mongo/monitoring/event/secure_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/server_closed_spec.rb b/spec/mongo/monitoring/event/server_closed_spec.rb index eb24eacdca..3eeb162c8d 100644 --- a/spec/mongo/monitoring/event/server_closed_spec.rb +++ b/spec/mongo/monitoring/event/server_closed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/server_description_changed_spec.rb b/spec/mongo/monitoring/event/server_description_changed_spec.rb index 1b661dbdde..f3e8921e88 100644 --- a/spec/mongo/monitoring/event/server_description_changed_spec.rb +++ b/spec/mongo/monitoring/event/server_description_changed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb b/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb index cbc403dea6..bc87c941df 100644 --- a/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +++ b/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/server_heartbeat_started_spec.rb b/spec/mongo/monitoring/event/server_heartbeat_started_spec.rb index d392e35a11..8e002f2c6d 100644 --- a/spec/mongo/monitoring/event/server_heartbeat_started_spec.rb +++ b/spec/mongo/monitoring/event/server_heartbeat_started_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb b/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb index 1325e718f7..45dac6dc40 100644 --- a/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +++ b/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/server_opening_spec.rb b/spec/mongo/monitoring/event/server_opening_spec.rb index 3db262fb4d..c008d5fb67 100644 --- a/spec/mongo/monitoring/event/server_opening_spec.rb +++ b/spec/mongo/monitoring/event/server_opening_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/topology_changed_spec.rb b/spec/mongo/monitoring/event/topology_changed_spec.rb index df55dc2f81..bf9c57fdac 100644 --- a/spec/mongo/monitoring/event/topology_changed_spec.rb +++ b/spec/mongo/monitoring/event/topology_changed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/topology_closed_spec.rb b/spec/mongo/monitoring/event/topology_closed_spec.rb index 746792dafa..00dd9982a0 100644 --- a/spec/mongo/monitoring/event/topology_closed_spec.rb +++ b/spec/mongo/monitoring/event/topology_closed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring/event/topology_opening_spec.rb b/spec/mongo/monitoring/event/topology_opening_spec.rb index f2a5d0bbd4..b7906ef688 100644 --- a/spec/mongo/monitoring/event/topology_opening_spec.rb +++ b/spec/mongo/monitoring/event/topology_opening_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/monitoring_spec.rb b/spec/mongo/monitoring_spec.rb index 66dfeab417..3b20fdf074 100644 --- a/spec/mongo/monitoring_spec.rb +++ b/spec/mongo/monitoring_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/aggregate/result_spec.rb b/spec/mongo/operation/aggregate/result_spec.rb index c5f2ba5e21..4142ceb8cd 100644 --- a/spec/mongo/operation/aggregate/result_spec.rb +++ b/spec/mongo/operation/aggregate/result_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/aggregate_spec.rb b/spec/mongo/operation/aggregate_spec.rb index 64fc22088a..c98e231cbe 100644 --- a/spec/mongo/operation/aggregate_spec.rb +++ b/spec/mongo/operation/aggregate_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/collections_info_spec.rb b/spec/mongo/operation/collections_info_spec.rb index 5ba9fbe6db..59c38c4c11 100644 --- a/spec/mongo/operation/collections_info_spec.rb +++ b/spec/mongo/operation/collections_info_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/command_spec.rb b/spec/mongo/operation/command_spec.rb index fa5cae6c34..a6866fe043 100644 --- a/spec/mongo/operation/command_spec.rb +++ b/spec/mongo/operation/command_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/create/op_msg_spec.rb b/spec/mongo/operation/create/op_msg_spec.rb index e1dac64d03..f4033611b8 100644 --- a/spec/mongo/operation/create/op_msg_spec.rb +++ b/spec/mongo/operation/create/op_msg_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/create_index_spec.rb b/spec/mongo/operation/create_index_spec.rb index 3da7598d5a..25a9f4d0f6 100644 --- a/spec/mongo/operation/create_index_spec.rb +++ b/spec/mongo/operation/create_index_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/create_user_spec.rb b/spec/mongo/operation/create_user_spec.rb index 7fddfe05ae..aebedcbb5e 100644 --- a/spec/mongo/operation/create_user_spec.rb +++ b/spec/mongo/operation/create_user_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/delete/bulk_spec.rb b/spec/mongo/operation/delete/bulk_spec.rb index ffada7c3b1..410e434fa5 100644 --- a/spec/mongo/operation/delete/bulk_spec.rb +++ b/spec/mongo/operation/delete/bulk_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/delete/op_msg_spec.rb b/spec/mongo/operation/delete/op_msg_spec.rb index 1ba5df3e90..ab163e3840 100644 --- a/spec/mongo/operation/delete/op_msg_spec.rb +++ b/spec/mongo/operation/delete/op_msg_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/delete_spec.rb b/spec/mongo/operation/delete_spec.rb index 1e6ff8c9d0..d45e8d40aa 100644 --- a/spec/mongo/operation/delete_spec.rb +++ b/spec/mongo/operation/delete_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/drop_index_spec.rb b/spec/mongo/operation/drop_index_spec.rb index 4fe70c6608..17d976d6cf 100644 --- a/spec/mongo/operation/drop_index_spec.rb +++ b/spec/mongo/operation/drop_index_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/find/builder/flags_spec.rb b/spec/mongo/operation/find/builder/flags_spec.rb index a2b480d707..abaec7d56a 100644 --- a/spec/mongo/operation/find/builder/flags_spec.rb +++ b/spec/mongo/operation/find/builder/flags_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/operation/find/builder/modifiers_spec.rb b/spec/mongo/operation/find/builder/modifiers_spec.rb index e1d472974f..28aedc79d4 100644 --- a/spec/mongo/operation/find/builder/modifiers_spec.rb +++ b/spec/mongo/operation/find/builder/modifiers_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/operation/indexes_spec.rb b/spec/mongo/operation/indexes_spec.rb index 96ac77a013..8343e203c2 100644 --- a/spec/mongo/operation/indexes_spec.rb +++ b/spec/mongo/operation/indexes_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/insert/bulk_spec.rb b/spec/mongo/operation/insert/bulk_spec.rb index 9aa30bc664..19934b8fda 100644 --- a/spec/mongo/operation/insert/bulk_spec.rb +++ b/spec/mongo/operation/insert/bulk_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/insert/op_msg_spec.rb b/spec/mongo/operation/insert/op_msg_spec.rb index c9966ea243..9da07f2ab6 100644 --- a/spec/mongo/operation/insert/op_msg_spec.rb +++ b/spec/mongo/operation/insert/op_msg_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/insert_spec.rb b/spec/mongo/operation/insert_spec.rb index 516a65c115..2c15d0fef6 100644 --- a/spec/mongo/operation/insert_spec.rb +++ b/spec/mongo/operation/insert_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/limited_spec.rb b/spec/mongo/operation/limited_spec.rb index 69101a9888..17ff87e882 100644 --- a/spec/mongo/operation/limited_spec.rb +++ b/spec/mongo/operation/limited_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/map_reduce_spec.rb b/spec/mongo/operation/map_reduce_spec.rb index d038675e8d..0ac59a7cc0 100644 --- a/spec/mongo/operation/map_reduce_spec.rb +++ b/spec/mongo/operation/map_reduce_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/read_preference_legacy_spec.rb b/spec/mongo/operation/read_preference_legacy_spec.rb index 1fb4ff5e34..6a3c06a711 100644 --- a/spec/mongo/operation/read_preference_legacy_spec.rb +++ b/spec/mongo/operation/read_preference_legacy_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/read_preference_op_msg_spec.rb b/spec/mongo/operation/read_preference_op_msg_spec.rb index 5a7c45fee2..6788503813 100644 --- a/spec/mongo/operation/read_preference_op_msg_spec.rb +++ b/spec/mongo/operation/read_preference_op_msg_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/remove_user_spec.rb b/spec/mongo/operation/remove_user_spec.rb index 2755430eee..2ddc989303 100644 --- a/spec/mongo/operation/remove_user_spec.rb +++ b/spec/mongo/operation/remove_user_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/result_spec.rb b/spec/mongo/operation/result_spec.rb index 224c5fa07c..e1ba8035e7 100644 --- a/spec/mongo/operation/result_spec.rb +++ b/spec/mongo/operation/result_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/specifiable_spec.rb b/spec/mongo/operation/specifiable_spec.rb index e67a078cd7..8dd01d85fc 100644 --- a/spec/mongo/operation/specifiable_spec.rb +++ b/spec/mongo/operation/specifiable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/update/bulk_spec.rb b/spec/mongo/operation/update/bulk_spec.rb index 0d2b13de0c..55464bdc7c 100644 --- a/spec/mongo/operation/update/bulk_spec.rb +++ b/spec/mongo/operation/update/bulk_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/update/op_msg_spec.rb b/spec/mongo/operation/update/op_msg_spec.rb index 88e8dd3681..da0e25bed5 100644 --- a/spec/mongo/operation/update/op_msg_spec.rb +++ b/spec/mongo/operation/update/op_msg_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/update_spec.rb b/spec/mongo/operation/update_spec.rb index aa56402435..d80e39e757 100644 --- a/spec/mongo/operation/update_spec.rb +++ b/spec/mongo/operation/update_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/operation/update_user_spec.rb b/spec/mongo/operation/update_user_spec.rb index 427a27840b..ba5113a0c2 100644 --- a/spec/mongo/operation/update_user_spec.rb +++ b/spec/mongo/operation/update_user_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/options/redacted_spec.rb b/spec/mongo/options/redacted_spec.rb index af87ded477..21a677ef26 100644 --- a/spec/mongo/options/redacted_spec.rb +++ b/spec/mongo/options/redacted_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' @@ -350,4 +350,4 @@ end end end -end \ No newline at end of file +end diff --git a/spec/mongo/protocol/caching_hash_spec.rb b/spec/mongo/protocol/caching_hash_spec.rb index e92c756679..8598425ac9 100644 --- a/spec/mongo/protocol/caching_hash_spec.rb +++ b/spec/mongo/protocol/caching_hash_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/protocol/compressed_spec.rb b/spec/mongo/protocol/compressed_spec.rb index c1dd31b27b..6858c7bb7b 100644 --- a/spec/mongo/protocol/compressed_spec.rb +++ b/spec/mongo/protocol/compressed_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/protocol/get_more_spec.rb b/spec/mongo/protocol/get_more_spec.rb index 3e1aa68726..2670fb49f3 100644 --- a/spec/mongo/protocol/get_more_spec.rb +++ b/spec/mongo/protocol/get_more_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/protocol' diff --git a/spec/mongo/protocol/kill_cursors_spec.rb b/spec/mongo/protocol/kill_cursors_spec.rb index 8008309f9c..b54ebf901c 100644 --- a/spec/mongo/protocol/kill_cursors_spec.rb +++ b/spec/mongo/protocol/kill_cursors_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/protocol' diff --git a/spec/mongo/protocol/msg_spec.rb b/spec/mongo/protocol/msg_spec.rb index 2a23a95a1b..9c28da0196 100644 --- a/spec/mongo/protocol/msg_spec.rb +++ b/spec/mongo/protocol/msg_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/protocol' diff --git a/spec/mongo/protocol/query_spec.rb b/spec/mongo/protocol/query_spec.rb index f7aa375c6d..14801d69e6 100644 --- a/spec/mongo/protocol/query_spec.rb +++ b/spec/mongo/protocol/query_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/protocol' diff --git a/spec/mongo/protocol/registry_spec.rb b/spec/mongo/protocol/registry_spec.rb index 6bd60bb191..d56b5fea03 100644 --- a/spec/mongo/protocol/registry_spec.rb +++ b/spec/mongo/protocol/registry_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/protocol/reply_spec.rb b/spec/mongo/protocol/reply_spec.rb index 7ca5124408..c9e1447ca6 100644 --- a/spec/mongo/protocol/reply_spec.rb +++ b/spec/mongo/protocol/reply_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/query_cache_middleware_spec.rb b/spec/mongo/query_cache_middleware_spec.rb index 96d9df8e0c..b02cc6a7df 100644 --- a/spec/mongo/query_cache_middleware_spec.rb +++ b/spec/mongo/query_cache_middleware_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require "spec_helper" diff --git a/spec/mongo/query_cache_spec.rb b/spec/mongo/query_cache_spec.rb index a0f8f033e7..a644facf14 100644 --- a/spec/mongo/query_cache_spec.rb +++ b/spec/mongo/query_cache_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/retryable_spec.rb b/spec/mongo/retryable_spec.rb index c1e3388dcd..2c9947816f 100644 --- a/spec/mongo/retryable_spec.rb +++ b/spec/mongo/retryable_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/semaphore_spec.rb b/spec/mongo/semaphore_spec.rb index 8e493c8bcf..ba39ee88c7 100644 --- a/spec/mongo/semaphore_spec.rb +++ b/spec/mongo/semaphore_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/server/app_metadata_spec.rb b/spec/mongo/server/app_metadata_spec.rb index 72ea2a989b..80b9c6cdca 100644 --- a/spec/mongo/server/app_metadata_spec.rb +++ b/spec/mongo/server/app_metadata_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/connection_auth_spec.rb b/spec/mongo/server/connection_auth_spec.rb index f3e64ea454..ac42e30757 100644 --- a/spec/mongo/server/connection_auth_spec.rb +++ b/spec/mongo/server/connection_auth_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/connection_common_spec.rb b/spec/mongo/server/connection_common_spec.rb index ea65f09e61..7f79b88614 100644 --- a/spec/mongo/server/connection_common_spec.rb +++ b/spec/mongo/server/connection_common_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/server/connection_pool/populator_spec.rb b/spec/mongo/server/connection_pool/populator_spec.rb index e9b30e5ffe..3b6e88363a 100644 --- a/spec/mongo/server/connection_pool/populator_spec.rb +++ b/spec/mongo/server/connection_pool/populator_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/connection_pool_spec.rb b/spec/mongo/server/connection_pool_spec.rb index 2aaaf4b346..1594cc0b62 100644 --- a/spec/mongo/server/connection_pool_spec.rb +++ b/spec/mongo/server/connection_pool_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/connection_spec.rb b/spec/mongo/server/connection_spec.rb index 3fcac8ca07..f1e0dabc56 100644 --- a/spec/mongo/server/connection_spec.rb +++ b/spec/mongo/server/connection_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/description/features_spec.rb b/spec/mongo/server/description/features_spec.rb index fef377ba78..b69beaa1ff 100644 --- a/spec/mongo/server/description/features_spec.rb +++ b/spec/mongo/server/description/features_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/description_query_methods_spec.rb b/spec/mongo/server/description_query_methods_spec.rb index e0f789a872..7cbaee4301 100644 --- a/spec/mongo/server/description_query_methods_spec.rb +++ b/spec/mongo/server/description_query_methods_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/description_spec.rb b/spec/mongo/server/description_spec.rb index d0a6a510b1..4038ca7d82 100644 --- a/spec/mongo/server/description_spec.rb +++ b/spec/mongo/server/description_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/monitor/app_metadata_spec.rb b/spec/mongo/server/monitor/app_metadata_spec.rb index dcf6269ee3..6627a1a8b0 100644 --- a/spec/mongo/server/monitor/app_metadata_spec.rb +++ b/spec/mongo/server/monitor/app_metadata_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/server/monitor/connection_spec.rb b/spec/mongo/server/monitor/connection_spec.rb index 48969b67ac..8d4bf9591d 100644 --- a/spec/mongo/server/monitor/connection_spec.rb +++ b/spec/mongo/server/monitor/connection_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/monitor_spec.rb b/spec/mongo/server/monitor_spec.rb index d81be08971..5e93b3a384 100644 --- a/spec/mongo/server/monitor_spec.rb +++ b/spec/mongo/server/monitor_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/push_monitor_spec.rb b/spec/mongo/server/push_monitor_spec.rb index 3400f9877e..bdfdd019d2 100644 --- a/spec/mongo/server/push_monitor_spec.rb +++ b/spec/mongo/server/push_monitor_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server/round_trip_time_averager_spec.rb b/spec/mongo/server/round_trip_time_averager_spec.rb index f22e694593..9e885d2246 100644 --- a/spec/mongo/server/round_trip_time_averager_spec.rb +++ b/spec/mongo/server/round_trip_time_averager_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/server_selector/nearest_spec.rb b/spec/mongo/server_selector/nearest_spec.rb index b2e827a442..30090bc4b0 100644 --- a/spec/mongo/server_selector/nearest_spec.rb +++ b/spec/mongo/server_selector/nearest_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/server_selector' diff --git a/spec/mongo/server_selector/primary_preferred_spec.rb b/spec/mongo/server_selector/primary_preferred_spec.rb index ff05d5a297..a351b59b00 100644 --- a/spec/mongo/server_selector/primary_preferred_spec.rb +++ b/spec/mongo/server_selector/primary_preferred_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/server_selector' diff --git a/spec/mongo/server_selector/primary_spec.rb b/spec/mongo/server_selector/primary_spec.rb index 07227db627..3d2ba70f86 100644 --- a/spec/mongo/server_selector/primary_spec.rb +++ b/spec/mongo/server_selector/primary_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/server_selector' diff --git a/spec/mongo/server_selector/secondary_preferred_spec.rb b/spec/mongo/server_selector/secondary_preferred_spec.rb index 8654c272e4..492a0f4b14 100644 --- a/spec/mongo/server_selector/secondary_preferred_spec.rb +++ b/spec/mongo/server_selector/secondary_preferred_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/server_selector' diff --git a/spec/mongo/server_selector/secondary_spec.rb b/spec/mongo/server_selector/secondary_spec.rb index a5752eb319..f9fc4a68ce 100644 --- a/spec/mongo/server_selector/secondary_spec.rb +++ b/spec/mongo/server_selector/secondary_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/server_selector' diff --git a/spec/mongo/server_selector_spec.rb b/spec/mongo/server_selector_spec.rb index 6ca5d17c9f..96382d46da 100644 --- a/spec/mongo/server_selector_spec.rb +++ b/spec/mongo/server_selector_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' require 'support/shared/server_selector' diff --git a/spec/mongo/server_spec.rb b/spec/mongo/server_spec.rb index 69e1601c66..8de783d092 100644 --- a/spec/mongo/server_spec.rb +++ b/spec/mongo/server_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/session/server_session_spec.rb b/spec/mongo/session/server_session_spec.rb index df4fa001f2..c80a1ecab1 100644 --- a/spec/mongo/session/server_session_spec.rb +++ b/spec/mongo/session/server_session_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/session/session_pool_spec.rb b/spec/mongo/session/session_pool_spec.rb index 6d196dff0f..1dedebed25 100644 --- a/spec/mongo/session/session_pool_spec.rb +++ b/spec/mongo/session/session_pool_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/session_spec.rb b/spec/mongo/session_spec.rb index 86868e1dec..6de42475a8 100644 --- a/spec/mongo/session_spec.rb +++ b/spec/mongo/session_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/session_transaction_spec.rb b/spec/mongo/session_transaction_spec.rb index ee95f400c2..851b08433f 100644 --- a/spec/mongo/session_transaction_spec.rb +++ b/spec/mongo/session_transaction_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/socket/ssl_spec.rb b/spec/mongo/socket/ssl_spec.rb index aa02a428d3..d3d7b5fa9d 100644 --- a/spec/mongo/socket/ssl_spec.rb +++ b/spec/mongo/socket/ssl_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/socket/tcp_spec.rb b/spec/mongo/socket/tcp_spec.rb index 1941aefe48..940340d704 100644 --- a/spec/mongo/socket/tcp_spec.rb +++ b/spec/mongo/socket/tcp_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/socket/unix_spec.rb b/spec/mongo/socket/unix_spec.rb index ea6f8fdf9d..d8c0c2adc0 100644 --- a/spec/mongo/socket/unix_spec.rb +++ b/spec/mongo/socket/unix_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/socket_spec.rb b/spec/mongo/socket_spec.rb index ae986bf39a..f6ef8cd696 100644 --- a/spec/mongo/socket_spec.rb +++ b/spec/mongo/socket_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/srv/monitor_spec.rb b/spec/mongo/srv/monitor_spec.rb index 4f69ee7f99..aaa50c16f4 100644 --- a/spec/mongo/srv/monitor_spec.rb +++ b/spec/mongo/srv/monitor_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/srv/result_spec.rb b/spec/mongo/srv/result_spec.rb index c852484e2b..227679a459 100644 --- a/spec/mongo/srv/result_spec.rb +++ b/spec/mongo/srv/result_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/timeout_spec.rb b/spec/mongo/timeout_spec.rb index 3915ad0228..7e10649369 100644 --- a/spec/mongo/timeout_spec.rb +++ b/spec/mongo/timeout_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/tls_context_hooks_spec.rb b/spec/mongo/tls_context_hooks_spec.rb index 3b297f4635..2c58b5b645 100644 --- a/spec/mongo/tls_context_hooks_spec.rb +++ b/spec/mongo/tls_context_hooks_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/uri/options_mapper_spec.rb b/spec/mongo/uri/options_mapper_spec.rb index ee63e616cc..4fa2ce236b 100644 --- a/spec/mongo/uri/options_mapper_spec.rb +++ b/spec/mongo/uri/options_mapper_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/uri/srv_protocol_spec.rb b/spec/mongo/uri/srv_protocol_spec.rb index 8d82e7797a..af21a43161 100644 --- a/spec/mongo/uri/srv_protocol_spec.rb +++ b/spec/mongo/uri/srv_protocol_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/uri_option_parsing_spec.rb b/spec/mongo/uri_option_parsing_spec.rb index bc45f3d7e7..a876ecfdd3 100644 --- a/spec/mongo/uri_option_parsing_spec.rb +++ b/spec/mongo/uri_option_parsing_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/uri_spec.rb b/spec/mongo/uri_spec.rb index da42f4e1f2..f2e2e55e4f 100644 --- a/spec/mongo/uri_spec.rb +++ b/spec/mongo/uri_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/utils_spec.rb b/spec/mongo/utils_spec.rb index 6ab5078d85..8d54059754 100644 --- a/spec/mongo/utils_spec.rb +++ b/spec/mongo/utils_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/mongo/write_concern/acknowledged_spec.rb b/spec/mongo/write_concern/acknowledged_spec.rb index 430788b2c1..d6be80ee21 100644 --- a/spec/mongo/write_concern/acknowledged_spec.rb +++ b/spec/mongo/write_concern/acknowledged_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/write_concern/unacknowledged_spec.rb b/spec/mongo/write_concern/unacknowledged_spec.rb index acb3a96289..d0bfb6e0a6 100644 --- a/spec/mongo/write_concern/unacknowledged_spec.rb +++ b/spec/mongo/write_concern/unacknowledged_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/mongo/write_concern_spec.rb b/spec/mongo/write_concern_spec.rb index 3a5c1feb38..34d80614ec 100644 --- a/spec/mongo/write_concern_spec.rb +++ b/spec/mongo/write_concern_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/runners/auth.rb b/spec/runners/auth.rb index 386bec0fea..dd95b49741 100644 --- a/spec/runners/auth.rb +++ b/spec/runners/auth.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/change_streams/outcome.rb b/spec/runners/change_streams/outcome.rb index 32620f7d76..00c6dd56b9 100644 --- a/spec/runners/change_streams/outcome.rb +++ b/spec/runners/change_streams/outcome.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/change_streams/spec.rb b/spec/runners/change_streams/spec.rb index e5630e7d7b..7ffd7c32e9 100644 --- a/spec/runners/change_streams/spec.rb +++ b/spec/runners/change_streams/spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/change_streams/test.rb b/spec/runners/change_streams/test.rb index 7d38a9ff50..e7b2799b6a 100644 --- a/spec/runners/change_streams/test.rb +++ b/spec/runners/change_streams/test.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/cmap.rb b/spec/runners/cmap.rb index 644daea162..7d2da534ab 100644 --- a/spec/runners/cmap.rb +++ b/spec/runners/cmap.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'runners/cmap/verifier' diff --git a/spec/runners/cmap/verifier.rb b/spec/runners/cmap/verifier.rb index 9dac0ea709..7081364ddc 100644 --- a/spec/runners/cmap/verifier.rb +++ b/spec/runners/cmap/verifier.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/spec/runners/command_monitoring.rb b/spec/runners/command_monitoring.rb index 9d37c840ae..e1269ce0b8 100644 --- a/spec/runners/command_monitoring.rb +++ b/spec/runners/command_monitoring.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/connection_string.rb b/spec/runners/connection_string.rb index 79b21eaf89..2426cfb714 100644 --- a/spec/runners/connection_string.rb +++ b/spec/runners/connection_string.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/crud.rb b/spec/runners/crud.rb index c0c1a7c651..765597bac0 100644 --- a/spec/runners/crud.rb +++ b/spec/runners/crud.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/crud/context.rb b/spec/runners/crud/context.rb index d315de9e91..99a83e84e7 100644 --- a/spec/runners/crud/context.rb +++ b/spec/runners/crud/context.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/spec/runners/crud/operation.rb b/spec/runners/crud/operation.rb index 7010bb735d..8e93bccd2d 100644 --- a/spec/runners/crud/operation.rb +++ b/spec/runners/crud/operation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/crud/outcome.rb b/spec/runners/crud/outcome.rb index bd8d773f10..be6f8bd9ca 100644 --- a/spec/runners/crud/outcome.rb +++ b/spec/runners/crud/outcome.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/crud/requirement.rb b/spec/runners/crud/requirement.rb index 88b35d0c95..641de1ef75 100644 --- a/spec/runners/crud/requirement.rb +++ b/spec/runners/crud/requirement.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo module CRUD diff --git a/spec/runners/crud/spec.rb b/spec/runners/crud/spec.rb index cb20cb14fc..0d830a4a6a 100644 --- a/spec/runners/crud/spec.rb +++ b/spec/runners/crud/spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo module CRUD diff --git a/spec/runners/crud/test.rb b/spec/runners/crud/test.rb index a049ecf413..57793e23c6 100644 --- a/spec/runners/crud/test.rb +++ b/spec/runners/crud/test.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo module CRUD diff --git a/spec/runners/crud/test_base.rb b/spec/runners/crud/test_base.rb index 17a872f83d..c6fcad02d4 100644 --- a/spec/runners/crud/test_base.rb +++ b/spec/runners/crud/test_base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo module CRUD diff --git a/spec/runners/crud/verifier.rb b/spec/runners/crud/verifier.rb index 147d9ba16f..4c69909618 100644 --- a/spec/runners/crud/verifier.rb +++ b/spec/runners/crud/verifier.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2019-2020 MongoDB Inc. # diff --git a/spec/runners/gridfs.rb b/spec/runners/gridfs.rb index 495fc1f0e3..28135c8aa7 100644 --- a/spec/runners/gridfs.rb +++ b/spec/runners/gridfs.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/read_write_concern_document.rb b/spec/runners/read_write_concern_document.rb index 26c7892b08..564c8a13d8 100644 --- a/spec/runners/read_write_concern_document.rb +++ b/spec/runners/read_write_concern_document.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module ReadWriteConcernDocument diff --git a/spec/runners/sdam.rb b/spec/runners/sdam.rb index 259fa4128f..4eccac9591 100644 --- a/spec/runners/sdam.rb +++ b/spec/runners/sdam.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Matcher for determining if the server is of the expected type according to # the test. diff --git a/spec/runners/sdam/verifier.rb b/spec/runners/sdam/verifier.rb index b4e1d5f905..adfafc214f 100644 --- a/spec/runners/sdam/verifier.rb +++ b/spec/runners/sdam/verifier.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Sdam class Verifier diff --git a/spec/runners/server_selection.rb b/spec/runners/server_selection.rb index fb6ff7001f..7a35b77d26 100644 --- a/spec/runners/server_selection.rb +++ b/spec/runners/server_selection.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo module ServerSelection diff --git a/spec/runners/server_selection_rtt.rb b/spec/runners/server_selection_rtt.rb index 5b41ab4eb1..f5e2b175fe 100644 --- a/spec/runners/server_selection_rtt.rb +++ b/spec/runners/server_selection_rtt.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo module ServerSelection diff --git a/spec/runners/transactions.rb b/spec/runners/transactions.rb index 1407241d2b..4b1717b91c 100644 --- a/spec/runners/transactions.rb +++ b/spec/runners/transactions.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/transactions/operation.rb b/spec/runners/transactions/operation.rb index cb34eba3d6..3f74c65305 100644 --- a/spec/runners/transactions/operation.rb +++ b/spec/runners/transactions/operation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/transactions/spec.rb b/spec/runners/transactions/spec.rb index 18823aeb5a..7fc5d906f3 100644 --- a/spec/runners/transactions/spec.rb +++ b/spec/runners/transactions/spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/transactions/test.rb b/spec/runners/transactions/test.rb index 26db187bd7..83fc4b50ea 100644 --- a/spec/runners/transactions/test.rb +++ b/spec/runners/transactions/test.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2014-2020 MongoDB Inc. # diff --git a/spec/runners/unified.rb b/spec/runners/unified.rb index 66161e01f9..7ab3b1904c 100644 --- a/spec/runners/unified.rb +++ b/spec/runners/unified.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'support/using_hash' require 'runners/unified/error' diff --git a/spec/runners/unified/assertions.rb b/spec/runners/unified/assertions.rb index 699fcec98f..722b11ad83 100644 --- a/spec/runners/unified/assertions.rb +++ b/spec/runners/unified/assertions.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/change_stream_operations.rb b/spec/runners/unified/change_stream_operations.rb index 1b4f890a16..343486db04 100644 --- a/spec/runners/unified/change_stream_operations.rb +++ b/spec/runners/unified/change_stream_operations.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/client_side_encryption_operations.rb b/spec/runners/unified/client_side_encryption_operations.rb index c7519fa963..216efc8732 100644 --- a/spec/runners/unified/client_side_encryption_operations.rb +++ b/spec/runners/unified/client_side_encryption_operations.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified module ClientSideEncryptionOperations diff --git a/spec/runners/unified/crud_operations.rb b/spec/runners/unified/crud_operations.rb index e1cad3041d..9274bf62df 100644 --- a/spec/runners/unified/crud_operations.rb +++ b/spec/runners/unified/crud_operations.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/ddl_operations.rb b/spec/runners/unified/ddl_operations.rb index 7424ffab93..185a251b7a 100644 --- a/spec/runners/unified/ddl_operations.rb +++ b/spec/runners/unified/ddl_operations.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/entity_map.rb b/spec/runners/unified/entity_map.rb index 503aa2e644..f585bb62d2 100644 --- a/spec/runners/unified/entity_map.rb +++ b/spec/runners/unified/entity_map.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified class EntityMap diff --git a/spec/runners/unified/error.rb b/spec/runners/unified/error.rb index 3f59bab731..22a6107260 100644 --- a/spec/runners/unified/error.rb +++ b/spec/runners/unified/error.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/event_subscriber.rb b/spec/runners/unified/event_subscriber.rb index f5cb40fd28..953d3402a8 100644 --- a/spec/runners/unified/event_subscriber.rb +++ b/spec/runners/unified/event_subscriber.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mrss/event_subscriber' diff --git a/spec/runners/unified/exceptions.rb b/spec/runners/unified/exceptions.rb index cd107150b6..6e87907b37 100644 --- a/spec/runners/unified/exceptions.rb +++ b/spec/runners/unified/exceptions.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/grid_fs_operations.rb b/spec/runners/unified/grid_fs_operations.rb index ffc321d3b9..c5cad546bf 100644 --- a/spec/runners/unified/grid_fs_operations.rb +++ b/spec/runners/unified/grid_fs_operations.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/support_operations.rb b/spec/runners/unified/support_operations.rb index c83d98448a..f8b4e53af3 100644 --- a/spec/runners/unified/support_operations.rb +++ b/spec/runners/unified/support_operations.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index c5bd6f453a..d1f21c5f60 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'runners/crud/requirement' require 'runners/unified/client_side_encryption_operations' diff --git a/spec/runners/unified/test_group.rb b/spec/runners/unified/test_group.rb index dea53fad87..63545cfc25 100644 --- a/spec/runners/unified/test_group.rb +++ b/spec/runners/unified/test_group.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/runners/unified/thread_operations.rb b/spec/runners/unified/thread_operations.rb index b19aa8cbe6..ef63cf5fb0 100644 --- a/spec/runners/unified/thread_operations.rb +++ b/spec/runners/unified/thread_operations.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Unified diff --git a/spec/solo/clean_exit_spec.rb b/spec/solo/clean_exit_spec.rb index 12d5d66886..1693b379e9 100644 --- a/spec/solo/clean_exit_spec.rb +++ b/spec/solo/clean_exit_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'mongo' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6e2d32f432..3684894c54 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/auth_spec.rb b/spec/spec_tests/auth_spec.rb index 4d4377c14a..f0125b698e 100644 --- a/spec/spec_tests/auth_spec.rb +++ b/spec/spec_tests/auth_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/change_streams_unified_spec.rb b/spec/spec_tests/change_streams_unified_spec.rb index cb2cb07ce8..c66c0b0dab 100644 --- a/spec/spec_tests/change_streams_unified_spec.rb +++ b/spec/spec_tests/change_streams_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/client_side_encryption_spec.rb b/spec/spec_tests/client_side_encryption_spec.rb index 67b01299a8..d45f670406 100644 --- a/spec/spec_tests/client_side_encryption_spec.rb +++ b/spec/spec_tests/client_side_encryption_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/client_side_encryption_unified_spec.rb b/spec/spec_tests/client_side_encryption_unified_spec.rb index 5422b93e19..769e663390 100644 --- a/spec/spec_tests/client_side_encryption_unified_spec.rb +++ b/spec/spec_tests/client_side_encryption_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/cmap_spec.rb b/spec/spec_tests/cmap_spec.rb index bfd67cfbf9..994c818b90 100644 --- a/spec/spec_tests/cmap_spec.rb +++ b/spec/spec_tests/cmap_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/collection_management_spec.rb b/spec/spec_tests/collection_management_spec.rb index ec0014ec18..260f91838b 100644 --- a/spec/spec_tests/collection_management_spec.rb +++ b/spec/spec_tests/collection_management_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/command_monitoring_unified_spec.rb b/spec/spec_tests/command_monitoring_unified_spec.rb index 54702aaf25..2edcfdc8ab 100644 --- a/spec/spec_tests/command_monitoring_unified_spec.rb +++ b/spec/spec_tests/command_monitoring_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/connection_string_spec.rb b/spec/spec_tests/connection_string_spec.rb index ae93a301d5..a8cf19a5c9 100644 --- a/spec/spec_tests/connection_string_spec.rb +++ b/spec/spec_tests/connection_string_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/crud_spec.rb b/spec/spec_tests/crud_spec.rb index 2550298fcf..cef0ae3abd 100644 --- a/spec/spec_tests/crud_spec.rb +++ b/spec/spec_tests/crud_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/crud_unified_spec.rb b/spec/spec_tests/crud_unified_spec.rb index 08d0ccc942..2b07f9894f 100644 --- a/spec/spec_tests/crud_unified_spec.rb +++ b/spec/spec_tests/crud_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/gridfs_spec.rb b/spec/spec_tests/gridfs_spec.rb index 956ab789f5..c0e9cdb1a8 100644 --- a/spec/spec_tests/gridfs_spec.rb +++ b/spec/spec_tests/gridfs_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/gridfs_unified_spec.rb b/spec/spec_tests/gridfs_unified_spec.rb index 6c0298e5a9..cd7e75a12e 100644 --- a/spec/spec_tests/gridfs_unified_spec.rb +++ b/spec/spec_tests/gridfs_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/load_balancers_spec.rb b/spec/spec_tests/load_balancers_spec.rb index 0d92e0b2e4..497952c580 100644 --- a/spec/spec_tests/load_balancers_spec.rb +++ b/spec/spec_tests/load_balancers_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/max_staleness_spec.rb b/spec/spec_tests/max_staleness_spec.rb index af631e2935..db964d4e70 100644 --- a/spec/spec_tests/max_staleness_spec.rb +++ b/spec/spec_tests/max_staleness_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/read_write_concern_connection_string_spec.rb b/spec/spec_tests/read_write_concern_connection_string_spec.rb index b7641729d0..b763258efc 100644 --- a/spec/spec_tests/read_write_concern_connection_string_spec.rb +++ b/spec/spec_tests/read_write_concern_connection_string_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/read_write_concern_document_spec.rb b/spec/spec_tests/read_write_concern_document_spec.rb index 451d3fd129..828b4d6edb 100644 --- a/spec/spec_tests/read_write_concern_document_spec.rb +++ b/spec/spec_tests/read_write_concern_document_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' require 'runners/read_write_concern_document' diff --git a/spec/spec_tests/read_write_concern_operaton_spec.rb b/spec/spec_tests/read_write_concern_operaton_spec.rb index a65c0437e7..d3ca5a3e2e 100644 --- a/spec/spec_tests/read_write_concern_operaton_spec.rb +++ b/spec/spec_tests/read_write_concern_operaton_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/retryable_reads_spec.rb b/spec/spec_tests/retryable_reads_spec.rb index d10c5a332a..7efead10f2 100644 --- a/spec/spec_tests/retryable_reads_spec.rb +++ b/spec/spec_tests/retryable_reads_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/retryable_reads_unified_spec.rb b/spec/spec_tests/retryable_reads_unified_spec.rb index c4749d6948..e57d187d7f 100644 --- a/spec/spec_tests/retryable_reads_unified_spec.rb +++ b/spec/spec_tests/retryable_reads_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/retryable_writes_spec.rb b/spec/spec_tests/retryable_writes_spec.rb index 2e512b45c3..e555850966 100644 --- a/spec/spec_tests/retryable_writes_spec.rb +++ b/spec/spec_tests/retryable_writes_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/retryable_writes_unified_spec.rb b/spec/spec_tests/retryable_writes_unified_spec.rb index de862d7a55..94120be589 100644 --- a/spec/spec_tests/retryable_writes_unified_spec.rb +++ b/spec/spec_tests/retryable_writes_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/sdam_monitoring_spec.rb b/spec/spec_tests/sdam_monitoring_spec.rb index e6517effe8..fa35e40ea7 100644 --- a/spec/spec_tests/sdam_monitoring_spec.rb +++ b/spec/spec_tests/sdam_monitoring_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/sdam_spec.rb b/spec/spec_tests/sdam_spec.rb index b9484691cb..c5858e479f 100644 --- a/spec/spec_tests/sdam_spec.rb +++ b/spec/spec_tests/sdam_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/sdam_unified_spec.rb b/spec/spec_tests/sdam_unified_spec.rb index 7165ddfa52..8454edc004 100644 --- a/spec/spec_tests/sdam_unified_spec.rb +++ b/spec/spec_tests/sdam_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/seed_list_discovery_spec.rb b/spec/spec_tests/seed_list_discovery_spec.rb index 0e71882d91..52f75bd283 100644 --- a/spec/spec_tests/seed_list_discovery_spec.rb +++ b/spec/spec_tests/seed_list_discovery_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/server_selection_rtt_spec.rb b/spec/spec_tests/server_selection_rtt_spec.rb index 5fe7c002a6..a12a47e832 100644 --- a/spec/spec_tests/server_selection_rtt_spec.rb +++ b/spec/spec_tests/server_selection_rtt_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/server_selection_spec.rb b/spec/spec_tests/server_selection_spec.rb index 42c325bb03..f6f2b5fc4e 100644 --- a/spec/spec_tests/server_selection_spec.rb +++ b/spec/spec_tests/server_selection_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/sessions_unified_spec.rb b/spec/spec_tests/sessions_unified_spec.rb index 8217299205..26bfc91323 100644 --- a/spec/spec_tests/sessions_unified_spec.rb +++ b/spec/spec_tests/sessions_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/transactions_api_spec.rb b/spec/spec_tests/transactions_api_spec.rb index 307ab1a316..881e53e203 100644 --- a/spec/spec_tests/transactions_api_spec.rb +++ b/spec/spec_tests/transactions_api_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/transactions_spec.rb b/spec/spec_tests/transactions_spec.rb index 94eebe4055..bf24604803 100644 --- a/spec/spec_tests/transactions_spec.rb +++ b/spec/spec_tests/transactions_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/transactions_unified_spec.rb b/spec/spec_tests/transactions_unified_spec.rb index 6b5958c2b9..c81ba9bb78 100644 --- a/spec/spec_tests/transactions_unified_spec.rb +++ b/spec/spec_tests/transactions_unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/unified_spec.rb b/spec/spec_tests/unified_spec.rb index 36c8dd2f51..70ad2f3efd 100644 --- a/spec/spec_tests/unified_spec.rb +++ b/spec/spec_tests/unified_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/spec_tests/uri_options_spec.rb b/spec/spec_tests/uri_options_spec.rb index 9e79cf6c9b..4d8a248c02 100644 --- a/spec/spec_tests/uri_options_spec.rb +++ b/spec/spec_tests/uri_options_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' diff --git a/spec/spec_tests/versioned_api_spec.rb b/spec/spec_tests/versioned_api_spec.rb index d649dd0288..af167cf144 100644 --- a/spec/spec_tests/versioned_api_spec.rb +++ b/spec/spec_tests/versioned_api_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/stress/cleanup_spec.rb b/spec/stress/cleanup_spec.rb index 1defe9e128..c486a43a85 100644 --- a/spec/stress/cleanup_spec.rb +++ b/spec/stress/cleanup_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/stress/connection_pool_stress_spec.rb b/spec/stress/connection_pool_stress_spec.rb index 4c53e5d9cf..6237861840 100644 --- a/spec/stress/connection_pool_stress_spec.rb +++ b/spec/stress/connection_pool_stress_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/stress/connection_pool_timing_spec.rb b/spec/stress/connection_pool_timing_spec.rb index e905a0fe88..0f61bf2e4f 100644 --- a/spec/stress/connection_pool_timing_spec.rb +++ b/spec/stress/connection_pool_timing_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/stress/fork_reconnect_stress_spec.rb b/spec/stress/fork_reconnect_stress_spec.rb index adbe1df08d..00b63858cd 100644 --- a/spec/stress/fork_reconnect_stress_spec.rb +++ b/spec/stress/fork_reconnect_stress_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/stress/push_monitor_close_spec.rb b/spec/stress/push_monitor_close_spec.rb index c79e2fa289..065726c20d 100644 --- a/spec/stress/push_monitor_close_spec.rb +++ b/spec/stress/push_monitor_close_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' diff --git a/spec/support/authorization.rb b/spec/support/authorization.rb index 208f18ce66..da0cd17ca9 100644 --- a/spec/support/authorization.rb +++ b/spec/support/authorization.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2009-2020 MongoDB Inc. # diff --git a/spec/support/aws_utils.rb b/spec/support/aws_utils.rb index 9dc2ec410c..00c54ec662 100644 --- a/spec/support/aws_utils.rb +++ b/spec/support/aws_utils.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all autoload :Byebug, 'byebug' autoload :Paint, 'paint' diff --git a/spec/support/aws_utils/base.rb b/spec/support/aws_utils/base.rb index 57e82b3cc8..065d41069b 100644 --- a/spec/support/aws_utils/base.rb +++ b/spec/support/aws_utils/base.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module AwsUtils class Base diff --git a/spec/support/aws_utils/inspector.rb b/spec/support/aws_utils/inspector.rb index 0f5598640a..cf0d5b4a9a 100644 --- a/spec/support/aws_utils/inspector.rb +++ b/spec/support/aws_utils/inspector.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module AwsUtils class Inspector < Base diff --git a/spec/support/aws_utils/orchestrator.rb b/spec/support/aws_utils/orchestrator.rb index 7ad595cbbb..aca9fb2876 100644 --- a/spec/support/aws_utils/orchestrator.rb +++ b/spec/support/aws_utils/orchestrator.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'securerandom' diff --git a/spec/support/aws_utils/provisioner.rb b/spec/support/aws_utils/provisioner.rb index ecf6a52d13..19a8f5b6d5 100644 --- a/spec/support/aws_utils/provisioner.rb +++ b/spec/support/aws_utils/provisioner.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module AwsUtils class Provisioner < Base diff --git a/spec/support/background_thread_registry.rb b/spec/support/background_thread_registry.rb index b353b7d201..add0038c11 100644 --- a/spec/support/background_thread_registry.rb +++ b/spec/support/background_thread_registry.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'singleton' require 'ostruct' diff --git a/spec/support/certificates/retrieve-atlas-cert b/spec/support/certificates/retrieve-atlas-cert index 6face7d13e..d9e53b64cd 100755 --- a/spec/support/certificates/retrieve-atlas-cert +++ b/spec/support/certificates/retrieve-atlas-cert @@ -1,4 +1,6 @@ #!/usr/bin/env ruby +# frozen_string_literal: true +# rubocop:todo all require 'tmpdir' diff --git a/spec/support/client_registry.rb b/spec/support/client_registry.rb index 1d8971091c..d1b3db1e6f 100644 --- a/spec/support/client_registry.rb +++ b/spec/support/client_registry.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'singleton' diff --git a/spec/support/client_registry_macros.rb b/spec/support/client_registry_macros.rb index 4200301225..ba824265be 100644 --- a/spec/support/client_registry_macros.rb +++ b/spec/support/client_registry_macros.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module ClientRegistryMacros def new_local_client(address, options=nil, &block) diff --git a/spec/support/cluster_tools.rb b/spec/support/cluster_tools.rb index aedde7be4e..253782da98 100644 --- a/spec/support/cluster_tools.rb +++ b/spec/support/cluster_tools.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'singleton' diff --git a/spec/support/common_shortcuts.rb b/spec/support/common_shortcuts.rb index 33a5b6c7f0..6092129778 100644 --- a/spec/support/common_shortcuts.rb +++ b/spec/support/common_shortcuts.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module CommonShortcuts module ClassMethods diff --git a/spec/support/constraints.rb b/spec/support/constraints.rb index c37b7b9b61..47057743fb 100644 --- a/spec/support/constraints.rb +++ b/spec/support/constraints.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Constraints diff --git a/spec/support/crypt.rb b/spec/support/crypt.rb index cf362c0edf..f6337eeb63 100644 --- a/spec/support/crypt.rb +++ b/spec/support/crypt.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Copyright (C) 2009-2020 MongoDB Inc. # diff --git a/spec/support/dns.rb b/spec/support/dns.rb index 86118a7199..d868f7bc3b 100644 --- a/spec/support/dns.rb +++ b/spec/support/dns.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'rubydns' diff --git a/spec/support/json_ext_formatter.rb b/spec/support/json_ext_formatter.rb index a49b05bbe6..b16527201d 100644 --- a/spec/support/json_ext_formatter.rb +++ b/spec/support/json_ext_formatter.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all class JsonExtFormatter < RSpec::Core::Formatters::JsonFormatter RSpec::Core::Formatters.register self, :message, diff --git a/spec/support/keyword_struct.rb b/spec/support/keyword_struct.rb index ab47bbc2af..53c6453a5b 100644 --- a/spec/support/keyword_struct.rb +++ b/spec/support/keyword_struct.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all # Intermediate step between a Struct and an OpenStruct. Allows only designated # field names to be read or written but allows passing fields to constructor diff --git a/spec/support/local_resource_registry.rb b/spec/support/local_resource_registry.rb index f094b3f340..33e72babdd 100644 --- a/spec/support/local_resource_registry.rb +++ b/spec/support/local_resource_registry.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'singleton' diff --git a/spec/support/macros.rb b/spec/support/macros.rb index 2f3cc83c07..645c043b87 100644 --- a/spec/support/macros.rb +++ b/spec/support/macros.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all module Mongo module Macros diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb index 1fccd9db21..c7a828e039 100644 --- a/spec/support/matchers.rb +++ b/spec/support/matchers.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all RSpec::Matchers.define :be_int32 do |num| match do |actual| diff --git a/spec/support/mongos_macros.rb b/spec/support/mongos_macros.rb index b8450016ab..b9b3c78859 100644 --- a/spec/support/mongos_macros.rb +++ b/spec/support/mongos_macros.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all module MongosMacros diff --git a/spec/support/monitoring_ext.rb b/spec/support/monitoring_ext.rb index b6f3fe2932..9268d4e636 100644 --- a/spec/support/monitoring_ext.rb +++ b/spec/support/monitoring_ext.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module Mongo class Monitoring diff --git a/spec/support/primary_socket.rb b/spec/support/primary_socket.rb index d3c8ddafb6..03a92fbc93 100644 --- a/spec/support/primary_socket.rb +++ b/spec/support/primary_socket.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all module PrimarySocket def self.included(base) diff --git a/spec/support/sdam_formatter_integration.rb b/spec/support/sdam_formatter_integration.rb index b453a6bdf3..b4e898c6c7 100644 --- a/spec/support/sdam_formatter_integration.rb +++ b/spec/support/sdam_formatter_integration.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all $sdam_formatter_lock = Mutex.new diff --git a/spec/support/shared/app_metadata.rb b/spec/support/shared/app_metadata.rb index 70bf0cc7fe..390e847245 100644 --- a/spec/support/shared/app_metadata.rb +++ b/spec/support/shared/app_metadata.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all shared_examples 'app metadata document' do let(:app_metadata) do diff --git a/spec/support/shared/auth_context.rb b/spec/support/shared/auth_context.rb index d16fff3605..2e96103263 100644 --- a/spec/support/shared/auth_context.rb +++ b/spec/support/shared/auth_context.rb @@ -1,3 +1,4 @@ +# rubocop:todo all shared_context 'auth unit tests' do let(:generation_manager) do Mongo::Server::ConnectionPool::GenerationManager.new(server: server) diff --git a/spec/support/shared/protocol.rb b/spec/support/shared/protocol.rb index dbb4ce7cbd..fcd861c005 100644 --- a/spec/support/shared/protocol.rb +++ b/spec/support/shared/protocol.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all shared_examples 'message with a header' do let(:collection_name) { 'test' } diff --git a/spec/support/shared/scram_conversation.rb b/spec/support/shared/scram_conversation.rb index 1428ab78c9..9d1d1bc802 100644 --- a/spec/support/shared/scram_conversation.rb +++ b/spec/support/shared/scram_conversation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all shared_context 'scram conversation context' do let(:connection) do diff --git a/spec/support/shared/server_selector.rb b/spec/support/shared/server_selector.rb index d327223c28..6260856a00 100644 --- a/spec/support/shared/server_selector.rb +++ b/spec/support/shared/server_selector.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all shared_context 'server selector' do diff --git a/spec/support/shared/session.rb b/spec/support/shared/session.rb index 4fe6a4d364..7188f5f883 100644 --- a/spec/support/shared/session.rb +++ b/spec/support/shared/session.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all shared_examples 'an operation using a session' do diff --git a/spec/support/spec_config.rb b/spec/support/spec_config.rb index a86b087c6e..ec5aa5c39d 100644 --- a/spec/support/spec_config.rb +++ b/spec/support/spec_config.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'singleton' require 'pathname' diff --git a/spec/support/spec_setup.rb b/spec/support/spec_setup.rb index 4f1fdf35ea..dcfa83a533 100644 --- a/spec/support/spec_setup.rb +++ b/spec/support/spec_setup.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require_relative './spec_config' require_relative './client_registry' diff --git a/spec/support/using_hash.rb b/spec/support/using_hash.rb index db400b3ef1..a80b8a2c8e 100644 --- a/spec/support/using_hash.rb +++ b/spec/support/using_hash.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all class UsingHash < Hash class UsingHashKeyError < KeyError diff --git a/spec/support/utils.rb b/spec/support/utils.rb index 0a206c50e3..fc99bc95dc 100644 --- a/spec/support/utils.rb +++ b/spec/support/utils.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all autoload :Base64, 'base64' autoload :JSON, 'json' From 137e302a53610f0ff02463c9c3180bd522ba6e7e Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 25 Apr 2023 12:01:29 +0200 Subject: [PATCH 118/198] RUBY-3088 Move FLE test configurations to docker (#2709) --- .evergreen/config.yml | 34 +++++++++++++++++++----------- .evergreen/config/axes.yml.erb | 3 ++- .evergreen/config/standard.yml.erb | 29 ++++++++++++++----------- .evergreen/run-tests-docker.sh | 2 +- .evergreen/run-tests-serverless.sh | 8 +++++++ spec/shared | 2 +- 6 files changed, 51 insertions(+), 27 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index a6a85b56fb..0290da04c1 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -609,6 +609,7 @@ axes: display_name: "latest" variables: MONGODB_VERSION: "latest" + CRYPT_SHARED_VERSION: "latest" - id: "6.0" display_name: "6.0" variables: @@ -802,6 +803,10 @@ axes: display_name: ubuntu1804 variables: DOCKER_DISTRO: ubuntu1804 + - id: ubuntu2004 + display_name: ubuntu2004 + variables: + DOCKER_DISTRO: ubuntu2004 - id: "compressor" display_name: Compressor @@ -1471,9 +1476,10 @@ buildvariants: mongodb-version: ['latest'] os: ubuntu2004 fle: helper + docker-distro: ubuntu2004 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" - matrix_name: "fle-6.0" matrix_spec: @@ -1483,9 +1489,10 @@ buildvariants: mongodb-version: ['6.0'] os: ubuntu2004 fle: helper + docker-distro: ubuntu2004 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" - matrix_name: "fle-4.4" matrix_spec: @@ -1493,11 +1500,12 @@ buildvariants: ruby: [ruby-3.1] topology: standalone mongodb-version: ['4.4'] - os: ubuntu1804 + os: ubuntu2004 fle: helper + docker-distro: ubuntu1804 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" - matrix_name: "fle-4.2" matrix_spec: @@ -1505,11 +1513,12 @@ buildvariants: ruby: [ruby-2.5] topology: [standalone, replica-set, sharded-cluster] mongodb-version: ["4.2"] - os: ubuntu1804 + os: ubuntu2004 fle: helper + docker-distro: ubuntu1804 display_name: "FLE: ${topology} ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" - matrix_name: "fle-4.0" matrix_spec: @@ -1517,11 +1526,12 @@ buildvariants: ruby: [ruby-2.6] topology: standalone mongodb-version: ["4.0"] - os: ubuntu1804 + os: ubuntu2004 fle: path + docker-distro: ubuntu1804 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" # https://jira.mongodb.org/browse/RUBY-3217 - matrix_name: "fle-jruby" @@ -1530,9 +1540,9 @@ buildvariants: ruby: [jruby-9.2, jruby-9.3] topology: standalone mongodb-version: ['5.0'] - os: ubuntu1804 + os: ubuntu2004 fle: helper - docker-distro: ubuntu1804 + docker-distro: ubuntu2004 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - name: "test-fle-via-docker" @@ -1795,9 +1805,9 @@ buildvariants: # https://jira.mongodb.org/browse/RUBY-3217 # ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.3"] ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1"] - fle: helper + fle: path display_name: "Atlas serverless ${ruby} single mongos" run_on: - - ubuntu1804-small + - ubuntu2004-small tasks: - name: test-serverless diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index e26b049b45..d925a355b8 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -17,6 +17,7 @@ axes: display_name: "latest" variables: MONGODB_VERSION: "latest" + CRYPT_SHARED_VERSION: "latest" - id: "6.0" display_name: "6.0" variables: @@ -198,7 +199,7 @@ axes: - id: docker-distro display_name: Docker Distro values: - <% %w(debian92 debian10 ubuntu1804).each do |distro| %> + <% %w(debian92 debian10 ubuntu1804 ubuntu2004).each do |distro| %> - id: <%= distro %> display_name: <%= distro %> variables: diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index d8d7cb0524..ec401e2012 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -475,9 +475,10 @@ buildvariants: mongodb-version: ['latest'] os: ubuntu2004 fle: helper + docker-distro: ubuntu2004 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" - matrix_name: "fle-6.0" matrix_spec: @@ -487,9 +488,10 @@ buildvariants: mongodb-version: ['6.0'] os: ubuntu2004 fle: helper + docker-distro: ubuntu2004 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" - matrix_name: "fle-4.4" matrix_spec: @@ -497,11 +499,12 @@ buildvariants: ruby: [ruby-3.1] topology: standalone mongodb-version: ['4.4'] - os: ubuntu1804 + os: ubuntu2004 fle: helper + docker-distro: ubuntu1804 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" - matrix_name: "fle-4.2" matrix_spec: @@ -509,11 +512,12 @@ buildvariants: ruby: [ruby-2.5] topology: [standalone, replica-set, sharded-cluster] mongodb-version: ["4.2"] - os: ubuntu1804 + os: ubuntu2004 fle: helper + docker-distro: ubuntu1804 display_name: "FLE: ${topology} ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" - matrix_name: "fle-4.0" matrix_spec: @@ -521,11 +525,12 @@ buildvariants: ruby: [ruby-2.6] topology: standalone mongodb-version: ["4.0"] - os: ubuntu1804 + os: ubuntu2004 fle: path + docker-distro: ubuntu1804 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - - name: "test-fle" + - name: "test-fle-via-docker" # https://jira.mongodb.org/browse/RUBY-3217 - matrix_name: "fle-jruby" @@ -534,9 +539,9 @@ buildvariants: ruby: [jruby-9.2, jruby-9.3] topology: standalone mongodb-version: ['5.0'] - os: ubuntu1804 + os: ubuntu2004 fle: helper - docker-distro: ubuntu1804 + docker-distro: ubuntu2004 display_name: "FLE: ${mongodb-version} ${ruby}" tasks: - name: "test-fle-via-docker" @@ -682,9 +687,9 @@ buildvariants: # https://jira.mongodb.org/browse/RUBY-3217 # ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.3"] ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1"] - fle: helper + fle: path display_name: "Atlas serverless ${ruby} single mongos" run_on: - - ubuntu1804-small + - ubuntu2004-small tasks: - name: test-serverless diff --git a/.evergreen/run-tests-docker.sh b/.evergreen/run-tests-docker.sh index 0dbebc22d5..ff8051e5ef 100755 --- a/.evergreen/run-tests-docker.sh +++ b/.evergreen/run-tests-docker.sh @@ -14,7 +14,7 @@ params= for var in MONGODB_VERSION TOPOLOGY RVM_RUBY \ OCSP_ALGORITHM OCSP_STATUS OCSP_DELEGATE OCSP_MUST_STAPLE \ OCSP_CONNECTIVITY OCSP_VERIFIER FLE \ - AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION + AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION CRYPT_SHARED_VERSION do value="${!var}" if test -n "$value"; then diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index aa83912ad3..b438d6ca6e 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -26,6 +26,14 @@ else exit -1 fi +mkdir libmongocrypt +cd libmongocrypt +curl --retry 3 -fLo libmongocrypt-all.tar.gz "https://s3.amazonaws.com/mciuploads/libmongocrypt/all/master/latest/libmongocrypt-all.tar.gz" +tar xf libmongocrypt-all.tar.gz +# We assume that serverless tests are always use ubuntu2004 +export LIBMONGOCRYPT_PATH=`pwd`/ubuntu2004-64/nocrypto/lib/libmongocrypt.so +cd - + cd .evergreen/csfle . ./activate-kmstlsvenv.sh diff --git a/spec/shared b/spec/shared index fb60e69cc9..cf850dfb42 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit fb60e69cc9f3a72c44cef0482a17a2666aceac6f +Subproject commit cf850dfb42e64599f0bae78ea21316feb2fdc8dc From 690e002fb3c700f55ffe7ad44002eb86be179e87 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 2 May 2023 20:53:36 +0200 Subject: [PATCH 119/198] RUBY-3088 Support Azure Managed Identity for KMS (#2710) --- .evergreen/config.yml | 86 +++++++++++++ .evergreen/config/common.yml.erb | 74 +++++++++++ .evergreen/config/standard.yml.erb | 12 ++ .evergreen/run-tests-azure.sh | 22 ++++ .evergreen/run-tests-docker.sh | 2 +- .evergreen/run-tests-gcp.sh | 2 +- .evergreen/run-tests-serverless.sh | 4 +- .evergreen/run-tests.sh | 2 + .mod/drivers-evergreen-tools | 2 +- .rubocop.yml | 3 + lib/mongo/crypt/context.rb | 79 +++++++++--- lib/mongo/crypt/handle.rb | 2 +- lib/mongo/crypt/kms/azure.rb | 2 + lib/mongo/crypt/kms/azure/access_token.rb | 54 ++++++++ lib/mongo/crypt/kms/azure/credentials.rb | 35 +++-- .../crypt/kms/azure/credentials_retriever.rb | 121 ++++++++++++++++++ .../crypt/kms/gcp/credentials_retriever.rb | 6 +- .../on_demand_azure_credentials_spec.rb | 48 +++++++ .../on_demand_gcp_credentials_spec.rb | 6 +- .../kms/azure/credentials_retriever_spec.rb | 86 +++++++++++++ spec/support/spec_config.rb | 2 +- 21 files changed, 605 insertions(+), 45 deletions(-) create mode 100755 .evergreen/run-tests-azure.sh create mode 100644 lib/mongo/crypt/kms/azure/access_token.rb create mode 100644 lib/mongo/crypt/kms/azure/credentials_retriever.rb create mode 100644 spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb create mode 100644 spec/mongo/crypt/kms/azure/credentials_retriever_spec.rb diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 0290da04c1..7b240585db 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -522,6 +522,50 @@ task_groups: tasks: - testgcpkms-task + - name: testazurekms_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: "create expansions" + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + export AZUREKMS_VMNAME_PREFIX=RUBY + export AZUREKMS_CLIENTID="${testazurekms_clientid}" + export AZUREKMS_TENANTID="${testazurekms_tenantid}" + export AZUREKMS_SECRET="${testazurekms_secret}" + export AZUREKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS + export AZUREKMS_RESOURCEGROUP="${testazurekms_resourcegroup}" + echo '${testazurekms_publickey}' > /tmp/testazurekms_public_key_file + export AZUREKMS_PUBLICKEYPATH="/tmp/testazurekms_public_key_file" + echo '${testazurekms_privatekey}' > /tmp/testazurekms_private_key_file + chmod 600 /tmp/testazurekms_private_key_file + export AZUREKMS_PRIVATEKEYPATH="/tmp/testazurekms_private_key_file" + export AZUREKMS_SCOPE="${testazurekms_scope}" + .evergreen/csfle/azurekms/create-and-setup-vm.sh + # Load the AZUREKMS_GCLOUD, AZUREKMS_INSTANCE, AZUREKMS_REGION, and AZUREKMS_ZONE expansions. + - command: expansions.update + params: + file: src/testazurekms-expansions.yml + teardown_group: + - command: expansions.update + params: + file: src/testazurekms-expansions.yml + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + export AZUREKMS_RESOURCEGROUP="${testazurekms_resourcegroup}" + .evergreen/csfle/azurekms/delete-vm.sh + tasks: + - testazurekms-task + tasks: - name: "test-atlas" commands: @@ -590,6 +634,36 @@ tasks: export GCPKMS_ZONE=${GCPKMS_ZONE} export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} GCPKMS_CMD="TEST_FLE_GCP_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}" MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY='${fle_gcp_private_key}' MONGO_RUBY_DRIVER_GCP_PROJECT_ID='${fle_gcp_project_id}' MONGO_RUBY_DRIVER_GCP_LOCATION='${fle_gcp_location}' MONGO_RUBY_DRIVER_GCP_KEY_RING='${fle_gcp_key_ring}' MONGO_RUBY_DRIVER_GCP_KEY_NAME='${fle_gcp_key_name}' ./.evergreen/run-tests-gcp.sh" .evergreen/csfle/gcpkms/run-command.sh + - name: "testazurekms-task" + commands: + - command: shell.exec + type: setup + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + echo "Copying files ... begin" + export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup} + export AZUREKMS_VMNAME=${AZUREKMS_VMNAME} + export AZUREKMS_PRIVATEKEYPATH="/tmp/testazurekms_private_key_file" + tar czf /tmp/mongo-ruby-driver.tgz . + AZUREKMS_SRC=/tmp/mongo-ruby-driver.tgz AZUREKMS_DST="~/" .evergreen/csfle/azurekms/copy-file.sh + echo "Copying files ... end" + echo "Untarring file ... begin" + AZUREKMS_CMD="tar xf mongo-ruby-driver.tgz" .evergreen/csfle/azurekms/run-command.sh + echo "Untarring file ... end" + - command: shell.exec + type: test + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup} + export AZUREKMS_VMNAME=${AZUREKMS_VMNAME} + export AZUREKMS_PRIVATEKEYPATH="/tmp/testazurekms_private_key_file" + AZUREKMS_CMD="TEST_FLE_AZURE_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_AZURE_TENANT_ID="${MONGO_RUBY_DRIVER_AZURE_TENANT_ID}" MONGO_RUBY_DRIVER_AZURE_CLIENT_ID="${MONGO_RUBY_DRIVER_AZURE_CLIENT_ID}" MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET="${MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET}" MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT="${MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT}" MONGO_RUBY_DRIVER_AZURE_KEY_VAULT_ENDPOINT="${testazurekms_keyvaultendpoint}" MONGO_RUBY_DRIVER_AZURE_KEY_NAME="${testazurekms_keyname}" ./.evergreen/run-tests-azure.sh" .evergreen/csfle/azurekms/run-command.sh axes: - id: preload @@ -1791,6 +1865,18 @@ buildvariants: - name: testgcpkms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - matrix_name: testazurekms-variant + matrix_spec: + ruby: ruby-3.0 + fle: helper + topology: standalone + os: debian11 + mongodb-version: 6.0 + display_name: "AZURE KMS" + tasks: + - name: testazurekms_task_group + batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - matrix_name: "atlas" matrix_spec: ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index f12903d8a3..fdb4a9d096 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -520,6 +520,50 @@ task_groups: tasks: - testgcpkms-task + - name: testazurekms_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: "create expansions" + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + export AZUREKMS_VMNAME_PREFIX=RUBY + export AZUREKMS_CLIENTID="${testazurekms_clientid}" + export AZUREKMS_TENANTID="${testazurekms_tenantid}" + export AZUREKMS_SECRET="${testazurekms_secret}" + export AZUREKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS + export AZUREKMS_RESOURCEGROUP="${testazurekms_resourcegroup}" + echo '${testazurekms_publickey}' > /tmp/testazurekms_public_key_file + export AZUREKMS_PUBLICKEYPATH="/tmp/testazurekms_public_key_file" + echo '${testazurekms_privatekey}' > /tmp/testazurekms_private_key_file + chmod 600 /tmp/testazurekms_private_key_file + export AZUREKMS_PRIVATEKEYPATH="/tmp/testazurekms_private_key_file" + export AZUREKMS_SCOPE="${testazurekms_scope}" + .evergreen/csfle/azurekms/create-and-setup-vm.sh + # Load the AZUREKMS_GCLOUD, AZUREKMS_INSTANCE, AZUREKMS_REGION, and AZUREKMS_ZONE expansions. + - command: expansions.update + params: + file: src/testazurekms-expansions.yml + teardown_group: + - command: expansions.update + params: + file: src/testazurekms-expansions.yml + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + export AZUREKMS_RESOURCEGROUP="${testazurekms_resourcegroup}" + .evergreen/csfle/azurekms/delete-vm.sh + tasks: + - testazurekms-task + tasks: - name: "test-atlas" commands: @@ -588,3 +632,33 @@ tasks: export GCPKMS_ZONE=${GCPKMS_ZONE} export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} GCPKMS_CMD="TEST_FLE_GCP_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_GCP_EMAIL="${fle_gcp_email}" MONGO_RUBY_DRIVER_GCP_PRIVATE_KEY='${fle_gcp_private_key}' MONGO_RUBY_DRIVER_GCP_PROJECT_ID='${fle_gcp_project_id}' MONGO_RUBY_DRIVER_GCP_LOCATION='${fle_gcp_location}' MONGO_RUBY_DRIVER_GCP_KEY_RING='${fle_gcp_key_ring}' MONGO_RUBY_DRIVER_GCP_KEY_NAME='${fle_gcp_key_name}' ./.evergreen/run-tests-gcp.sh" .evergreen/csfle/gcpkms/run-command.sh + - name: "testazurekms-task" + commands: + - command: shell.exec + type: setup + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + echo "Copying files ... begin" + export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup} + export AZUREKMS_VMNAME=${AZUREKMS_VMNAME} + export AZUREKMS_PRIVATEKEYPATH="/tmp/testazurekms_private_key_file" + tar czf /tmp/mongo-ruby-driver.tgz . + AZUREKMS_SRC=/tmp/mongo-ruby-driver.tgz AZUREKMS_DST="~/" .evergreen/csfle/azurekms/copy-file.sh + echo "Copying files ... end" + echo "Untarring file ... begin" + AZUREKMS_CMD="tar xf mongo-ruby-driver.tgz" .evergreen/csfle/azurekms/run-command.sh + echo "Untarring file ... end" + - command: shell.exec + type: test + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup} + export AZUREKMS_VMNAME=${AZUREKMS_VMNAME} + export AZUREKMS_PRIVATEKEYPATH="/tmp/testazurekms_private_key_file" + AZUREKMS_CMD="TEST_FLE_AZURE_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_AZURE_TENANT_ID="${MONGO_RUBY_DRIVER_AZURE_TENANT_ID}" MONGO_RUBY_DRIVER_AZURE_CLIENT_ID="${MONGO_RUBY_DRIVER_AZURE_CLIENT_ID}" MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET="${MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET}" MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT="${MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT}" MONGO_RUBY_DRIVER_AZURE_KEY_VAULT_ENDPOINT="${testazurekms_keyvaultendpoint}" MONGO_RUBY_DRIVER_AZURE_KEY_NAME="${testazurekms_keyname}" ./.evergreen/run-tests-azure.sh" .evergreen/csfle/azurekms/run-command.sh diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index ec401e2012..e2171b1bd8 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -673,6 +673,18 @@ buildvariants: - name: testgcpkms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - matrix_name: testazurekms-variant + matrix_spec: + ruby: ruby-3.0 + fle: helper + topology: standalone + os: debian11 + mongodb-version: 6.0 + display_name: "AZURE KMS" + tasks: + - name: testazurekms_task_group + batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - matrix_name: "atlas" matrix_spec: ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] diff --git a/.evergreen/run-tests-azure.sh b/.evergreen/run-tests-azure.sh new file mode 100755 index 0000000000..93d522f155 --- /dev/null +++ b/.evergreen/run-tests-azure.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -ex + +. `dirname "$0"`/../spec/shared/shlib/distro.sh +. `dirname "$0"`/../spec/shared/shlib/set_env.sh +. `dirname "$0"`/functions.sh + +set_env_vars +set_env_python +set_env_ruby + +sudo apt-get -y install libyaml-dev cmake + +bundle_install + +echo "Running specs" +export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=${CRYPT_SHARED_LIB_PATH} +bundle exec rake spec:prepare +bundle exec rspec spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb + +exit ${test_status} diff --git a/.evergreen/run-tests-docker.sh b/.evergreen/run-tests-docker.sh index ff8051e5ef..b04c48de5d 100755 --- a/.evergreen/run-tests-docker.sh +++ b/.evergreen/run-tests-docker.sh @@ -14,7 +14,7 @@ params= for var in MONGODB_VERSION TOPOLOGY RVM_RUBY \ OCSP_ALGORITHM OCSP_STATUS OCSP_DELEGATE OCSP_MUST_STAPLE \ OCSP_CONNECTIVITY OCSP_VERIFIER FLE \ - AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION CRYPT_SHARED_VERSION + AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION CRYPT_SHARED_VERSION MONGO_RUBY_DRIVER_AZURE_METADATA_HOST do value="${!var}" if test -n "$value"; then diff --git a/.evergreen/run-tests-gcp.sh b/.evergreen/run-tests-gcp.sh index 32032fbeff..0c84ebc765 100755 --- a/.evergreen/run-tests-gcp.sh +++ b/.evergreen/run-tests-gcp.sh @@ -10,7 +10,7 @@ set_env_vars set_env_python set_env_ruby -sudo apt-get -y install libyaml-dev +sudo apt-get -y install libyaml-dev cmake bundle_install diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index b438d6ca6e..faebcf2ca4 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -75,7 +75,6 @@ bundle exec rspec \ spec/spec_tests/retryable_writes_spec.rb \ spec/spec_tests/transactions_spec.rb \ spec/spec_tests/change_streams_unified_spec.rb \ - spec/spec_tests/client_side_encryption_spec.rb \ spec/spec_tests/client_side_encryption_unified_spec.rb \ spec/spec_tests/command_monitoring_unified_spec.rb \ spec/spec_tests/crud_unified_spec.rb \ @@ -86,6 +85,9 @@ bundle exec rspec \ spec/spec_tests/sessions_unified_spec.rb \ spec/spec_tests/transactions_unified_spec.rb +# https://jira.mongodb.org/browse/RUBY-3249 +# Add when fixed: spec/spec_tests/client_side_encryption_spec.rb \ + kill_jruby # Terminate all kmip servers... and whatever else happens to be running # that is a python script. diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 193fa92e3c..7209663dd1 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -200,6 +200,7 @@ if test -n "$FLE"; then python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/wrong-host.pem --port 8001 & python3 -u .evergreen/csfle/kms_http_server.py --ca_file .evergreen/x509gen/ca.pem --cert_file .evergreen/x509gen/server.pem --port 8002 --require_client_cert & python3 -u .evergreen/csfle/kms_kmip_server.py & + python3 -u .evergreen/csfle/fake_azure.py & # Obtain temporary AWS credentials PYTHON=python3 . .evergreen/csfle/set-temp-creds.sh @@ -255,6 +256,7 @@ if test -n "$FLE"; then wait_for_kms_server 8001 wait_for_kms_server 8002 wait_for_kms_server 5698 + wait_for_kms_server 8080 echo "Waiting for mock KMS servers to start... done." fi diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index a3a17f1185..aa07574ff2 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit a3a17f1185643135a6b24f318bf0ce1e58fdea94 +Subproject commit aa07574ff2dc491b8a3b99c35a87e753c3b34edf diff --git a/.rubocop.yml b/.rubocop.yml index 9083d289b6..97efe7186e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -47,3 +47,6 @@ Gemspec/OrderedDependencies: Style/Documentation: Exclude: - 'spec/**/*' + +Metrics/MethodLength: + Max: 20 diff --git a/lib/mongo/crypt/context.rb b/lib/mongo/crypt/context.rb index 18de60b5c0..5ace2b9dde 100644 --- a/lib/mongo/crypt/context.rb +++ b/lib/mongo/crypt/context.rb @@ -51,6 +51,7 @@ def initialize(mongocrypt_handle, io) ) @encryption_io = io + @cached_azure_token = nil end attr_reader :ctx_p @@ -115,29 +116,9 @@ def run_state_machine Binding.ctx_kms_done(self) when :need_kms_credentials - providers = {} - if kms_providers.aws&.empty? - begin - aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials - rescue Auth::Aws::CredentialsNotFound - raise Error::CryptError.new( - "Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)" - ) - end - providers[:aws] = aws_credentials.to_h - elsif kms_providers.gcp&.empty? - begin - gcp_access_token = KMS::GCP::CredentialsRetriever.get_access_token - rescue KMS::CredentialsNotFound => e - raise Error::CryptError.new( - "Could not locate GCP credentials: #{e.class}: #{e.message}" - ) - end - providers[:gcp] = { access_token: gcp_access_token } - end Binding.ctx_provide_kms_providers( self, - KMS::Credentials.new(providers).to_document + retrieve_kms_credentials.to_document ) else raise Error::CryptError.new( @@ -162,6 +143,62 @@ def mongocrypt_done def mongocrypt_feed(doc) Binding.ctx_mongo_feed(self, doc) end + + # Retrieves KMS credentials for providers that are configured + # for automatic credentials retrieval. + # + # @return [ Crypt::KMS::Credentials ] Credentials for the configured + # KMS providers. + def retrieve_kms_credentials + providers = {} + if kms_providers.aws&.empty? + begin + aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials + rescue Auth::Aws::CredentialsNotFound + raise Error::CryptError.new( + "Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)" + ) + end + providers[:aws] = aws_credentials.to_h + end + if kms_providers.gcp&.empty? + providers[:gcp] = { access_token: gcp_access_token } + end + if kms_providers.azure&.empty? + providers[:azure] = { access_token: azure_access_token } + end + KMS::Credentials.new(providers) + end + + # Retrieves a GCP access token. + # + # @return [ String ] A GCP access token. + # + # @raise [ Error::CryptError ] If the GCP access token could not be + def gcp_access_token + KMS::GCP::CredentialsRetriever.fetch_access_token + rescue KMS::CredentialsNotFound => e + raise Error::CryptError.new( + "Could not locate GCP credentials: #{e.class}: #{e.message}" + ) + end + + # Returns an Azure access token, retrieving it if necessary. + # + # @return [ String ] An Azure access token. + # + # @raise [ Error::CryptError ] If the Azure access token could not be + # retrieved. + def azure_access_token + if @cached_azure_token.nil? || @cached_azure_token.expired? + @cached_azure_token = KMS::Azure::CredentialsRetriever.fetch_access_token + end + @cached_azure_token.access_token + rescue KMS::CredentialsNotFound => e + raise Error::CryptError.new( + "Could not locate Azure credentials: #{e.class}: #{e.message}" + ) + end end end end diff --git a/lib/mongo/crypt/handle.rb b/lib/mongo/crypt/handle.rb index 5716fb054b..b6cd0f7f67 100644 --- a/lib/mongo/crypt/handle.rb +++ b/lib/mongo/crypt/handle.rb @@ -98,7 +98,7 @@ def initialize(kms_providers, kms_tls_options, options={}) Binding.setopt_kms_providers(self, @kms_providers.to_document) - if @kms_providers.aws&.empty? || @kms_providers.gcp&.empty? + if @kms_providers.aws&.empty? || @kms_providers.gcp&.empty? || @kms_providers.azure&.empty? Binding.setopt_use_need_kms_credentials_state(self) end diff --git a/lib/mongo/crypt/kms/azure.rb b/lib/mongo/crypt/kms/azure.rb index 6750e6d029..55e2eeb044 100644 --- a/lib/mongo/crypt/kms/azure.rb +++ b/lib/mongo/crypt/kms/azure.rb @@ -15,5 +15,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'mongo/crypt/kms/azure/access_token' require 'mongo/crypt/kms/azure/credentials' +require 'mongo/crypt/kms/azure/credentials_retriever' require 'mongo/crypt/kms/azure/master_document' diff --git a/lib/mongo/crypt/kms/azure/access_token.rb b/lib/mongo/crypt/kms/azure/access_token.rb new file mode 100644 index 0000000000..f564a5d9aa --- /dev/null +++ b/lib/mongo/crypt/kms/azure/access_token.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module Azure + # Azure access token for temporary credentials. + # + # @api private + class AccessToken + # @return [ String ] Azure access token. + attr_reader :access_token + + # @return [ Integer ] Azure access token expiration time. + attr_reader :expires_in + + # Creates an Azure access token object. + # + # @param [ String ] access_token Azure access token. + # @param [ Integer ] expires_in Azure access token expiration time. + def initialize(access_token, expires_in) + @access_token = access_token + @expires_in = expires_in + @expires_at = Time.now.to_i + @expires_in + end + + # Checks if the access token is expired. + # + # The access token is considered expired if it is within 60 seconds + # of its expiration time. + # + # @return [ true | false ] Whether the access token is expired. + def expired? + Time.now.to_i >= @expires_at - 60 + end + end + end + end + end +end diff --git a/lib/mongo/crypt/kms/azure/credentials.rb b/lib/mongo/crypt/kms/azure/credentials.rb index 8f717e9b4a..29609b1f77 100644 --- a/lib/mongo/crypt/kms/azure/credentials.rb +++ b/lib/mongo/crypt/kms/azure/credentials.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # @@ -38,11 +37,14 @@ class Credentials # @return [ String | nil ] Azure identity platform endpoint. attr_reader :identity_platform_endpoint + # @return [ String | nil ] Azure access token. + attr_reader :access_token + # @api private def_delegator :@opts, :empty? - FORMAT_HINT = "Azure KMS provider options must be in the format: " + - "{ tenant_id: 'TENANT-ID', client_id: 'TENANT_ID', client_secret: 'CLIENT_SECRET' }" + FORMAT_HINT = 'Azure KMS provider options must be in the format: \ + { tenant_id: "TENANT-ID", client_id: "TENANT_ID", client_secret: "CLIENT_SECRET" }' # Creates an Azure KMS credentials object form a parameters hash. # @@ -58,7 +60,11 @@ class Credentials # formatted. def initialize(opts) @opts = opts - unless empty? + return if empty? + + if opts[:access_token] + @access_token = opts[:access_token] + else @tenant_id = validate_param(:tenant_id, opts, FORMAT_HINT) @client_id = validate_param(:client_id, opts, FORMAT_HINT) @client_secret = validate_param(:client_secret, opts, FORMAT_HINT) @@ -73,13 +79,20 @@ def initialize(opts) # @return [ BSON::Document ] Azure KMS credentials in libmongocrypt format. def to_document return BSON::Document.new if empty? - BSON::Document.new({ - tenantId: @tenant_id, - clientId: @client_id, - clientSecret: @client_secret, - }).tap do |bson| - unless identity_platform_endpoint.nil? - bson.update({ identityPlatformEndpoint: identity_platform_endpoint }) + + if access_token + BSON::Document.new({ accessToken: access_token }) + else + BSON::Document.new( + { + tenantId: @tenant_id, + clientId: @client_id, + clientSecret: @client_secret + } + ).tap do |bson| + unless identity_platform_endpoint.nil? + bson.update({ identityPlatformEndpoint: identity_platform_endpoint }) + end end end end diff --git a/lib/mongo/crypt/kms/azure/credentials_retriever.rb b/lib/mongo/crypt/kms/azure/credentials_retriever.rb new file mode 100644 index 0000000000..f008e84884 --- /dev/null +++ b/lib/mongo/crypt/kms/azure/credentials_retriever.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +# Copyright (C) 2019-2021 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + module Crypt + module KMS + module Azure + # This class retrieves Azure credentials using Azure + # metadata host. This should be used when the driver is used on the + # Azure environment. + # + # @api private + class CredentialsRetriever + # Default host to obtain Azure metadata. + DEFAULT_HOST = '169.254.169.254' + + # Fetches Azure credentials from Azure metadata host. + # + # @param [Hash] extra_headers Extra headers to be passed to the + # request. This is used for testing. + # @param [String | nil] metadata_host Azure metadata host. This + # is used for testing. + # + # @return [ KMS::Azure::AccessToken ] Azure access token. + # + # @raise [KMS::CredentialsNotFound] If credentials could not be found. + def self.fetch_access_token(extra_headers: {}, metadata_host: nil) + uri, req = prepare_request(extra_headers, metadata_host) + parsed_response = fetch_response(uri, req) + Azure::AccessToken.new( + parsed_response.fetch('access_token'), + Integer(parsed_response.fetch('expires_in')) + ) + rescue KeyError, ArgumentError => e + raise KMS::CredentialsNotFound, + "Azure metadata response is invalid: '#{parsed_response}'; #{e.class}: #{e.message}" + end + + # Prepares a request to Azure metadata host. + # + # @param [Hash] extra_headers Extra headers to be passed to the + # request. This is used for testing. + # @param [String | nil] metadata_host Azure metadata host. This + # is used for testing. + # + # @return [Array] URI and request object. + def self.prepare_request(extra_headers, metadata_host) + host = metadata_host || DEFAULT_HOST + host = DEFAULT_HOST if host.empty? + uri = URI("http://#{host}/metadata/identity/oauth2/token") + uri.query = ::URI.encode_www_form( + 'api-version' => '2018-02-01', + 'resource' => 'https://vault.azure.net' + ) + req = Net::HTTP::Get.new(uri) + req['Metadata'] = 'true' + req['Accept'] = 'application/json' + extra_headers.each { |k, v| req[k] = v } + [uri, req] + end + private_class_method :prepare_request + + # Fetches response from Azure metadata host. + # + # @param [URI] uri URI to Azure metadata host. + # @param [Net::HTTP::Get] req Request object. + # + # @return [Hash] Parsed response. + # + # @raise [KMS::CredentialsNotFound] If cannot fetch response or + # response is invalid. + def self.fetch_response(uri, req) + resp = do_request(uri, req) + if resp.code != '200' + raise KMS::CredentialsNotFound, + "Azure metadata host responded with code #{resp.code}" + end + JSON.parse(resp.body) + rescue JSON::ParserError => e + raise KMS::CredentialsNotFound, + "Azure metadata response is invalid: '#{resp.body}'; #{e.class}: #{e.message}" + end + private_class_method :fetch_response + + # Performs a request to Azure metadata host. + # + # @param [URI] uri URI to Azure metadata host. + # @param [Net::HTTP::Get] req Request object. + # + # @return [Net::HTTPResponse] Response object. + # + # @raise [KMS::CredentialsNotFound] If cannot execute request. + def self.do_request(uri, req) + ::Timeout.timeout(10) do + Net::HTTP.start(uri.hostname, uri.port, use_ssl: false) do |http| + http.request(req) + end + end + rescue ::Timeout::Error, IOError, SystemCallError, SocketError => e + raise KMS::CredentialsNotFound, + "Could not receive Azure metadata response; #{e.class}: #{e.message}" + end + private_class_method :do_request + end + end + end + end +end diff --git a/lib/mongo/crypt/kms/gcp/credentials_retriever.rb b/lib/mongo/crypt/kms/gcp/credentials_retriever.rb index 231fccfb40..723cda43c4 100644 --- a/lib/mongo/crypt/kms/gcp/credentials_retriever.rb +++ b/lib/mongo/crypt/kms/gcp/credentials_retriever.rb @@ -25,12 +25,11 @@ module GCP # # @api private class CredentialsRetriever - METADATA_HOST_ENV = 'GCE_METADATA_HOST' DEFAULT_HOST = 'metadata.google.internal' - def self.get_access_token + def self.fetch_access_token host = ENV.fetch(METADATA_HOST_ENV) { DEFAULT_HOST } uri = URI("http://#{host}/computeMetadata/v1/instance/service-accounts/default/token") req = Net::HTTP::Get.new(uri) @@ -47,6 +46,9 @@ def self.get_access_token rescue JSON::ParserError, KeyError => e raise KMS::CredentialsNotFound, "GCE metadata response is invalid: '#{resp.body}'; #{e.class}: #{e.message}" + rescue ::Timeout::Error, IOError, SystemCallError, SocketError => e + raise KMS::CredentialsNotFound, + "Could not receive GCP metadata response; #{e.class}: #{e.message}" end end end diff --git a/spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb b/spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb new file mode 100644 index 0000000000..4d9c35291c --- /dev/null +++ b/spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# rubocop:disable RSpec/DescribeClass +describe 'On-demand Azure Credentials' do + require_libmongocrypt + include_context 'define shared FLE helpers' + include_context 'with Azure kms_providers' + + let(:client) { ClientRegistry.instance.new_local_client(SpecConfig.instance.addresses) } + + let(:client_encryption_opts) do + { + kms_providers: { azure: {} }, + kms_tls_options: kms_tls_options, + key_vault_namespace: key_vault_namespace + } + end + + let(:client_encryption) do + Mongo::ClientEncryption.new( + client, + client_encryption_opts + ) + end + + context 'when credentials are available' do + it 'authenticates successfully' do + skip 'This tests should be run inside Azure Cloud only' unless ENV['TEST_FLE_AZURE_AUTO'] + + expect do + client_encryption.create_data_key('azure', data_key_options) + end.not_to raise_error + end + end + + context 'when credentials are not available' do + it 'raises an error' do + skip 'This tests should NOT be run inside Azure Cloud only' if ENV['TEST_FLE_AZURE_AUTO'] + + expect do + client_encryption.create_data_key('azure', data_key_options) + end.to raise_error(Mongo::Error::CryptError, /Azure credentials/) + end + end +end +# rubocop:enable RSpec/DescribeClass diff --git a/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb b/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb index 1c4aac80b8..204d17c2f3 100644 --- a/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb +++ b/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb @@ -36,11 +36,7 @@ context 'when credentials are not available' do it 'raises an error' do - expect( - Mongo::Crypt::KMS::GCP::CredentialsRetriever - ).to receive(:get_access_token).with(no_args).once.and_raise( - Mongo::Crypt::KMS::CredentialsNotFound - ) + skip 'This tests should NOT be run inside Google Cloud only' if ENV['TEST_FLE_GCP_AUTO'] expect do client_encryption.create_data_key('gcp', data_key_options) diff --git a/spec/mongo/crypt/kms/azure/credentials_retriever_spec.rb b/spec/mongo/crypt/kms/azure/credentials_retriever_spec.rb new file mode 100644 index 0000000000..03cfff33b2 --- /dev/null +++ b/spec/mongo/crypt/kms/azure/credentials_retriever_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'lite_spec_helper' + +describe Mongo::Crypt::KMS::Azure::CredentialsRetriever do + # The tests here require fake azure server, which is started in FLE + # configurations on evergreen. If you want to run these tests locally, + # you need to start the server manually. See .evergreen/run-tests.sh + # for the command to start the server. + before do + skip 'These tests require fake azure server to be running' unless SpecConfig.instance.fle? + end + + let(:metadata_host) do + 'localhost:8080' + end + + describe '.fetch_access_token' do + context 'when response is valid' do + let(:token) do + described_class.fetch_access_token(metadata_host: metadata_host) + end + + it 'returns access token' do + expect(token.access_token).to eq('magic-cookie') + end + + it 'returns expiration time' do + expect(token.expires_in).to eq(70) + end + end + + context 'when response contains empty json' do + it 'raises error' do + expect do + described_class.fetch_access_token( + extra_headers: { 'X-MongoDB-HTTP-TestParams' => 'case=empty-json' }, metadata_host: metadata_host + ) + end.to raise_error(Mongo::Crypt::KMS::CredentialsNotFound) + end + end + + context 'when response contains invalid json' do + it 'raises error' do + expect do + described_class.fetch_access_token( + extra_headers: { 'X-MongoDB-HTTP-TestParams' => 'case=bad-json' }, metadata_host: metadata_host + ) + end.to raise_error(Mongo::Crypt::KMS::CredentialsNotFound) + end + end + + context 'when metadata host responds with 500' do + it 'raises error' do + expect do + described_class.fetch_access_token( + extra_headers: { 'X-MongoDB-HTTP-TestParams' => 'case=500' }, metadata_host: metadata_host + ) + end.to raise_error(Mongo::Crypt::KMS::CredentialsNotFound) + end + end + + context 'when metadata host responds with 404' do + it 'raises error' do + expect do + described_class.fetch_access_token( + extra_headers: { 'X-MongoDB-HTTP-TestParams' => 'case=404' }, metadata_host: metadata_host + ) + end.to raise_error(Mongo::Crypt::KMS::CredentialsNotFound) + end + end + + context 'when metadata host is slow' do + # On JRuby Timeout.timeout does not work in this case. + fails_on_jruby + + it 'raises error' do + expect do + described_class.fetch_access_token( + extra_headers: { 'X-MongoDB-HTTP-TestParams' => 'case=slow' }, metadata_host: metadata_host + ) + end.to raise_error(Mongo::Crypt::KMS::CredentialsNotFound) + end + end + end +end diff --git a/spec/support/spec_config.rb b/spec/support/spec_config.rb index ec5aa5c39d..eb090db2ac 100644 --- a/spec/support/spec_config.rb +++ b/spec/support/spec_config.rb @@ -372,7 +372,7 @@ def test_db # Whether FLE tests should be enabled def fle? - ENV['FLE'] + %w(1 true yes helper).include?(ENV['FLE']&.downcase) end # AWS IAM user access key id From 899fd8d3619d6ea5f5fc411667e09cae18a3318e Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 12 May 2023 10:21:01 +0200 Subject: [PATCH 120/198] RUBY-3247 Remove rspec-mocks-diag (#2711) --- .evergreen/download-mongodb.sh | 1 + gemfiles/standard.rb | 6 +----- spec/mongo/address_spec.rb | 2 +- spec/shared | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) create mode 120000 .evergreen/download-mongodb.sh diff --git a/.evergreen/download-mongodb.sh b/.evergreen/download-mongodb.sh new file mode 120000 index 0000000000..d0b2306cc8 --- /dev/null +++ b/.evergreen/download-mongodb.sh @@ -0,0 +1 @@ +../.mod/drivers-evergreen-tools/.evergreen/download-mongodb.sh \ No newline at end of file diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index 538e7fc2e4..bfa3fa3726 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -8,9 +8,7 @@ def standard_dependencies group :development, :testing do gem 'jruby-openssl', platforms: :jruby gem 'json', platforms: :jruby - # Explicitly specify each rspec dependency so that we can use - # rspec-mocks-diag instead of rspec-mocks - gem 'rspec-core', '~> 3.9' + gem 'rspec', '~> 3.12' gem 'activesupport', '<7.1' gem 'rake' gem 'webrick' @@ -52,8 +50,6 @@ def standard_dependencies gem 'ice_nine' gem 'rubydns', platforms: :mri gem 'rspec-retry' - gem 'rspec-expectations', '~> 3.9' - gem 'rspec-mocks-diag', '~> 3.9' gem 'rfc', '~> 0.2.0' gem 'fuubar' gem 'timeout-interrupt', platforms: :mri diff --git a/spec/mongo/address_spec.rb b/spec/mongo/address_spec.rb index 97e68d76c6..d637f6eb9c 100644 --- a/spec/mongo/address_spec.rb +++ b/spec/mongo/address_spec.rb @@ -311,7 +311,7 @@ RSpec::Mocks.with_temporary_scope do resolved_address = double('address') # This test's expectation - expect(resolved_address).to receive(:socket).with(0, connect_timeout: 10) + expect(resolved_address).to receive(:socket).with(0, {connect_timeout: 10}) expect(Mongo::Address::IPv4).to receive(:new).and_return(resolved_address) diff --git a/spec/shared b/spec/shared index cf850dfb42..faf76703ec 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit cf850dfb42e64599f0bae78ea21316feb2fdc8dc +Subproject commit faf76703ec5aa2fec37a84f3a73b86913c4ff1c4 From 2803b97d35dbf73afbd60b347c4e8cae11feb4b4 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 12 May 2023 10:47:18 +0200 Subject: [PATCH 121/198] RUBY-3231 Bump maxWireVersion for 7.0 (#2713) --- lib/mongo/server/description.rb | 4 ++++ lib/mongo/server/description/features.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/mongo/server/description.rb b/lib/mongo/server/description.rb index bfd7905ad9..3e21b6a635 100644 --- a/lib/mongo/server/description.rb +++ b/lib/mongo/server/description.rb @@ -869,6 +869,10 @@ def ==(other) # @api private def server_version_gte?(version) required_wv = case version + when '7.0' + 21 + when '6.0' + 17 when '5.2' 15 when '5.1' diff --git a/lib/mongo/server/description/features.rb b/lib/mongo/server/description/features.rb index f3bafc308b..849374546c 100644 --- a/lib/mongo/server/description/features.rb +++ b/lib/mongo/server/description/features.rb @@ -82,7 +82,7 @@ class Features # The wire protocol versions that this version of the driver supports. # # @since 2.0.0 - DRIVER_WIRE_VERSIONS = (6..17).freeze + DRIVER_WIRE_VERSIONS = (6..21).freeze # Create the methods for each mapping to tell if they are supported. # From 7fc62fa6814a91b8b29921eb6da22b174e4235cb Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 12 May 2023 12:45:39 +0200 Subject: [PATCH 122/198] Remove egos (#2715) --- .evergreen/config.yml | 28 ++++++---------------------- .evergreen/config/common.yml.erb | 29 ++++++----------------------- 2 files changed, 12 insertions(+), 45 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 7b240585db..919ee71c08 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -16,7 +16,6 @@ command_type: system # Protect ourself against rogue test case, or curl gone wild, that runs forever. exec_timeout_secs: 3600 - # What to do when evergreen hits the timeout (`post:` tasks are run automatically) timeout: - command: shell.exec @@ -38,20 +37,6 @@ functions: git submodule update --init --recursive - "fetch egos": - - command: shell.exec - params: - working_dir: "src" - script: | - set -e - - # Python version: - #curl -sfLo egos https://raw.githubusercontent.com/p-mongo/egos/master/egos - - # C version: - curl --retry 3 -sfLo egos.c https://raw.githubusercontent.com/p-mongo/egos/master/egos.c - gcc -O2 -g -oegos -lrt egos.c - "create expansions": # Make an evergreen expansion file with dynamic values - command: shell.exec @@ -383,7 +368,7 @@ functions: script: | ${PREPARE_SHELL} set -x - ./egos stdbuf -i0 -o0 -e0 .evergreen/test-on-docker -d ${os} MONGODB_VERSION=${mongodb-version} TOPOLOGY=${topology} RVM_RUBY=${ruby} -s .evergreen/run-tests.sh TEST_CMD=true ${PRELOAD_ARG} + .evergreen/test-on-docker -d ${os} MONGODB_VERSION=${mongodb-version} TOPOLOGY=${topology} RVM_RUBY=${ruby} -s .evergreen/run-tests.sh TEST_CMD=true ${PRELOAD_ARG} "run tests": - command: shell.exec @@ -393,7 +378,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests.sh + .evergreen/run-tests.sh "run tests via docker": - command: shell.exec @@ -403,7 +388,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-docker.sh + .evergreen/run-tests-docker.sh "run AWS auth tests": - command: shell.exec @@ -413,7 +398,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-aws-auth.sh + .evergreen/run-tests-aws-auth.sh "run Kerberos unit tests": - command: shell.exec @@ -423,7 +408,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-kerberos-unit.sh + .evergreen/run-tests-kerberos-unit.sh "run Kerberos integration tests": - command: shell.exec @@ -433,7 +418,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-kerberos-integration.sh + .evergreen/run-tests-kerberos-integration.sh "run Atlas tests": - command: shell.exec @@ -472,7 +457,6 @@ functions: pre: - func: "fetch source" - - func: "fetch egos" - func: "create expansions" post: diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index fdb4a9d096..4febb453fa 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -13,8 +13,6 @@ command_type: system # Protect ourself against rogue test case, or curl gone wild, that runs forever. exec_timeout_secs: 3600 -<% egos_wrap = "./egos stdbuf -i0 -o0 -e0" %> - # What to do when evergreen hits the timeout (`post:` tasks are run automatically) timeout: - command: shell.exec @@ -36,20 +34,6 @@ functions: git submodule update --init --recursive - "fetch egos": - - command: shell.exec - params: - working_dir: "src" - script: | - set -e - - # Python version: - #curl -sfLo egos https://raw.githubusercontent.com/p-mongo/egos/master/egos - - # C version: - curl --retry 3 -sfLo egos.c https://raw.githubusercontent.com/p-mongo/egos/master/egos.c - gcc -O2 -g -oegos -lrt egos.c - "create expansions": # Make an evergreen expansion file with dynamic values - command: shell.exec @@ -381,7 +365,7 @@ functions: script: | ${PREPARE_SHELL} set -x - <%= egos_wrap %> .evergreen/test-on-docker -d ${os} MONGODB_VERSION=${mongodb-version} TOPOLOGY=${topology} RVM_RUBY=${ruby} -s .evergreen/run-tests.sh TEST_CMD=true ${PRELOAD_ARG} + .evergreen/test-on-docker -d ${os} MONGODB_VERSION=${mongodb-version} TOPOLOGY=${topology} RVM_RUBY=${ruby} -s .evergreen/run-tests.sh TEST_CMD=true ${PRELOAD_ARG} "run tests": - command: shell.exec @@ -391,7 +375,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - <%= egos_wrap %> .evergreen/run-tests.sh + .evergreen/run-tests.sh "run tests via docker": - command: shell.exec @@ -401,7 +385,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - <%= egos_wrap %> .evergreen/run-tests-docker.sh + .evergreen/run-tests-docker.sh "run AWS auth tests": - command: shell.exec @@ -411,7 +395,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - <%= egos_wrap %> .evergreen/run-tests-aws-auth.sh + .evergreen/run-tests-aws-auth.sh "run Kerberos unit tests": - command: shell.exec @@ -421,7 +405,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - <%= egos_wrap %> .evergreen/run-tests-kerberos-unit.sh + .evergreen/run-tests-kerberos-unit.sh "run Kerberos integration tests": - command: shell.exec @@ -431,7 +415,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - <%= egos_wrap %> .evergreen/run-tests-kerberos-integration.sh + .evergreen/run-tests-kerberos-integration.sh "run Atlas tests": - command: shell.exec @@ -470,7 +454,6 @@ functions: pre: - func: "fetch source" - - func: "fetch egos" - func: "create expansions" post: From 0feee3ab4dbebd6bffa9924259e71d3ccb299a67 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 25 May 2023 16:01:59 +0200 Subject: [PATCH 123/198] POC for DRIVERS-2609 (#2714) --- .../change_streams_unified/change-streams-clusterTime.yml | 2 +- .../change-streams-disambiguatedPaths.yml | 2 +- .../data/change_streams_unified/change-streams-errors.yml | 4 ++-- .../change-streams-pre_and_post_images.yml | 2 +- .../change-streams-resume-allowlist.yml | 2 +- .../change-streams-resume-errorLabels.yml | 2 +- .../change-streams-showExpandedEvents.yml | 5 +++-- .../retryable_writes/unified/bulkWrite-serverErrors.yml | 2 +- .../retryable_writes/unified/insertOne-serverErrors.yml | 2 +- .../snapshot-sessions-unsupported-ops.yml | 2 +- .../data/sessions_unified/snapshot-sessions.yml | 2 +- .../spec_tests/data/transactions_unified/mongos-unpin.yml | 2 +- .../data/versioned_api/crud-api-version-1-strict.yml | 2 +- spec/spec_tests/data/versioned_api/crud-api-version-1.yml | 2 +- .../data/versioned_api/transaction-handling.yml | 8 ++++---- 15 files changed, 21 insertions(+), 20 deletions(-) diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml b/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml index 997d4d5761..b1d9f20e01 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml @@ -15,7 +15,7 @@ createEntities: runOnRequirements: - minServerVersion: "4.0.0" - topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] + topologies: [ replicaset, load-balanced, sharded ] serverless: forbid initialData: diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml b/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml index a2524d23ce..9ca9abf2e3 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml @@ -15,7 +15,7 @@ createEntities: runOnRequirements: - minServerVersion: "6.1.0" - topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] + topologies: [ replicaset, load-balanced, sharded ] serverless: forbid initialData: diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml b/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml index 47ad1d07ab..85133dae0a 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml @@ -74,7 +74,7 @@ tests: - description: Change Stream should error when _id is projected out runOnRequirements: - minServerVersion: "4.1.11" - topologies: [ replicaset, sharded-replicaset, load-balanced ] + topologies: [ replicaset, sharded, load-balanced ] operations: - name: createChangeStream object: *collection0 @@ -93,7 +93,7 @@ tests: - description: change stream errors on ElectionInProgress runOnRequirements: - minServerVersion: "4.2" - topologies: [ replicaset, sharded-replicaset, load-balanced ] + topologies: [ replicaset, sharded, load-balanced ] operations: - name: failPoint object: testRunner diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-pre_and_post_images.yml b/spec/spec_tests/data/change_streams_unified/change-streams-pre_and_post_images.yml index 2f6edd9820..6bc58eaf2d 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-pre_and_post_images.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-pre_and_post_images.yml @@ -4,7 +4,7 @@ schemaVersion: "1.4" runOnRequirements: - minServerVersion: "6.0.0" - topologies: [ replicaset, sharded-replicaset, load-balanced ] + topologies: [ replicaset, sharded, load-balanced ] serverless: forbid createEntities: diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml b/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml index 5c69e23010..c5b7a874d3 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml @@ -5,7 +5,7 @@ schemaVersion: "1.7" runOnRequirements: - minServerVersion: "3.6" - topologies: [ replicaset, sharded-replicaset, load-balanced ] + topologies: [ replicaset, sharded, load-balanced ] serverless: forbid createEntities: diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml b/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml index 5879b59c6a..5cc6d423a4 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml @@ -5,7 +5,7 @@ schemaVersion: "1.7" runOnRequirements: - minServerVersion: "4.3.1" - topologies: [ replicaset, sharded-replicaset, load-balanced ] + topologies: [ replicaset, sharded, load-balanced ] serverless: forbid createEntities: diff --git a/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml b/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml index 7a15ba4b54..e6289047bf 100644 --- a/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml +++ b/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml @@ -2,7 +2,7 @@ description: "change-streams-showExpandedEvents" schemaVersion: "1.7" runOnRequirements: - minServerVersion: "6.0.0" - topologies: [ replicaset, sharded-replicaset, sharded ] + topologies: [ replicaset, sharded ] serverless: forbid createEntities: - client: @@ -276,7 +276,8 @@ tests: - description: "when showExpandedEvents is true, shardCollection events are reported" runOnRequirements: - - topologies: [ sharded-replicaset, sharded ] + # Note: minServerVersion is specified in top-level runOnRequirements + - topologies: [ sharded ] operations: - name: dropCollection object: *shardedDb diff --git a/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml b/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml index cb67304c74..dc664ab76b 100644 --- a/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml +++ b/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml @@ -33,7 +33,7 @@ tests: - minServerVersion: "4.0" topologies: [ replicaset ] - minServerVersion: "4.1.7" - topologies: [ sharded-replicaset ] + topologies: [ sharded ] operations: - name: failPoint object: testRunner diff --git a/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml b/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml index 3d9672cdca..689328818b 100644 --- a/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml +++ b/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml @@ -33,7 +33,7 @@ tests: - minServerVersion: "4.0" topologies: [ replicaset ] - minServerVersion: "4.1.7" - topologies: [ sharded-replicaset ] + topologies: [ sharded ] operations: - name: failPoint object: testRunner diff --git a/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml b/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml index 1d5dce8933..91d785e47e 100644 --- a/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml +++ b/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml @@ -4,7 +4,7 @@ schemaVersion: "1.0" runOnRequirements: - minServerVersion: "5.0" - topologies: [replicaset, sharded-replicaset] + topologies: [replicaset, sharded] createEntities: - client: diff --git a/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml b/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml index 2f5fc23125..bcf0f7eec6 100644 --- a/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml +++ b/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml @@ -4,7 +4,7 @@ schemaVersion: "1.0" runOnRequirements: - minServerVersion: "5.0" - topologies: [replicaset, sharded-replicaset] + topologies: [replicaset, sharded] createEntities: - client: diff --git a/spec/spec_tests/data/transactions_unified/mongos-unpin.yml b/spec/spec_tests/data/transactions_unified/mongos-unpin.yml index c13798acaa..5f7e78e5e8 100644 --- a/spec/spec_tests/data/transactions_unified/mongos-unpin.yml +++ b/spec/spec_tests/data/transactions_unified/mongos-unpin.yml @@ -4,7 +4,7 @@ schemaVersion: '1.4' runOnRequirements: - minServerVersion: '4.2' - topologies: [ sharded-replicaset ] + topologies: [ sharded ] createEntities: - client: diff --git a/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml b/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml index 3e1a0cdbb6..17c4bee0f9 100644 --- a/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +++ b/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml @@ -231,7 +231,7 @@ tests: - description: "estimatedDocumentCount appends declared API version" # See: https://jira.mongodb.org/browse/SERVER-63850 runOnRequirements: - - minServerVersion: "5.0.8" + - minServerVersion: "5.0.9" maxServerVersion: "5.0.99" - minServerVersion: "5.3.2" operations: diff --git a/spec/spec_tests/data/versioned_api/crud-api-version-1.yml b/spec/spec_tests/data/versioned_api/crud-api-version-1.yml index 397419c477..50135c1458 100644 --- a/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +++ b/spec/spec_tests/data/versioned_api/crud-api-version-1.yml @@ -225,7 +225,7 @@ tests: - description: "estimatedDocumentCount appends declared API version" # See: https://jira.mongodb.org/browse/SERVER-63850 runOnRequirements: - - minServerVersion: "5.0.8" + - minServerVersion: "5.0.9" maxServerVersion: "5.0.99" - minServerVersion: "5.3.2" operations: diff --git a/spec/spec_tests/data/versioned_api/transaction-handling.yml b/spec/spec_tests/data/versioned_api/transaction-handling.yml index 7e79d20e21..bc7cc318dc 100644 --- a/spec/spec_tests/data/versioned_api/transaction-handling.yml +++ b/spec/spec_tests/data/versioned_api/transaction-handling.yml @@ -1,10 +1,10 @@ description: "Transaction handling" -schemaVersion: "1.1" +schemaVersion: "1.3" runOnRequirements: - minServerVersion: "4.9" - topologies: [ replicaset, sharded-replicaset ] + topologies: [ replicaset, sharded, load-balanced ] createEntities: - client: @@ -46,7 +46,7 @@ initialData: tests: - description: "All commands in a transaction declare an API version" runOnRequirements: - - topologies: [ replicaset, sharded-replicaset ] + - topologies: [ replicaset, sharded, load-balanced ] operations: - name: startTransaction object: *session @@ -87,7 +87,7 @@ tests: <<: *expectedApiVersion - description: "abortTransaction includes an API version" runOnRequirements: - - topologies: [ replicaset, sharded-replicaset ] + - topologies: [ replicaset, sharded, load-balanced ] operations: - name: startTransaction object: *session From fcedc87cef20bf2c2707ac3dacd01320979be143 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 25 May 2023 15:33:08 -0600 Subject: [PATCH 124/198] RUBY-3226 Update libmongocrypt payloads to new QE protocol (#2718) * update the QE specs * bump libmongocrypt-helper to 1.8.0 * update prose specs (server version >= 7, linting) * linting * didn't mean to commit this file * more prose specs to be updated * use the right version of fle2v2 specs they do a funky thing where they treat nil values as implying the absence of a field; had to do some work arounds to make that work. Hopefully it doesn't break anything new... * disallow encrypted collections on older servers * eccCollection is not to be created * enable linting on a couple more files * allow constants to be loaded after the file loads * some of these constants need to be in the utils namespace * don't need both the min_fcv and min_server_version constraints --- .rubocop.yml | 51 +- gemfiles/standard.rb | 9 +- lib/mongo/collection/queryable_encryption.rb | 117 ++- .../crypt/kms/azure/credentials_retriever.rb | 1 + ...tomatic_data_encryption_keys_prose_spec.rb | 151 ++-- .../decryption_events_prose_spec.rb | 117 +-- .../explicit_queryable_encryption_spec.rb | 69 +- .../mongocryptd_prose_spec.rb | 37 +- .../on_demand_azure_credentials_spec.rb | 2 - .../queryable_encryption_examples_spec.rb | 25 +- .../range_explicit_encryption_prose_spec.rb | 161 ++-- .../rewrap_prose_spec.rb | 64 +- ...nique_index_on_key_alt_names_prose_spec.rb | 36 +- spec/runners/transactions/test.rb | 13 +- .../spec_tests/client_side_encryption_spec.rb | 11 +- ...sis.yml => fle2v2-BypassQueryAnalysis.yml} | 47 +- .../{fle2-Compact.yml => fle2v2-Compact.yml} | 7 +- .../fle2v2-CreateCollection-OldServer.yml | 39 + ...ection.yml => fle2v2-CreateCollection.yml} | 755 +++++------------- ...ata.yml => fle2v2-DecryptExistingData.yml} | 5 +- .../{fle2-Delete.yml => fle2v2-Delete.yml} | 40 +- ...EncryptedFields-vs-EncryptedFieldsMap.yml} | 10 +- ... fle2v2-EncryptedFields-vs-jsonSchema.yml} | 23 +- ...=> fle2v2-EncryptedFieldsMap-defaults.yml} | 10 +- ...Update.yml => fle2v2-FindOneAndUpdate.yml} | 74 +- ...exed.yml => fle2v2-InsertFind-Indexed.yml} | 21 +- ...ed.yml => fle2v2-InsertFind-Unindexed.yml} | 7 +- ...2-MissingKey.yml => fle2v2-MissingKey.yml} | 7 +- ...Encryption.yml => fle2v2-NoEncryption.yml} | 5 +- ...te.yml => fle2v2-Range-Date-Aggregate.yml} | 29 +- ....yml => fle2v2-Range-Date-Correctness.yml} | 9 +- ...elete.yml => fle2v2-Range-Date-Delete.yml} | 48 +- ...=> fle2v2-Range-Date-FindOneAndUpdate.yml} | 47 +- ...d.yml => fle2v2-Range-Date-InsertFind.yml} | 29 +- ...pdate.yml => fle2v2-Range-Date-Update.yml} | 87 +- ...yml => fle2v2-Range-Decimal-Aggregate.yml} | 29 +- ...l => fle2v2-Range-Decimal-Correctness.yml} | 9 +- ...te.yml => fle2v2-Range-Decimal-Delete.yml} | 48 +- ...fle2v2-Range-Decimal-FindOneAndUpdate.yml} | 47 +- ...ml => fle2v2-Range-Decimal-InsertFind.yml} | 29 +- ...te.yml => fle2v2-Range-Decimal-Update.yml} | 87 +- ...e2v2-Range-DecimalPrecision-Aggregate.yml} | 29 +- ...v2-Range-DecimalPrecision-Correctness.yml} | 9 +- ... fle2v2-Range-DecimalPrecision-Delete.yml} | 48 +- ...nge-DecimalPrecision-FindOneAndUpdate.yml} | 47 +- ...2v2-Range-DecimalPrecision-InsertFind.yml} | 29 +- ... fle2v2-Range-DecimalPrecision-Update.yml} | 89 +-- ....yml => fle2v2-Range-Double-Aggregate.yml} | 29 +- ...ml => fle2v2-Range-Double-Correctness.yml} | 9 +- ...ete.yml => fle2v2-Range-Double-Delete.yml} | 48 +- ... fle2v2-Range-Double-FindOneAndUpdate.yml} | 47 +- ...yml => fle2v2-Range-Double-InsertFind.yml} | 29 +- ...ate.yml => fle2v2-Range-Double-Update.yml} | 87 +- ...le2v2-Range-DoublePrecision-Aggregate.yml} | 29 +- ...2v2-Range-DoublePrecision-Correctness.yml} | 9 +- ...> fle2v2-Range-DoublePrecision-Delete.yml} | 48 +- ...ange-DoublePrecision-FindOneAndUpdate.yml} | 47 +- ...e2v2-Range-DoublePrecision-InsertFind.yml} | 29 +- ...> fle2v2-Range-DoublePrecision-Update.yml} | 91 +-- ...ate.yml => fle2v2-Range-Int-Aggregate.yml} | 29 +- ...s.yml => fle2v2-Range-Int-Correctness.yml} | 9 +- ...Delete.yml => fle2v2-Range-Int-Delete.yml} | 48 +- ... => fle2v2-Range-Int-FindOneAndUpdate.yml} | 47 +- ...nd.yml => fle2v2-Range-Int-InsertFind.yml} | 29 +- ...Update.yml => fle2v2-Range-Int-Update.yml} | 89 +-- ...te.yml => fle2v2-Range-Long-Aggregate.yml} | 29 +- ....yml => fle2v2-Range-Long-Correctness.yml} | 9 +- ...elete.yml => fle2v2-Range-Long-Delete.yml} | 48 +- ...=> fle2v2-Range-Long-FindOneAndUpdate.yml} | 47 +- ...d.yml => fle2v2-Range-Long-InsertFind.yml} | 29 +- ...pdate.yml => fle2v2-Range-Long-Update.yml} | 89 +-- ...ongType.yml => fle2v2-Range-WrongType.yml} | 9 +- .../{fle2-Update.yml => fle2v2-Update.yml} | 76 +- ...v2-validatorAndPartialFieldExpression.yml} | 18 +- .../encrypted_fields/encryptedFields.json | 1 - spec/support/utils.rb | 536 +++++++------ 76 files changed, 2160 insertions(+), 2147 deletions(-) rename spec/spec_tests/data/client_side_encryption/{fle2-BypassQueryAnalysis.yml => fle2v2-BypassQueryAnalysis.yml} (52%) rename spec/spec_tests/data/client_side_encryption/{fle2-Compact.yml => fle2v2-Compact.yml} (86%) create mode 100644 spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml rename spec/spec_tests/data/client_side_encryption/{fle2-CreateCollection.yml => fle2v2-CreateCollection.yml} (53%) rename spec/spec_tests/data/client_side_encryption/{fle2-DecryptExistingData.yml => fle2v2-DecryptExistingData.yml} (92%) rename spec/spec_tests/data/client_side_encryption/{fle2-Delete.yml => fle2v2-Delete.yml} (68%) rename spec/spec_tests/data/client_side_encryption/{fle2-EncryptedFields-vs-EncryptedFieldsMap.yml => fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml} (81%) rename spec/spec_tests/data/client_side_encryption/{fle2-EncryptedFields-vs-jsonSchema.yml => fle2v2-EncryptedFields-vs-jsonSchema.yml} (72%) rename spec/spec_tests/data/client_side_encryption/{fle2-EncryptedFieldsMap-defaults.yml => fle2v2-EncryptedFieldsMap-defaults.yml} (88%) rename spec/spec_tests/data/client_side_encryption/{fle2-FindOneAndUpdate.yml => fle2v2-FindOneAndUpdate.yml} (72%) rename spec/spec_tests/data/client_side_encryption/{fle2-InsertFind-Indexed.yml => fle2v2-InsertFind-Indexed.yml} (72%) rename spec/spec_tests/data/client_side_encryption/{fle2-InsertFind-Unindexed.yml => fle2v2-InsertFind-Unindexed.yml} (86%) rename spec/spec_tests/data/client_side_encryption/{fle2-MissingKey.yml => fle2v2-MissingKey.yml} (72%) rename spec/spec_tests/data/client_side_encryption/{fle2-NoEncryption.yml => fle2v2-NoEncryption.yml} (85%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Date-Aggregate.yml => fle2v2-Range-Date-Aggregate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Date-Correctness.yml => fle2v2-Range-Date-Correctness.yml} (96%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Date-Delete.yml => fle2v2-Range-Date-Delete.yml} (70%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Date-FindOneAndUpdate.yml => fle2v2-Range-Date-FindOneAndUpdate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Date-InsertFind.yml => fle2v2-Range-Date-InsertFind.yml} (75%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Date-Update.yml => fle2v2-Range-Date-Update.yml} (65%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Decimal-Aggregate.yml => fle2v2-Range-Decimal-Aggregate.yml} (75%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Decimal-Correctness.yml => fle2v2-Range-Decimal-Correctness.yml} (96%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Decimal-Delete.yml => fle2v2-Range-Decimal-Delete.yml} (65%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Decimal-FindOneAndUpdate.yml => fle2v2-Range-Decimal-FindOneAndUpdate.yml} (75%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Decimal-InsertFind.yml => fle2v2-Range-Decimal-InsertFind.yml} (75%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Decimal-Update.yml => fle2v2-Range-Decimal-Update.yml} (63%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DecimalPrecision-Aggregate.yml => fle2v2-Range-DecimalPrecision-Aggregate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DecimalPrecision-Correctness.yml => fle2v2-Range-DecimalPrecision-Correctness.yml} (96%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DecimalPrecision-Delete.yml => fle2v2-Range-DecimalPrecision-Delete.yml} (70%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DecimalPrecision-FindOneAndUpdate.yml => fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml} (76%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DecimalPrecision-InsertFind.yml => fle2v2-Range-DecimalPrecision-InsertFind.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DecimalPrecision-Update.yml => fle2v2-Range-DecimalPrecision-Update.yml} (65%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Double-Aggregate.yml => fle2v2-Range-Double-Aggregate.yml} (79%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Double-Correctness.yml => fle2v2-Range-Double-Correctness.yml} (95%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Double-Delete.yml => fle2v2-Range-Double-Delete.yml} (70%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Double-FindOneAndUpdate.yml => fle2v2-Range-Double-FindOneAndUpdate.yml} (79%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Double-InsertFind.yml => fle2v2-Range-Double-InsertFind.yml} (79%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Double-Update.yml => fle2v2-Range-Double-Update.yml} (69%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DoublePrecision-Aggregate.yml => fle2v2-Range-DoublePrecision-Aggregate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DoublePrecision-Correctness.yml => fle2v2-Range-DoublePrecision-Correctness.yml} (96%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DoublePrecision-Delete.yml => fle2v2-Range-DoublePrecision-Delete.yml} (70%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DoublePrecision-FindOneAndUpdate.yml => fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DoublePrecision-InsertFind.yml => fle2v2-Range-DoublePrecision-InsertFind.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-DoublePrecision-Update.yml => fle2v2-Range-DoublePrecision-Update.yml} (65%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Int-Aggregate.yml => fle2v2-Range-Int-Aggregate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Int-Correctness.yml => fle2v2-Range-Int-Correctness.yml} (96%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Int-Delete.yml => fle2v2-Range-Int-Delete.yml} (70%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Int-FindOneAndUpdate.yml => fle2v2-Range-Int-FindOneAndUpdate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Int-InsertFind.yml => fle2v2-Range-Int-InsertFind.yml} (75%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Int-Update.yml => fle2v2-Range-Int-Update.yml} (65%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Long-Aggregate.yml => fle2v2-Range-Long-Aggregate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Long-Correctness.yml => fle2v2-Range-Long-Correctness.yml} (96%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Long-Delete.yml => fle2v2-Range-Long-Delete.yml} (70%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Long-FindOneAndUpdate.yml => fle2v2-Range-Long-FindOneAndUpdate.yml} (77%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Long-InsertFind.yml => fle2v2-Range-Long-InsertFind.yml} (75%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-Long-Update.yml => fle2v2-Range-Long-Update.yml} (65%) rename spec/spec_tests/data/client_side_encryption/{fle2-Range-WrongType.yml => fle2v2-Range-WrongType.yml} (80%) rename spec/spec_tests/data/client_side_encryption/{fle2-Update.yml => fle2v2-Update.yml} (72%) rename spec/spec_tests/data/client_side_encryption/{fle2-validatorAndPartialFieldExpression.yml => fle2v2-validatorAndPartialFieldExpression.yml} (65%) diff --git a/.rubocop.yml b/.rubocop.yml index 97efe7186e..3f63fb451b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -44,9 +44,56 @@ Bundler/OrderedGems: Gemspec/OrderedDependencies: Enabled: false +Layout/SpaceInsideArrayLiteralBrackets: + EnforcedStyle: space + +Layout/SpaceInsidePercentLiteralDelimiters: + Enabled: false + +Metrics/ModuleLength: + Enabled: false + +Metrics/MethodLength: + Max: 20 + +RSpec/BeforeAfterAll: + Enabled: false + +# Ideally, we'd use this one, too, but our tests have not historically followed +# this style and it's not worth changing right now, IMO +RSpec/DescribeClass: + Enabled: false + +RSpec/ImplicitExpect: + EnforcedStyle: is_expected + +RSpec/MultipleExpectations: + Enabled: false + +RSpec/MultipleMemoizedHelpers: + Enabled: false + +RSpec/NestedGroups: + Enabled: false + Style/Documentation: Exclude: - 'spec/**/*' -Metrics/MethodLength: - Max: 20 +Style/ModuleFunction: + EnforcedStyle: extend_self + +Style/OptionalBooleanParameter: + Enabled: false + +Style/ParallelAssignment: + Enabled: false + +Style/TernaryParentheses: + EnforcedStyle: require_parentheses_when_complex + +Style/TrailingCommaInArrayLiteral: + Enabled: false + +Style/TrailingCommaInHashLiteral: + Enabled: false diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index bfa3fa3726..89a68c5548 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# rubocop:todo all +# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength def standard_dependencies gem 'yard' gem 'ffi' @@ -29,7 +29,7 @@ def standard_dependencies # for static analysis -- ignore ruby < 2.6 because of rubocop # version incompatibilities - if RUBY_VERSION > "2.5.99" + if RUBY_VERSION > '2.5.99' gem 'rubocop', '~> 1.45.1' gem 'rubocop-performance', '~> 1.16.0' gem 'rubocop-rake', '~> 0.6.0' @@ -66,7 +66,6 @@ def standard_dependencies gem 'solargraph', platforms: :mri end - if ENV['FLE'] == 'helper' - gem 'libmongocrypt-helper', '~> 1.7.0' - end + gem 'libmongocrypt-helper', '~> 1.8.0' if ENV['FLE'] == 'helper' end +# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength diff --git a/lib/mongo/collection/queryable_encryption.rb b/lib/mongo/collection/queryable_encryption.rb index 703bffc3dc..2bb93197e3 100644 --- a/lib/mongo/collection/queryable_encryption.rb +++ b/lib/mongo/collection/queryable_encryption.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all # Copyright (C) 2014-2022 MongoDB Inc. # @@ -22,6 +21,8 @@ class Collection # # @api private module QueryableEncryption + # The minimum wire version for QE2 support + QE2_MIN_WIRE_VERSION = 21 # Creates auxiliary collections and indices for queryable encryption if necessary. # @@ -32,32 +33,17 @@ module QueryableEncryption # # @return [ Result ] The result of provided block. def maybe_create_qe_collections(encrypted_fields, client, session) - encrypted_fields = if encrypted_fields - encrypted_fields - elsif encrypted_fields_map - encrypted_fields_map[namespace] || {} - else - {} - end - if encrypted_fields.empty? - return yield - end + encrypted_fields = encrypted_fields_from(encrypted_fields) + return yield if encrypted_fields.empty? + + check_wire_version! emm_collections(encrypted_fields).each do |coll| context = Operation::Context.new(client: client, session: session) - Operation::Create.new( - selector: { - create: coll, - clusteredIndex: { - key: { - _id: 1 - }, - unique: true - } - }, - db_name: database.name, - ).execute(next_primary(nil, session), context: context) + create_operation_for(coll) + .execute(next_primary(nil, session), context: context) end + yield(encrypted_fields).tap do |result| indexes.create_one(__safeContent__: 1) if result end @@ -73,31 +59,25 @@ def maybe_create_qe_collections(encrypted_fields, client, session) # @return [ Result ] The result of provided block. def maybe_drop_emm_collections(encrypted_fields, client, session) encrypted_fields = if encrypted_fields - encrypted_fields - elsif encrypted_fields_map - if encrypted_fields_map[namespace] - encrypted_fields_map[namespace] - else - database.list_collections(filter: { name: name }) - .first - &.fetch(:options, {}) - &.fetch(:encryptedFields, {}) || {} - end - else - {} - end - if encrypted_fields.empty? - return yield - end + encrypted_fields + elsif encrypted_fields_map + encrypted_fields_for_drop_from_map + else + {} + end + + return yield if encrypted_fields.empty? + emm_collections(encrypted_fields).each do |coll| context = Operation::Context.new(client: client, session: session) operation = Operation::Drop.new( selector: { drop: coll }, db_name: database.name, - session: session, + session: session ) do_drop(operation, session, context) end + yield end @@ -112,11 +92,66 @@ def maybe_drop_emm_collections(encrypted_fields, client, session) def emm_collections(encrypted_fields) [ encrypted_fields['escCollection'] || "enxcol_.#{name}.esc", - encrypted_fields['eccCollection'] || "enxcol_.#{name}.ecc", encrypted_fields['ecocCollection'] || "enxcol_.#{name}.ecoc", ] end + # Creating encrypted collections is only supported on 7.0.0 and later + # (wire version 21+). + # + # @raise [ Mongo::Error ] if the wire version is not + # recent enough + def check_wire_version! + return unless next_primary.max_wire_version < QE2_MIN_WIRE_VERSION + + raise Mongo::Error, + 'Driver support of Queryable Encryption is incompatible with server. ' \ + 'Upgrade server to use Queryable Encryption.' + end + + # Tries to return the encrypted fields from the argument. If the argument + # is nil, tries to find the encrypted fields from the + # encrypted_fields_map. + # + # @param [ Hash | nil ] fields the encrypted fields + # + # @return [ Hash ] the encrypted fields + def encrypted_fields_from(fields) + fields || + (encrypted_fields_map && encrypted_fields_map[namespace]) || + {} + end + + # Tries to return the encrypted fields from the {{encrypted_fields_map}} + # value, for the current namespace. + # + # @return [ Hash | nil ] the encrypted fields, if found + def encrypted_fields_for_drop_from_map + encrypted_fields_map[namespace] || + database.list_collections(filter: { name: name }) + .first + &.fetch(:options, {}) + &.fetch(:encryptedFields, {}) || + {} + end + + # Returns a new create operation for the given collection. + # + # @param [ String ] coll the name of the collection to create. + # + # @return [ Operation::Create ] the new create operation. + def create_operation_for(coll) + Operation::Create.new( + selector: { + create: coll, + clusteredIndex: { + key: { _id: 1 }, + unique: true + } + }, + db_name: database.name + ) + end end end end diff --git a/lib/mongo/crypt/kms/azure/credentials_retriever.rb b/lib/mongo/crypt/kms/azure/credentials_retriever.rb index f008e84884..88787d4409 100644 --- a/lib/mongo/crypt/kms/azure/credentials_retriever.rb +++ b/lib/mongo/crypt/kms/azure/credentials_retriever.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:todo all # Copyright (C) 2019-2021 MongoDB Inc. # diff --git a/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb b/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb index 3792e0113b..a151ece93b 100644 --- a/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb +++ b/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' @@ -8,7 +7,7 @@ require_libmongocrypt require_enterprise require_topology :replica_set, :sharded, :load_balanced - min_server_fcv '6.0' + min_server_version '7.0.0-rc0' include_context 'define shared FLE helpers' @@ -41,104 +40,110 @@ authorized_client.use(test_database_name).database end - before(:each) do + before do authorized_client.use(key_vault_db)[key_vault_coll].drop authorized_client.use(test_database_name).database.drop end shared_examples 'creates data keys automatically' do - it 'fails document validation when insert unencrypted value' do - opts = { - encrypted_fields: { - fields: [{ - path: 'ssn', - bsonType: 'string', - keyId: nil - }] + let(:opts) do + { encrypted_fields: { fields: [ field ] } } + end + + context 'when insert unencrypted value' do + let(:field) do + { + path: 'ssn', + bsonType: 'string', + keyId: nil } - } - client_encryption.create_encrypted_collection( - database, - 'testing1', - opts, - kms_provider, - master_key - ) - expect do - database['testing1'].insert_one(ssn: '123-45-6789') - end.to raise_error(Mongo::Error::OperationFailure, /Document failed validation/) + end + + it 'fails document validation' do + client_encryption.create_encrypted_collection( + database, 'testing1', opts, kms_provider, master_key + ) + expect { database['testing1'].insert_one(ssn: '123-45-6789') } + .to raise_error(Mongo::Error::OperationFailure, /Document failed validation/) + end end it 'fails when missing encrypted field' do expect do client_encryption.create_encrypted_collection( - database, - 'testing1', - {}, - kms_provider, - master_key + database, 'testing1', {}, kms_provider, master_key ) end.to raise_error(ArgumentError, /coll_opts must contain :encrypted_fields/) end - it 'fails when invalid keyId provided' do - opts = { - encrypted_fields: { - fields: [{ - path: 'ssn', - bsonType: 'string', - keyId: false - }] + context 'when invalid keyId provided' do + let(:field) do + { + path: 'ssn', + bsonType: 'string', + keyId: false } - } - expect do - client_encryption.create_encrypted_collection( - database, - 'testing1', - opts, - kms_provider, - master_key - ) - end.to raise_error(Mongo::Error::CryptError, /keyId' is the wrong type/) + end + + it 'fails' do + expect do + client_encryption.create_encrypted_collection( + database, 'testing1', opts, kms_provider, master_key + ) + end.to raise_error(Mongo::Error::CryptError, /keyId' is the wrong type/) + end end - it 'successfully inserts encrypted value' do - opts = { - encrypted_fields: { - fields: [{ - path: 'ssn', - bsonType: 'string', - keyId: nil - }] + context 'when configured correctly' do + let(:field) do + { + path: 'ssn', + bsonType: 'string', + keyId: nil } - } - _, new_encrypted_fields = client_encryption.create_encrypted_collection( - database, - 'testing1', - opts, - kms_provider, - master_key - ) - key_id = new_encrypted_fields[:fields].first[:keyId] - encrypted_payload = client_encryption.encrypt( - '123-45-6789', - key_id: key_id, - algorithm: 'Unindexed' - ) - expect do - database['testing1'].insert_one(ssn: encrypted_payload) - end.not_to raise_error + end + + let(:new_encrypted_fields) do + _, new_encrypted_fields = client_encryption.create_encrypted_collection( + database, 'testing1', opts, kms_provider, master_key + ) + + new_encrypted_fields + end + + let(:key_id) do + new_encrypted_fields[:fields].first[:keyId] + end + + let(:encrypted_payload) do + client_encryption.encrypt( + '123-45-6789', + key_id: key_id, + algorithm: 'Unindexed' + ) + end + + it 'successfully inserts encrypted value' do + expect do + database['testing1'].insert_one(ssn: encrypted_payload) + end.not_to raise_error + end end end - context 'aws' do + context 'with aws' do let(:kms_provider) { 'aws' } - let(:master_key) { { region: 'us-east-1', key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0' } } + let(:master_key) do + { + region: 'us-east-1', + key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0' + } + end it_behaves_like 'creates data keys automatically' end - context 'local' do + context 'with local' do let(:kms_provider) { 'local' } let(:master_key) { { key: local_master_key } } diff --git a/spec/integration/client_side_encryption/decryption_events_prose_spec.rb b/spec/integration/client_side_encryption/decryption_events_prose_spec.rb index 8274d5a1e5..495aa52df4 100644 --- a/spec/integration/client_side_encryption/decryption_events_prose_spec.rb +++ b/spec/integration/client_side_encryption/decryption_events_prose_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' @@ -9,13 +8,13 @@ require_libmongocrypt include_context 'define shared FLE helpers' require_topology :replica_set - + min_server_version '7.0.0-rc0' let(:setup_client) do ClientRegistry.instance.new_local_client( SpecConfig.instance.addresses, SpecConfig.instance.test_options.merge( - database: SpecConfig.instance.test_db, + database: SpecConfig.instance.test_db ) ) end @@ -65,7 +64,7 @@ }, database: SpecConfig.instance.test_db, retry_reads: false, - max_read_retries: 0, + max_read_retries: 0 ) ) end @@ -76,7 +75,36 @@ let(:subscriber) { Mrss::EventSubscriber.new } - before(:each) do + let(:command_error) do + { + 'configureFailPoint' => 'failCommand', + 'mode' => { 'times' => 1 }, + 'data' => { + 'errorCode' => 123, + 'failCommands' => [ 'aggregate' ] + } + } + end + + let(:network_error) do + { + 'configureFailPoint' => 'failCommand', + 'mode' => { 'times' => 1 }, + 'data' => { + 'errorCode' => 123, + 'closeConnection' => true, + 'failCommands' => [ 'aggregate' ] + } + } + end + + let(:aggregate_event) do + subscriber.succeeded_events.detect do |evt| + evt.command_name == 'aggregate' + end + end + + before do setup_client[collection_name].drop setup_client[collection_name].create @@ -84,75 +112,48 @@ end it 'tests command error' do - setup_client.use(:admin).command( - { - "configureFailPoint" => "failCommand", - "mode" => { - "times" => 1 - }, - "data" => { - "errorCode" => 123, - "failCommands" => [ - "aggregate" - ] - } - } - ) + setup_client.use(:admin).command(command_error) expect do collection.aggregate([]).to_a end.to raise_error(Mongo::Error::OperationFailure, /Failing command (?:via|due to) 'failCommand' failpoint/) - expect(subscriber.failed_events.length).to eql(1) + expect(subscriber.failed_events.length).to be 1 end it 'tests network error' do - setup_client.use(:admin).command( - { - "configureFailPoint" => "failCommand", - "mode" => { - "times" => 1 - }, - "data" => { - "errorCode" => 123, - "closeConnection": true, - "failCommands" => [ - "aggregate" - ] - } - } - ) + setup_client.use(:admin).command(network_error) expect do collection.aggregate([]).to_a end.to raise_error(Mongo::Error::SocketError) - expect(subscriber.failed_events.length).to eql(1) + expect(subscriber.failed_events.length).to be 1 end - it 'tests decrypt error' do - collection.insert_one(encrypted: malformed_ciphertext) - expect do - collection.aggregate([]).to_a - end.to raise_error(Mongo::Error::CryptError) - aggregate_event = subscriber.succeeded_events.detect do |evt| - evt.command_name == 'aggregate' + context 'when decrypt error' do + before do + collection.insert_one(encrypted: malformed_ciphertext) + end + + it 'fails' do + expect { collection.aggregate([]).to_a }.to raise_error(Mongo::Error::CryptError) + expect(aggregate_event).not_to be_nil + expect( + aggregate_event.reply.dig('cursor', 'firstBatch')&.first&.dig('encrypted') + ).to be_a(BSON::Binary) end - expect(aggregate_event).not_to be_nil - expect( - aggregate_event.reply.dig('cursor', 'firstBatch')&.first&.dig('encrypted') - ).to be_a_kind_of(BSON::Binary) end - it 'tests decrypt success' do - collection.insert_one(encrypted: ciphertext) - expect do - collection.aggregate([]).to_a - end.not_to raise_error - aggregate_event = subscriber.succeeded_events.detect do |evt| - evt.command_name == 'aggregate' + context 'when decrypt success' do + before do + collection.insert_one(encrypted: ciphertext) + end + + it 'succeeds' do + expect { collection.aggregate([]).to_a }.not_to raise_error + expect(aggregate_event).not_to be_nil + expect( + aggregate_event.reply.dig('cursor', 'firstBatch')&.first&.dig('encrypted') + ).to be_a(BSON::Binary) end - expect(aggregate_event).not_to be_nil - expect( - aggregate_event.reply.dig('cursor', 'firstBatch')&.first&.dig('encrypted') - ).to be_a_kind_of(BSON::Binary) end end diff --git a/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb b/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb index f9c5e3e6e3..66313efd46 100644 --- a/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb +++ b/spec/integration/client_side_encryption/explicit_queryable_encryption_spec.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' +# No need to rewrite existing specs to make the examples shorter, until/unless +# we revisit these specs and need to make substantial changes. +# rubocop:disable RSpec/ExampleLength describe 'Explicit Queryable Encryption' do require_libmongocrypt - min_server_version '6.0.0-rc8' + min_server_version '7.0.0-rc0' require_topology :replica_set, :sharded, :load_balanced include_context 'define shared FLE helpers' @@ -19,11 +21,11 @@ end let(:value) do - "encrypted indexed value" + 'encrypted indexed value' end let(:unindexed_value) do - "encrypted unindexed value" + 'encrypted unindexed value' end let(:key_vault_client) do @@ -57,81 +59,81 @@ ) end - before(:each) do + before do authorized_client[encrypted_coll].drop(encrypted_fields: encrypted_fields) authorized_client[encrypted_coll].create(encrypted_fields: encrypted_fields) authorized_client.use(key_vault_db)[key_vault_coll].drop - authorized_client.use(key_vault_db)[key_vault_coll, write_concern: {w: :majority}].insert_one(key1_document) + authorized_client.use(key_vault_db)[key_vault_coll, write_concern: { w: :majority }].insert_one(key1_document) end - after(:each) do + after do authorized_client[encrypted_coll].drop(encrypted_fields: encrypted_fields) authorized_client.use(key_vault_db)[key_vault_coll].drop end it 'can insert encrypted indexed and find' do insert_payload = client_encryption.encrypt( - value, key_id: key1_id, algorithm: "Indexed", contention_factor: 0 + value, key_id: key1_id, algorithm: 'Indexed', contention_factor: 0 ) encrypted_client[encrypted_coll].insert_one( - "encryptedIndexed" => insert_payload + 'encryptedIndexed' => insert_payload ) find_payload = client_encryption.encrypt( - value, key_id: key1_id, algorithm: "Indexed", query_type: "equality", contention_factor: 0 + value, key_id: key1_id, algorithm: 'Indexed', query_type: 'equality', contention_factor: 0 ) find_results = encrypted_client[encrypted_coll] - .find("encryptedIndexed" => find_payload) - .to_a + .find('encryptedIndexed' => find_payload) + .to_a expect(find_results.size).to eq(1) - expect(find_results.first["encryptedIndexed"]).to eq(value) + expect(find_results.first['encryptedIndexed']).to eq(value) end it 'can insert encrypted indexed and find with non-zero contention' do 10.times do insert_payload = client_encryption.encrypt( - value, key_id: key1_id, algorithm: "Indexed", contention_factor: 10 + value, key_id: key1_id, algorithm: 'Indexed', contention_factor: 10 ) encrypted_client[encrypted_coll].insert_one( - "encryptedIndexed" => insert_payload + 'encryptedIndexed' => insert_payload ) end find_payload = client_encryption.encrypt( - value, key_id: key1_id, algorithm: "Indexed", query_type: "equality", contention_factor: 0 + value, key_id: key1_id, algorithm: 'Indexed', query_type: 'equality', contention_factor: 0 ) find_results = encrypted_client[encrypted_coll] - .find("encryptedIndexed" => find_payload) - .to_a + .find('encryptedIndexed' => find_payload) + .to_a expect(find_results.size).to be < 10 find_results.each do |doc| - expect(doc["encryptedIndexed"]).to eq(value) + expect(doc['encryptedIndexed']).to eq(value) end - find_payload_2 = client_encryption.encrypt( - value, key_id: key1_id, algorithm: "Indexed", query_type: "equality", contention_factor: 10 + find_payload2 = client_encryption.encrypt( + value, key_id: key1_id, algorithm: 'Indexed', query_type: 'equality', contention_factor: 10 ) - find_results_2 = encrypted_client[encrypted_coll] - .find("encryptedIndexed" => find_payload_2) - .to_a - expect(find_results_2.size).to eq(10) - find_results_2.each do |doc| - expect(doc["encryptedIndexed"]).to eq(value) + find_results2 = encrypted_client[encrypted_coll] + .find('encryptedIndexed' => find_payload2) + .to_a + expect(find_results2.size).to eq(10) + find_results2.each do |doc| + expect(doc['encryptedIndexed']).to eq(value) end end it 'can insert encrypted unindexed' do insert_payload = client_encryption.encrypt( - unindexed_value, key_id: key1_id, algorithm: "Unindexed" + unindexed_value, key_id: key1_id, algorithm: 'Unindexed' ) encrypted_client[encrypted_coll].insert_one( - "_id" => 1, "encryptedUnindexed" => insert_payload + '_id' => 1, 'encryptedUnindexed' => insert_payload ) - find_results = encrypted_client[encrypted_coll].find("_id" => 1).to_a + find_results = encrypted_client[encrypted_coll].find('_id' => 1).to_a expect(find_results.size).to eq(1) - expect(find_results.first["encryptedUnindexed"]).to eq(unindexed_value) + expect(find_results.first['encryptedUnindexed']).to eq(unindexed_value) end it 'can roundtrip encrypted indexed' do payload = client_encryption.encrypt( - value, key_id: key1_id, algorithm: "Indexed", contention_factor: 0 + value, key_id: key1_id, algorithm: 'Indexed', contention_factor: 0 ) decrypted_value = client_encryption.decrypt(payload) expect(decrypted_value).to eq(value) @@ -139,9 +141,10 @@ it 'can roundtrip encrypted unindexed' do payload = client_encryption.encrypt( - unindexed_value, key_id: key1_id, algorithm: "Unindexed" + unindexed_value, key_id: key1_id, algorithm: 'Unindexed' ) decrypted_value = client_encryption.decrypt(payload) expect(decrypted_value).to eq(unindexed_value) end end +# rubocop:enable RSpec/ExampleLength diff --git a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb index 029140955f..41fe7ef46b 100644 --- a/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb +++ b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb @@ -1,18 +1,16 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' -describe 'mongcryptd prose tests' do +describe 'mongocryptd prose tests' do require_libmongocrypt require_enterprise + min_server_version '7.0.0-rc0' include_context 'define shared FLE helpers' include_context 'with local kms_providers' - let(:mongocryptd_uri) do - "mongodb://localhost:27777" - end + let(:mongocryptd_uri) { 'mongodb://localhost:27777' } let(:encryption_client) do new_local_client( @@ -22,15 +20,15 @@ kms_providers: kms_providers, kms_tls_options: kms_tls_options, key_vault_namespace: key_vault_namespace, - schema_map: { "auto_encryption.users" => schema_map }, + schema_map: { 'auto_encryption.users' => schema_map }, extra_options: extra_options, }, - database: 'auto_encryption', - ), + database: 'auto_encryption' + ) ) end - before(:each) do + before do skip 'This test requires crypt shared library' unless SpecConfig.instance.crypt_shared_lib_path key_vault_collection.drop @@ -69,7 +67,7 @@ def done! let!(:listener) do Thread.new do - TCPServer.new(27777).accept + TCPServer.new(27_777).accept connect_attempt.done! end end @@ -80,7 +78,7 @@ def done! it 'does not try to connect to mongocryptd' do encryption_client[:users].insert_one(ssn: ssn) - expect(connect_attempt.done?).to eq(false) + expect(connect_attempt.done?).to be false end end @@ -90,19 +88,18 @@ def done! crypt_shared_lib_path: SpecConfig.instance.crypt_shared_lib_path, crypt_shared_lib_required: true, mongocryptd_uri: mongocryptd_uri, - mongocryptd_spawn_args: [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27777"] + mongocryptd_spawn_args: [ '--pidfilepath=bypass-spawning-mongocryptd.pid', '--port=27777' ] } end + let(:mongocryptd_client) { new_local_client(mongocryptd_uri) } + it 'does not spawn mongocryptd' do - expect do - encryption_client[:users].insert_one(ssn: ssn) - end.not_to raise_error - - mongocryptd_client = new_local_client(mongocryptd_uri) - expect do - mongocryptd_client.database.command(hello: 1) - end.to raise_error(Mongo::Error::NoServerAvailable) + expect { encryption_client[:users].insert_one(ssn: ssn) } + .not_to raise_error + + expect { mongocryptd_client.database.command(hello: 1) } + .to raise_error(Mongo::Error::NoServerAvailable) end end end diff --git a/spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb b/spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb index 4d9c35291c..115cb457e2 100644 --- a/spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb +++ b/spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' -# rubocop:disable RSpec/DescribeClass describe 'On-demand Azure Credentials' do require_libmongocrypt include_context 'define shared FLE helpers' @@ -45,4 +44,3 @@ end end end -# rubocop:enable RSpec/DescribeClass diff --git a/spec/integration/client_side_encryption/queryable_encryption_examples_spec.rb b/spec/integration/client_side_encryption/queryable_encryption_examples_spec.rb index c2035b28b5..0b818d2aef 100644 --- a/spec/integration/client_side_encryption/queryable_encryption_examples_spec.rb +++ b/spec/integration/client_side_encryption/queryable_encryption_examples_spec.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' +# No need to rewrite existing specs to make the examples shorter, until/unless +# we revisit these specs and need to make substantial changes. +# rubocop:disable RSpec/ExampleLength describe 'Queryable encryption examples' do require_libmongocrypt - min_server_version '6.0.0-rc8' + min_server_version '7.0.0-rc0' require_topology :replica_set, :sharded, :load_balanced require_enterprise @@ -63,7 +65,7 @@ SpecConfig.instance.addresses, SpecConfig.instance.test_options.merge( auto_encryption_options: { - key_vault_namespace: "keyvault.datakeys", + key_vault_namespace: 'keyvault.datakeys', kms_providers: { local: { key: local_master_key @@ -84,22 +86,22 @@ # Auto encrypt an insert and find. encrypted_client['encrypted'].insert_one( _id: 1, - encrypted_indexed: "indexed_value", - encrypted_unindexed: "unindexed_value", + encrypted_indexed: 'indexed_value', + encrypted_unindexed: 'unindexed_value' ) find_results = encrypted_client['encrypted'].find( - encrypted_indexed: "indexed_value" + encrypted_indexed: 'indexed_value' ).to_a expect(find_results.size).to eq(1) - expect(find_results.first[:encrypted_indexed]).to eq("indexed_value") - expect(find_results.first[:encrypted_unindexed]).to eq("unindexed_value") + expect(find_results.first[:encrypted_indexed]).to eq('indexed_value') + expect(find_results.first[:encrypted_unindexed]).to eq('unindexed_value') # Find documents without decryption. find_results = authorized_client - .use('docs_examples')['encrypted'] - .find(_id: 1) - .to_a + .use('docs_examples')['encrypted'] + .find(_id: 1) + .to_a expect(find_results.size).to eq(1) expect(find_results.first[:encrypted_indexed]).to be_a(BSON::Binary) expect(find_results.first[:encrypted_unindexed]).to be_a(BSON::Binary) @@ -109,3 +111,4 @@ authorized_client.use('keyvault')['datakeys'].drop end end +# rubocop:enable RSpec/ExampleLength diff --git a/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb index 0cc8a9da3b..78266e8d79 100644 --- a/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb +++ b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' +# Unnecessary to rewrite a legacy test to use shorter examples; this can +# be revisited if these tests ever need to be significantly modified. +# rubocop:disable RSpec/ExampleLength describe 'Range Explicit Encryption' do - min_server_version '6.2' + min_server_version '7.0.0-rc0' require_libmongocrypt include_context 'define shared FLE helpers' @@ -12,13 +14,6 @@ key1_document['_id'] end - before(:each) do - authorized_client['explicit_encryption'].drop(encrypted_fields: encrypted_fields) - authorized_client['explicit_encryption'].create(encrypted_fields: encrypted_fields) - authorized_client.use(key_vault_db)[key_vault_coll].drop - authorized_client.use(key_vault_db)[key_vault_coll, write_concern: {w: :majority}].insert_one(key1_document) - end - let(:key_vault_client) do ClientRegistry.instance.new_local_client(SpecConfig.instance.addresses) end @@ -44,6 +39,13 @@ ) end + before do + authorized_client['explicit_encryption'].drop(encrypted_fields: encrypted_fields) + authorized_client['explicit_encryption'].create(encrypted_fields: encrypted_fields) + authorized_client.use(key_vault_db)[key_vault_coll].drop + authorized_client.use(key_vault_db)[key_vault_coll, write_concern: { w: :majority }].insert_one(key1_document) + end + shared_examples 'common cases' do it 'can decrypt a payload' do value = value_converter.call(6) @@ -51,7 +53,7 @@ value, { key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts } @@ -62,103 +64,103 @@ it 'can find encrypted range and return the maximum' do expr = { - "$and": [ - {"encrypted#{type}" => {"$gte": value_converter.call(6)}}, - {"encrypted#{type}" => {"$lte": value_converter.call(200)}} + '$and': [ + { "encrypted#{type}" => { '$gte': value_converter.call(6) } }, + { "encrypted#{type}" => { '$lte': value_converter.call(200) } } ] } find_payload = client_encryption.encrypt_expression( expr, { key_id: key1_id, - algorithm: "RangePreview", - query_type: "rangePreview", + algorithm: 'RangePreview', + query_type: 'rangePreview', contention_factor: 0, range_opts: range_opts } ) - results = encrypted_client['explicit_encryption'].find(find_payload, sort: {_id: 1}).to_a + results = encrypted_client['explicit_encryption'].find(find_payload, sort: { _id: 1 }).to_a expect(results.size).to eq(3) - value_converter.call([6, 30, 200]).each_with_index do |value, idx| + value_converter.call([ 6, 30, 200 ]).each_with_index do |value, idx| expect(results[idx]["encrypted#{type}"]).to eq(value) end end it 'can find encrypted range and return the minimum' do expr = { - "$and": [ - {"encrypted#{type}" => {"$gte": value_converter.call(0)}}, - {"encrypted#{type}" => {"$lte": value_converter.call(6)}} + '$and': [ + { "encrypted#{type}" => { '$gte': value_converter.call(0) } }, + { "encrypted#{type}" => { '$lte': value_converter.call(6) } } ] } find_payload = client_encryption.encrypt_expression( expr, { key_id: key1_id, - algorithm: "RangePreview", - query_type: "rangePreview", + algorithm: 'RangePreview', + query_type: 'rangePreview', contention_factor: 0, range_opts: range_opts } ) - results = encrypted_client['explicit_encryption'].find(find_payload, sort: {_id: 1}).to_a + results = encrypted_client['explicit_encryption'].find(find_payload, sort: { _id: 1 }).to_a expect(results.size).to eq(2) - value_converter.call([0, 6]).each_with_index do |value, idx| + value_converter.call([ 0, 6 ]).each_with_index do |value, idx| expect(results[idx]["encrypted#{type}"]).to eq(value) end end it 'can find encrypted range with an open range query' do expr = { - "$and": [ - { "encrypted#{type}" => { "$gt": value_converter.call(30) } } + '$and': [ + { "encrypted#{type}" => { '$gt': value_converter.call(30) } } ] } find_payload = client_encryption.encrypt_expression( expr, { key_id: key1_id, - algorithm: "RangePreview", - query_type: "rangePreview", + algorithm: 'RangePreview', + query_type: 'rangePreview', contention_factor: 0, range_opts: range_opts } ) - results = encrypted_client['explicit_encryption'].find(find_payload, sort: {_id: 1}).to_a + results = encrypted_client['explicit_encryption'].find(find_payload, sort: { _id: 1 }).to_a expect(results.size).to eq(1) expect(results.first["encrypted#{type}"]).to eq(value_converter.call(200)) end it 'can run an aggregation expression inside $expr' do - expr = {'$and': [ { '$lt': [ "$encrypted#{type}", value_converter.call(30) ] } ] } + expr = { '$and': [ { '$lt': [ "$encrypted#{type}", value_converter.call(30) ] } ] } find_payload = client_encryption.encrypt_expression( expr, { key_id: key1_id, - algorithm: "RangePreview", - query_type: "rangePreview", + algorithm: 'RangePreview', + query_type: 'rangePreview', contention_factor: 0, range_opts: range_opts } ) results = encrypted_client['explicit_encryption'].find( - {'$expr' => find_payload}, + { '$expr' => find_payload }, sort: { _id: 1 } ).to_a expect(results.size).to eq(2) - value_converter.call([0, 6]).each_with_index do |value, idx| + value_converter.call([ 0, 6 ]).each_with_index do |value, idx| expect(results[idx]["encrypted#{type}"]).to eq(value) end end it 'encrypting a document greater than the maximum errors' do - skip if %w(DoubleNoPrecision DecimalNoPrecision).include?(type) + skip if %w[ DoubleNoPrecision DecimalNoPrecision ].include?(type) expect do client_encryption.encrypt( value_converter.call(201), { key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts } @@ -166,8 +168,8 @@ end.to raise_error(Mongo::Error::CryptError, /less than or equal to the maximum value/) end - it 'encrypting a document of a different type errors ' do - skip if %w(DoubleNoPrecision DecimalNoPrecision).include?(type) + it 'encrypting a document of a different type errors' do + skip if %w[ DoubleNoPrecision DecimalNoPrecision ].include?(type) value = if type == 'Int' 6.0 else @@ -178,7 +180,7 @@ value, { key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts } @@ -187,13 +189,13 @@ end it 'setting precision errors if the type is not a double' do - skip if %w(DoublePrecision DoubleNoPrecision DecimalPrecision DecimalNoPrecision).include?(type) + skip if %w[ DoublePrecision DoubleNoPrecision DecimalPrecision DecimalNoPrecision ].include?(type) expect do client_encryption.encrypt( value_converter.call(6), { key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: { min: value_converter.call(0), @@ -207,13 +209,13 @@ end end - context 'Int' do + context 'when Int' do let(:type) do 'Int' end let(:value_converter) do - Proc.new do |value| + proc do |value| if value.is_a?(Array) value.map(&:to_i) else @@ -234,12 +236,12 @@ } end - before(:each) do - [0, 6, 30, 200].each_with_index do |num, idx| + before do + [ 0, 6, 30, 200 ].each_with_index do |num, idx| insert_payload = client_encryption.encrypt( num, key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts ) @@ -253,15 +255,15 @@ include_examples 'common cases' end - context 'Long' do + context 'when Long' do let(:type) do 'Long' end let(:value_converter) do - Proc.new do |value| + proc do |value| if value.is_a?(Array) - value.map { |i| BSON::Int64.new(i)} + value.map { |i| BSON::Int64.new(i) } else BSON::Int64.new(value) end @@ -280,12 +282,12 @@ } end - before(:each) do - [0, 6, 30, 200].each_with_index do |num, idx| + before do + [ 0, 6, 30, 200 ].each_with_index do |num, idx| insert_payload = client_encryption.encrypt( BSON::Int64.new(num), key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts ) @@ -299,13 +301,13 @@ include_examples 'common cases' end - context 'DoublePrecision' do + context 'when DoublePrecision' do let(:type) do 'DoublePrecision' end let(:value_converter) do - Proc.new do |value| + proc do |value| if value.is_a?(Array) value.map(&:to_f) else @@ -327,12 +329,12 @@ } end - before(:each) do - [0.0, 6.0, 30.0, 200.0].each_with_index do |num, idx| + before do + [ 0.0, 6.0, 30.0, 200.0 ].each_with_index do |num, idx| insert_payload = client_encryption.encrypt( num, key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts ) @@ -346,13 +348,13 @@ include_examples 'common cases' end - context 'DoubleNoPrecision' do + context 'when DoubleNoPrecision' do let(:type) do 'DoubleNoPrecision' end let(:value_converter) do - Proc.new do |value| + proc do |value| if value.is_a?(Array) value.map(&:to_f) else @@ -371,12 +373,12 @@ } end - before(:each) do - [0.0, 6.0, 30.0, 200.0].each_with_index do |num, idx| + before do + [ 0.0, 6.0, 30.0, 200.0 ].each_with_index do |num, idx| insert_payload = client_encryption.encrypt( num, key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts ) @@ -390,13 +392,13 @@ include_examples 'common cases' end - context 'Date' do + context 'when Date' do let(:type) do 'Date' end let(:value_converter) do - Proc.new do |value| + proc do |value| if value.is_a?(Array) value.map { |i| Time.new(i) } else @@ -417,12 +419,12 @@ } end - before(:each) do - [0, 6, 30, 200].each_with_index do |num, idx| + before do + [ 0, 6, 30, 200 ].each_with_index do |num, idx| insert_payload = client_encryption.encrypt( Time.new(num), key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts ) @@ -436,7 +438,7 @@ include_examples 'common cases' end - context 'DecimalPrecision' do + context 'when DecimalPrecision' do require_topology :replica_set let(:type) do @@ -444,9 +446,9 @@ end let(:value_converter) do - Proc.new do |value| + proc do |value| if value.is_a?(Array) - value.map { |val| BSON::Decimal128.new(val.to_s)} + value.map { |val| BSON::Decimal128.new(val.to_s) } else BSON::Decimal128.new(value.to_s) end @@ -466,12 +468,12 @@ } end - before(:each) do - %w[0 6 30 200].each_with_index do |num, idx| + before do + %w[ 0 6 30 200 ].each_with_index do |num, idx| insert_payload = client_encryption.encrypt( BSON::Decimal128.new(num), key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts ) @@ -485,7 +487,7 @@ include_examples 'common cases' end - context 'DecimalNoPrecision' do + context 'when DecimalNoPrecision' do require_topology :replica_set let(:type) do @@ -493,9 +495,9 @@ end let(:value_converter) do - Proc.new do |value| + proc do |value| if value.is_a?(Array) - value.map { |val| BSON::Decimal128.new(val.to_s)} + value.map { |val| BSON::Decimal128.new(val.to_s) } else BSON::Decimal128.new(value.to_s) end @@ -512,12 +514,12 @@ } end - before(:each) do - %w[0 6 30 200].each_with_index do |num, idx| + before do + %w[ 0 6 30 200 ].each_with_index do |num, idx| insert_payload = client_encryption.encrypt( BSON::Decimal128.new(num), key_id: key1_id, - algorithm: "RangePreview", + algorithm: 'RangePreview', contention_factor: 0, range_opts: range_opts ) @@ -531,3 +533,4 @@ include_examples 'common cases' end end +# rubocop:enable RSpec/ExampleLength diff --git a/spec/integration/client_side_encryption/rewrap_prose_spec.rb b/spec/integration/client_side_encryption/rewrap_prose_spec.rb index fada5ee6e7..9ba2e19bfc 100644 --- a/spec/integration/client_side_encryption/rewrap_prose_spec.rb +++ b/spec/integration/client_side_encryption/rewrap_prose_spec.rb @@ -1,14 +1,13 @@ # frozen_string_literal: true -# rubocop:todo all -require "spec_helper" +require 'spec_helper' -describe "RewrapManyDataKey" do +describe 'RewrapManyDataKey' do require_libmongocrypt - min_server_version "6.0.0" + min_server_version '7.0.0-rc0' require_topology :replica_set, :sharded, :load_balanced - include_context "define shared FLE helpers" + include_context 'define shared FLE helpers' let(:kms_providers) do {}.merge(aws_kms_providers) @@ -21,31 +20,31 @@ let(:master_keys) do { aws: { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + region: 'us-east-1', + key: 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0', }, azure: { - key_vault_endpoint: "key-vault-csfle.vault.azure.net", - key_name: "key-name-csfle", + key_vault_endpoint: 'key-vault-csfle.vault.azure.net', + key_name: 'key-name-csfle', }, gcp: { - project_id: "devprod-drivers", - location: "global", - key_ring: "key-ring-csfle", - key_name: "key-name-csfle", + project_id: 'devprod-drivers', + location: 'global', + key_ring: 'key-ring-csfle', + key_name: 'key-name-csfle', }, kmip: {} } end - before(:each) do + before do authorized_client.use('keyvault')['datakeys'].drop end - %i(aws azure gcp kmip local).each do |src_provider| - %i(aws azure gcp kmip local).each do |dst_provider| + %i[ aws azure gcp kmip local ].each do |src_provider| + %i[ aws azure gcp kmip local ].each do |dst_provider| context "with #{src_provider} as source provider and #{dst_provider} as destination provider" do - let(:client_encryption_1) do + let(:client_encryption1) do key_vault_client = ClientRegistry.instance.new_local_client( SpecConfig.instance.addresses, SpecConfig.instance.test_options @@ -56,11 +55,11 @@ kms_providers: kms_providers, kms_tls_options: { kmip: default_kms_tls_options_for_provider - }, + } ) end - let(:client_encryption_2) do + let(:client_encryption2) do key_vault_client = ClientRegistry.instance.new_local_client( SpecConfig.instance.addresses, SpecConfig.instance.test_options @@ -71,27 +70,36 @@ kms_providers: kms_providers, kms_tls_options: { kmip: default_kms_tls_options_for_provider - }, + } ) end - it 'rewraps' do - key_id = client_encryption_1.create_data_key( + let(:key_id) do + client_encryption1.create_data_key( src_provider.to_s, master_key: master_keys[src_provider] ) - ciphertext = client_encryption_1.encrypt( - "test", + end + + let(:ciphertext) do + client_encryption1.encrypt( + 'test', key_id: key_id, - algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' ) - client_encryption_2.rewrap_many_data_key( + end + + before do + client_encryption2.rewrap_many_data_key( {}, provider: dst_provider.to_s, master_key: master_keys[dst_provider] ) - expect(client_encryption_1.decrypt(ciphertext)).to eq('test') - expect(client_encryption_2.decrypt(ciphertext)).to eq('test') + end + + it 'rewraps', :aggregate_failures do + expect(client_encryption1.decrypt(ciphertext)).to eq('test') + expect(client_encryption2.decrypt(ciphertext)).to eq('test') end end end diff --git a/spec/integration/client_side_encryption/unique_index_on_key_alt_names_prose_spec.rb b/spec/integration/client_side_encryption/unique_index_on_key_alt_names_prose_spec.rb index a6689d3b53..51c5979129 100644 --- a/spec/integration/client_side_encryption/unique_index_on_key_alt_names_prose_spec.rb +++ b/spec/integration/client_side_encryption/unique_index_on_key_alt_names_prose_spec.rb @@ -1,19 +1,22 @@ # frozen_string_literal: true -# rubocop:todo all -require "spec_helper" +require 'spec_helper' -describe "Decryption events" do +# No need to rewrite legacy tests to use shorter examples, unless/until we +# revisit these tests and need to make more significant changes. +# rubocop:disable RSpec/ExampleLength +describe 'Decryption events' do require_enterprise - min_server_fcv "4.2" + min_server_fcv '4.2' require_libmongocrypt - include_context "define shared FLE helpers" + include_context 'define shared FLE helpers' + min_server_version '7.0.0-rc0' let(:client) do ClientRegistry.instance.new_local_client( SpecConfig.instance.addresses, SpecConfig.instance.test_options.merge( - database: SpecConfig.instance.test_db, + database: SpecConfig.instance.test_db ) ) end @@ -31,34 +34,34 @@ end let(:existing_key_id) do - client_encryption.create_data_key('local', key_alt_names: [existing_key_alt_name]) + client_encryption.create_data_key('local', key_alt_names: [ existing_key_alt_name ]) end - before(:each) do + before do client.use(key_vault_db)[key_vault_coll].drop - client.use(key_vault_db).command({ + client.use(key_vault_db).command( createIndexes: key_vault_coll, indexes: [ { - name: "keyAltNames_1", - key: { "keyAltNames": 1 }, + name: 'keyAltNames_1', + key: { keyAltNames: 1 }, unique: true, - partialFilterExpression: { keyAltNames: { "$exists" => true } }, + partialFilterExpression: { keyAltNames: { '$exists' => true } }, }, ], - writeConcern: { w: "majority" }, - }) + writeConcern: { w: 'majority' } + ) # Force key creation existing_key_id end it 'tests create_data_key' do expect do - client_encryption.create_data_key('local', key_alt_names: ['abc']) + client_encryption.create_data_key('local', key_alt_names: [ 'abc' ]) end.not_to raise_error expect do - client_encryption.create_data_key('local', key_alt_names: [existing_key_alt_name]) + client_encryption.create_data_key('local', key_alt_names: [ existing_key_alt_name ]) end.to raise_error(Mongo::Error::OperationFailure, /E11000/) # duplicate key error end @@ -83,3 +86,4 @@ end.not_to raise_error end end +# rubocop:enable RSpec/ExampleLength diff --git a/spec/runners/transactions/test.rb b/spec/runners/transactions/test.rb index 83fc4b50ea..ce54d16cd5 100644 --- a/spec/runners/transactions/test.rb +++ b/spec/runners/transactions/test.rb @@ -29,6 +29,9 @@ class TransactionsTest < CRUD::CRUDTestBase attr_reader :results + # @return [ Crud::Spec ] the top-level YAML specification object + attr_reader :spec + # Instantiate the new CRUDTest. # # @example Create the test. @@ -38,6 +41,9 @@ class TransactionsTest < CRUD::CRUDTestBase # @param [ Array ] data The documents the collection # must have before the test runs. # @param [ Hash ] test The test specification. + # @param [ true | false | Proc ] expectations_bson_types Whether bson + # types should be expected. If a Proc is given, it is invoked with the + # test as its argument, and should return true or false. # # @since 2.6.0 def initialize(crud_spec, data, test, expectations_bson_types: true) @@ -71,10 +77,11 @@ def initialize(crud_spec, data, test, expectations_bson_types: true) Operation.new(self, op) end - mode = if expectations_bson_types then :bson else nil end - if crud_spec.description == 'fle2-CreateCollection.yml' - mode = nil + if expectations_bson_types.respond_to?(:call) + expectations_bson_types = expectations_bson_types[self] end + + mode = if expectations_bson_types then :bson else nil end @expectations = BSON::ExtJSON.parse_obj(test['expectations'], mode: mode) if test['outcome'] diff --git a/spec/spec_tests/client_side_encryption_spec.rb b/spec/spec_tests/client_side_encryption_spec.rb index d45f670406..164151c7db 100644 --- a/spec/spec_tests/client_side_encryption_spec.rb +++ b/spec/spec_tests/client_side_encryption_spec.rb @@ -6,13 +6,20 @@ require 'runners/crud' require 'runners/transactions' +SPECS_IGNORING_BSON_TYPES = %w[ fle2v2-CreateCollection.yml ] + +# expect bson types for all specs EXCEPT those mentioned in +# SPECS_IGNORING_BSON_TYPES +EXPECTATIONS_BSON_TYPES = -> (test) { !SPECS_IGNORING_BSON_TYPES.include?(test.spec.description) } + describe 'Client-Side Encryption' do require_libmongocrypt require_enterprise + min_libmongocrypt_version '1.8.0' context 'with mongocryptd' do SpecConfig.instance.without_crypt_shared_lib_path do - define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: true) + define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: EXPECTATIONS_BSON_TYPES) end end @@ -24,7 +31,7 @@ fails_on_jruby SpecConfig.instance.require_crypt_shared do - define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: true) + define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: EXPECTATIONS_BSON_TYPES) end end end diff --git a/spec/spec_tests/data/client_side_encryption/fle2-BypassQueryAnalysis.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-BypassQueryAnalysis.yml similarity index 52% rename from spec/spec_tests/data/client_side_encryption/fle2-BypassQueryAnalysis.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-BypassQueryAnalysis.yml index e6b3d66505..1ef8197c29 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-BypassQueryAnalysis.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-BypassQueryAnalysis.yml @@ -1,12 +1,15 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} -key_vault_data: [{'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}}, {'_id': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +key_vault_data: [{'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "BypassQueryAnalysis decrypts" @@ -22,8 +25,8 @@ tests: "_id": 1, "encryptedIndexed": { "$binary": { - # Payload has an IndexKey of key1 and UserKey of key2. - "base64": "BHEBAAAFZAAgAAAAAHb62aV7+mqmaGcotPLdG3KP7S8diFwWMLM/5rYtqLrEBXMAIAAAAAAVJ6OWHRv3OtCozHpt3ZzfBhaxZirLv3B+G8PuaaO4EgVjACAAAAAAsZXWOWA+UiCBbrJNB6bHflB/cn7pWSvwWN2jw4FPeIUFcABQAAAAAMdD1nV2nqeI1eXEQNskDflCy8I7/HvvqDKJ6XxjhrPQWdLqjz+8GosGUsB7A8ee/uG9/guENuL25XD+Fxxkv1LLXtavHOlLF7iW0u9yabqqBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AE0AAAAAq83vqxI0mHYSNBI0VniQEkzZZBBDgeZh+h+gXEmOrSFtVvkUcnHWj/rfPW7iJ0G3UJ8zpuBmUM/VjOMJCY4+eDqdTiPIwX+/vNXegc8FZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsAA==", + # Payload has an IndexKey of key1 and UserKey of key1. + "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", "subType": "06" } } @@ -31,7 +34,7 @@ tests: - name: find arguments: filter: { "_id": 1 } - result: [{"_id": 1, "encryptedIndexed": "value123" }] + result: [{"_id": 1, "encryptedIndexed": "123" }] expectations: - command_started_event: command: @@ -45,6 +48,14 @@ tests: documents: - *doc0_encrypted ordered: true + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -73,29 +84,7 @@ tests: $db: keyvault readConcern: { level: "majority" } command_name: find - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find outcome: collection: data: - - {"_id": 1, "encryptedIndexed": { "$$type": "binData" }, "__safeContent__": [{ "$binary" : { "base64" : "ThpoKfQ8AkOzkFfNC1+9PF0pY2nIzfXvRdxQgjkNbBw=", "subType" : "00" } }] } \ No newline at end of file + - {"_id": 1, "encryptedIndexed": { "$$type": "binData" }, "__safeContent__": [{ "$binary" : { "base64" : "31eCYlbQoVboc5zwC8IoyJVSkag9PxREka8dkmbXJeY=", "subType" : "00" } }] } \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Compact.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Compact.yml similarity index 86% rename from spec/spec_tests/data/client_side_encryption/fle2-Compact.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Compact.yml index 6e3a198d02..8e88cc5203 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Compact.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Compact.yml @@ -1,11 +1,14 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}}, {'_id': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Compact works" diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml new file mode 100644 index 0000000000..f55001a429 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml @@ -0,0 +1,39 @@ +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "6.0.0" + maxServerVersion: "6.3.99" + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] + +database_name: &database_name "default" +collection_name: &collection_name "default" + +tests: + - description: "driver returns an error if creating a QEv2 collection on unsupported server" + clientOptions: + autoEncryptOpts: + kmsProviders: + aws: {} # Credentials filled in from environment. + encryptedFieldsMap: + default.encryptedCollection: { + "fields": [ + { + "path": "firstName", + "bsonType": "string", + "keyId": { "$binary": { "base64": "AAAAAAAAAAAAAAAAAAAAAA==", "subType": "04" }} + } + ] + } + + operations: + # Do an initial drop to remove collections that may exist from previous test runs. + - name: dropCollection + object: database + arguments: + collection: "encryptedCollection" + - name: createCollection + object: database + arguments: + collection: "encryptedCollection" + result: + errorContains: "Driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption." diff --git a/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml similarity index 53% rename from spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml index 3413b47b10..ea33865a21 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml @@ -1,6 +1,8 @@ -# This test requires libmongocrypt 1.5.0-alpha2. +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] @@ -14,10 +16,7 @@ tests: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: &encrypted_fields0 { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", + default.encryptedCollection: &encrypted_fields { "fields": [ { "path": "firstName", @@ -32,58 +31,54 @@ tests: - name: dropCollection object: database arguments: - collection: "encryptedCollection" + collection: &encrypted_collection_name "encryptedCollection" - name: createCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.esc" - - name: assertCollectionExists + collection: &esc_collection_name "enxcol_.encryptedCollection.esc" + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: &ecc_collection_name "enxcol_.encryptedCollection.ecc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" + collection: &ecoc_collection_name "enxcol_.encryptedCollection.ecoc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertIndexExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name index: __safeContent___1 expectations: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end @@ -91,33 +86,39 @@ tests: # State collections are created first. - command_started_event: command: - create: "enxcol_.encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "enxcol_.encryptedCollection.ecoc" + create: *ecoc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name # Data collection is created after. - command_started_event: command: - create: "encryptedCollection" - encryptedFields: *encrypted_fields0 + create: *encrypted_collection_name + encryptedFields: &encrypted_fields_expectation { + # Expect state collections are not included in the encryptedFields sent to the server. + "escCollection": null, + "ecocCollection": null, + "eccCollection": null, + "fields": [ + { + "path": "firstName", + "bsonType": "string", + "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} + } + ] + } command_name: create database_name: *database_name # Index on __safeContents__ is then created. - command_started_event: command: - createIndexes: "encryptedCollection" + createIndexes: *encrypted_collection_name indexes: - name: __safeContent___1 key: { __safeContent__: 1 } @@ -130,79 +131,61 @@ tests: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: &encrypted_fields1 { - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }}, - "queries": { - "queryType": "equality", - "contention": { - "$numberLong": "0" - } - } - } - ] - } + default.encryptedCollection: *encrypted_fields operations: # Do an initial drop to remove collections that may exist from previous test runs. - name: dropCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: createCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.esc" - - name: assertCollectionExists + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" + collection: *ecoc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertIndexExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name index: __safeContent___1 expectations: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end @@ -210,33 +193,27 @@ tests: # State collections are created first. - command_started_event: command: - create: "enxcol_.encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "enxcol_.encryptedCollection.ecoc" + create: *ecoc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name # Data collection is created after. - command_started_event: command: - create: "encryptedCollection" - encryptedFields: *encrypted_fields1 + create: *encrypted_collection_name + encryptedFields: *encrypted_fields_expectation command_name: create database_name: *database_name # Index on __safeContents__ is then created. - command_started_event: command: - createIndexes: "encryptedCollection" + createIndexes: *encrypted_collection_name indexes: - name: __safeContent___1 key: { __safeContent__: 1 } @@ -249,104 +226,83 @@ tests: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: &encrypted_fields2 { - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }}, - "queries": { - "queryType": "equality", - "contention": { - "$numberLong": "0" - } - } - } - ] - } + default.encryptedCollection: *encrypted_fields operations: # Do an initial drop to remove collections that may exist from previous test runs. - name: dropCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: createCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.esc" - - name: assertCollectionExists + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" + collection: *ecoc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertIndexExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name index: __safeContent___1 - name: dropCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *ecoc_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" - - name: assertCollectionNotExists - object: testRunner - arguments: - database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertIndexNotExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name index: __safeContent___1 expectations: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end @@ -354,33 +310,27 @@ tests: # State collections are created first. - command_started_event: command: - create: "enxcol_.encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "enxcol_.encryptedCollection.ecoc" + create: *ecoc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name # Data collection is created after. - command_started_event: command: - create: "encryptedCollection" - encryptedFields: *encrypted_fields2 + create: *encrypted_collection_name + encryptedFields: *encrypted_fields command_name: create database_name: *database_name # Index on __safeContents__ is then created. - command_started_event: command: - createIndexes: "encryptedCollection" + createIndexes: *encrypted_collection_name indexes: - name: __safeContent___1 key: { __safeContent__: 1 } @@ -390,171 +340,27 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end - - description: "encryptedFieldsMap with cyclic entries does not loop" - clientOptions: - autoEncryptOpts: - kmsProviders: - aws: {} # Credentials filled in from environment. - encryptedFieldsMap: - # encryptedCollection has encryptedCollection.esc as the escCollection. - # encryptedCollection.esc has encryptedCollection as the escCollection. - default.encryptedCollection: &encrypted_fields3 { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } - default.encryptedCollection.esc: { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } - operations: - # Do an initial drop to remove collections that may exist from previous test runs. - - name: dropCollection - object: database - arguments: - collection: "encryptedCollection" - - name: createCollection - object: database - arguments: - collection: "encryptedCollection" - - name: assertCollectionExists - object: testRunner - arguments: - database: *database_name - collection: "enxcol_.encryptedCollection.esc" - - name: assertCollectionExists - object: testRunner - arguments: - database: *database_name - collection: "enxcol_.encryptedCollection.ecc" - - name: assertCollectionExists - object: testRunner - arguments: - database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" - - name: assertCollectionExists - object: testRunner - arguments: - database: *database_name - collection: "encryptedCollection" - - name: assertIndexExists - object: testRunner - arguments: - database: *database_name - collection: "encryptedCollection" - index: __safeContent___1 - expectations: - # events from dropCollection ... begin - - command_started_event: - command: - drop: "enxcol_.encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "enxcol_.encryptedCollection.ecc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "enxcol_.encryptedCollection.ecoc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection" - command_name: drop - database_name: *database_name - # events from dropCollection ... end - # events from createCollection ... begin - # State collections are created first. - - command_started_event: - command: - create: "enxcol_.encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecoc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - # Data collection is created after. - - command_started_event: - command: - create: "encryptedCollection" - encryptedFields: *encrypted_fields3 - command_name: create - database_name: *database_name - # Index on __safeContents__ is then created. - - command_started_event: - command: - createIndexes: "encryptedCollection" - indexes: - - name: __safeContent___1 - key: { __safeContent__: 1 } - command_name: createIndexes - database_name: *database_name - # events from createCollection ... end - description: "CreateCollection without encryptedFields." clientOptions: autoEncryptOpts: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + default.encryptedCollection: *encrypted_fields operations: # Do an initial drop to remove collections that may exist from previous test runs. - name: dropCollection @@ -597,75 +403,60 @@ tests: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: &encrypted_fields4 { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + default.encryptedCollection: *encrypted_fields operations: # Do an initial drop to remove collections that may exist from previous test runs. - name: dropCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: createCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.esc" - - name: assertCollectionExists + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" + collection: *ecoc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertIndexExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name index: __safeContent___1 expectations: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end @@ -673,33 +464,27 @@ tests: # State collections are created first. - command_started_event: command: - create: "enxcol_.encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "enxcol_.encryptedCollection.ecoc" + create: *ecoc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name # Data collection is created after. - command_started_event: command: - create: "encryptedCollection" - encryptedFields: *encrypted_fields4 + create: *encrypted_collection_name + encryptedFields: *encrypted_fields_expectation command_name: create database_name: *database_name # Index on __safeContents__ is then created. - command_started_event: command: - createIndexes: "encryptedCollection" + createIndexes: *encrypted_collection_name indexes: - name: __safeContent___1 key: { __safeContent__: 1 } @@ -716,82 +501,56 @@ tests: - name: dropCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + collection: *encrypted_collection_name + encryptedFields: *encrypted_fields - name: createCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: &encrypted_fields5 { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + collection: *encrypted_collection_name + encryptedFields: *encrypted_fields - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.esc" - - name: assertCollectionExists + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" + collection: *ecoc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertIndexExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name index: __safeContent___1 expectations: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end @@ -799,40 +558,34 @@ tests: # State collections are created first. - command_started_event: command: - create: "enxcol_.encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "enxcol_.encryptedCollection.ecoc" + create: *ecoc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name # Data collection is created after. - command_started_event: command: - create: "encryptedCollection" - encryptedFields: *encrypted_fields5 + create: *encrypted_collection_name + encryptedFields: *encrypted_fields_expectation command_name: create database_name: *database_name # libmongocrypt requests listCollections to get a schema for the "createIndexes" command. - command_started_event: command: listCollections: 1 - filter: { name: "encryptedCollection" } + filter: { name: *encrypted_collection_name } command_name: listCollections database_name: *database_name # Index on __safeContents__ is then created. - command_started_event: command: - createIndexes: "encryptedCollection" + createIndexes: *encrypted_collection_name indexes: - name: __safeContent___1 key: { __safeContent__: 1 } @@ -846,43 +599,27 @@ tests: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + default.encryptedCollection: *encrypted_fields operations: - name: dropCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name expectations: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end @@ -897,156 +634,111 @@ tests: - name: dropCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + collection: *encrypted_collection_name + encryptedFields: *encrypted_fields - name: createCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + collection: *encrypted_collection_name + encryptedFields: *encrypted_fields - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.esc" - - name: assertCollectionExists + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" + collection: *ecoc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertIndexExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name index: __safeContent___1 - name: dropCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: &encrypted_fields6 { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } - - name: assertCollectionNotExists - object: testRunner - arguments: - database: *database_name - collection: "enxcol_.encryptedCollection.esc" + collection: *encrypted_collection_name + encryptedFields: *encrypted_fields + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" + collection: *ecoc_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name expectations: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end # events from createCollection ... begin - command_started_event: command: - create: "enxcol_.encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "enxcol_.encryptedCollection.ecoc" + create: *ecoc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection" - encryptedFields: *encrypted_fields6 + create: *encrypted_collection_name + encryptedFields: *encrypted_fields_expectation command_name: create database_name: *database_name # libmongocrypt requests listCollections to get a schema for the "createIndexes" command. - command_started_event: command: listCollections: 1 - filter: { name: "encryptedCollection" } + filter: { name: *encrypted_collection_name } command_name: listCollections database_name: *database_name # Index on __safeContents__ is then created. - command_started_event: command: - createIndexes: "encryptedCollection" + createIndexes: *encrypted_collection_name indexes: - name: __safeContent___1 key: { __safeContent__: 1 } @@ -1056,22 +748,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end @@ -1088,145 +775,111 @@ tests: - name: dropCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + collection: *encrypted_collection_name + encryptedFields: *encrypted_fields - name: createCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: &encrypted_fields7 { - "escCollection": "enxcol_.encryptedCollection.esc", - "eccCollection": "enxcol_.encryptedCollection.ecc", - "ecocCollection": "enxcol_.encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } + collection: *encrypted_collection_name + encryptedFields: *encrypted_fields - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.esc" - - name: assertCollectionExists + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" + collection: *ecoc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name - name: assertIndexExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name index: __safeContent___1 - name: dropCollection object: database arguments: - collection: "encryptedCollection" + collection: *encrypted_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.esc" + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecc" + collection: *ecoc_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "enxcol_.encryptedCollection.ecoc" - - name: assertCollectionNotExists - object: testRunner - arguments: - database: *database_name - collection: "encryptedCollection" + collection: *encrypted_collection_name expectations: # events from dropCollection ... begin - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name # events from dropCollection ... end # events from createCollection ... begin - command_started_event: command: - create: "enxcol_.encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "enxcol_.encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "enxcol_.encryptedCollection.ecoc" + create: *ecoc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "encryptedCollection" - encryptedFields: *encrypted_fields7 + create: *encrypted_collection_name + encryptedFields: *encrypted_fields_expectation command_name: create database_name: *database_name # libmongocrypt requests listCollections to get a schema for the "createIndexes" command. - command_started_event: command: listCollections: 1 - filter: { name: "encryptedCollection" } + filter: { name: *encrypted_collection_name } command_name: listCollections database_name: *database_name # Index on __safeContents__ is then created. - command_started_event: command: - createIndexes: "encryptedCollection" + createIndexes: *encrypted_collection_name indexes: - name: __safeContent___1 key: { __safeContent__: 1 } @@ -1237,27 +890,53 @@ tests: - command_started_event: command: listCollections: 1 - filter: { name: "encryptedCollection" } + filter: { name: *encrypted_collection_name } command_name: listCollections database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "enxcol_.encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "enxcol_.encryptedCollection.ecoc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection" + drop: *encrypted_collection_name command_name: drop database_name: *database_name - # events from dropCollection ... end \ No newline at end of file + # events from dropCollection ... end + - description: "encryptedFields are consulted for metadata collection names" + clientOptions: + autoEncryptOpts: + kmsProviders: + aws: {} # Credentials filled in from environment. + encryptedFieldsMap: + default.encryptedCollection: { + "escCollection": "invalid_esc_name", + "ecocCollection": "invalid_ecoc_name", + "fields": [ + { + "path": "firstName", + "bsonType": "string", + "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} + } + ] + } + + operations: + # Do an initial drop to remove collections that may exist from previous test runs. + - name: dropCollection + object: database + arguments: + collection: *encrypted_collection_name + - name: createCollection + object: database + arguments: + collection: *encrypted_collection_name + result: + # Expect error due to server constraints added in SERVER-74069 + errorContains: "Encrypted State Collection name should follow" \ No newline at end of file diff --git a/spec/spec_tests/data/client_side_encryption/fle2-DecryptExistingData.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml similarity index 92% rename from spec/spec_tests/data/client_side_encryption/fle2-DecryptExistingData.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml index d1d0e2384a..8ed6e1d89c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-DecryptExistingData.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml @@ -1,5 +1,8 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Delete.yml similarity index 68% rename from spec/spec_tests/data/client_side_encryption/fle2-Delete.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Delete.yml index 7dfe117681..c9a8aced59 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Delete.yml @@ -1,11 +1,14 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Delete can query an FLE2 indexed field" @@ -60,7 +63,11 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -71,7 +78,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -79,28 +86,15 @@ tests: }, "limit": 1 } + ordered: true encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields - deleteTokens: { - "default.default": { - "encryptedIndexed": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: delete outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml similarity index 81% rename from spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml index fff3e24561..13d1129c8b 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-EncryptedFieldsMap.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml @@ -1,11 +1,14 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}}] tests: - description: "encryptedFieldsMap is preferred over remote encryptedFields" @@ -15,9 +18,6 @@ tests: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: { "default.default": { - "escCollection": "enxcol_.default.esc", - "eccCollection": "enxcol_.default.ecc", - "ecocCollection": "enxcol_.default.ecoc", "fields": [] } } diff --git a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-jsonSchema.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-jsonSchema.yml similarity index 72% rename from spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-jsonSchema.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-jsonSchema.yml index 2ff2e4da1c..3d6e5ce957 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFields-vs-jsonSchema.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-jsonSchema.yml @@ -1,5 +1,8 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" @@ -9,7 +12,7 @@ json_schema: { "properties": {}, "bsonType": "object" } -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "encryptedFields is preferred over jsonSchema" @@ -63,7 +66,12 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert - command_started_event: command: @@ -72,7 +80,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVjACAAAAAAWuidNu47c9A4Clic3DvFhn1AQJVC+FJtoE5bGZuz6PsFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVsACAAAAAApJtKPW4+o9B7gAynNLL26jtlB4+hq5TXResijcYet8USY20AAAAAAAAAAAAA", "subType": "06" } } @@ -81,7 +89,12 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFieldsMap-defaults.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFieldsMap-defaults.yml similarity index 88% rename from spec/spec_tests/data/client_side_encryption/fle2-EncryptedFieldsMap-defaults.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFieldsMap-defaults.yml index 0ee418db05..4d85e304df 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-EncryptedFieldsMap-defaults.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFieldsMap-defaults.yml @@ -1,5 +1,8 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" @@ -42,10 +45,9 @@ tests: }, "schema": { "default.default": { - "fields": [], "escCollection": "enxcol_.default.esc", - "eccCollection": "enxcol_.default.ecc", - "ecocCollection": "enxcol_.default.ecoc" + "ecocCollection": "enxcol_.default.ecoc", + "fields": [] } } } diff --git a/spec/spec_tests/data/client_side_encryption/fle2-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-FindOneAndUpdate.yml similarity index 72% rename from spec/spec_tests/data/client_side_encryption/fle2-FindOneAndUpdate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-FindOneAndUpdate.yml index a5596c2c24..1a2211130a 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-FindOneAndUpdate.yml @@ -1,11 +1,14 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "findOneAndUpdate can query an FLE2 indexed field" @@ -61,7 +64,12 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert - command_started_event: command: @@ -70,7 +78,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -80,24 +88,12 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields - deleteTokens: - "default.default": { - "encryptedIndexed": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify outcome: collection: @@ -162,7 +158,12 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert - command_started_event: @@ -172,7 +173,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -182,25 +183,12 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields - deleteTokens: { - "default.default": { - "encryptedIndexed": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify - command_started_event: command: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Indexed.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Indexed.yml similarity index 72% rename from spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Indexed.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Indexed.yml index 5c5efbcc5e..27520226be 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Indexed.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Indexed.yml @@ -1,11 +1,14 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Insert and find FLE2 indexed field" @@ -59,7 +62,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -68,7 +75,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVjACAAAAAAWuidNu47c9A4Clic3DvFhn1AQJVC+FJtoE5bGZuz6PsFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVsACAAAAAApJtKPW4+o9B7gAynNLL26jtlB4+hq5TXResijcYet8USY20AAAAAAAAAAAAA", "subType": "06" } } @@ -77,7 +84,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Unindexed.yml similarity index 86% rename from spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Unindexed.yml index 71f1993e56..aeff4a88fc 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-InsertFind-Unindexed.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Unindexed.yml @@ -1,11 +1,14 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Insert and find FLE2 unindexed field" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-MissingKey.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml similarity index 72% rename from spec/spec_tests/data/client_side_encryption/fle2-MissingKey.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml index 937c82ee9e..0ff40dbe8c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-MissingKey.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml @@ -1,5 +1,8 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" @@ -14,7 +17,7 @@ data: [ } } ] -encrypted_fields: {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [] tests: - description: "FLE2 encrypt fails with mising key" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-NoEncryption.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml similarity index 85% rename from spec/spec_tests/data/client_side_encryption/fle2-NoEncryption.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml index 8725ac1ef7..66fb5e2625 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-NoEncryption.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml @@ -1,5 +1,8 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Aggregate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml index 4d17344dc5..b03e4d9a25 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Date. Aggregate." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedDate": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -97,7 +108,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: aggregate outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml similarity index 96% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Correctness.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml index 65382eec6b..6e5f80265c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml @@ -1,14 +1,17 @@ # Test correctness results. # Does not include command monitoring expectations or outcome assertions to make tests more readable. -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml similarity index 70% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Delete.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml index 6bb2535bb4..e15d19d843 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Date. Delete." @@ -64,7 +67,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -75,7 +82,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -86,7 +97,7 @@ tests: "encryptedDate": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -95,28 +106,15 @@ tests: "limit": 1 } ] + ordered: true encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDate": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: delete outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Date-FindOneAndUpdate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml index d220bf8ddf..5f25f9756b 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Date. FindOneAndUpdate." @@ -65,7 +68,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -76,7 +83,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedDate": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -95,25 +106,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDate": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: findAndModify outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml similarity index 75% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Date-InsertFind.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml index 50a06cedaf..d9f9a10d51 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Date. Insert and Find." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -83,7 +94,7 @@ tests: "encryptedDate": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -91,7 +102,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml similarity index 65% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Update.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml index ba327caad7..097d54c184 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Date-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Date. Update." @@ -67,7 +70,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -78,59 +85,45 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command_name: update command: - { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", - "subType": "06" - } + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", + "subType": "06" } } - }, - "u": { - "$set": { - "encryptedDate": { $$type: "binData" } - } } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": *encrypted_fields }, - "deleteTokens": { - "default.default": { - "encryptedDate": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } + "u": { + "$set": { + "encryptedDate": { $$type: "binData" } } } - }, - "$db": "default" - } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml similarity index 75% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Aggregate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml index 20c9cfd51e..807a11376c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml @@ -1,13 +1,16 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. topology: [ "replicaset" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. Aggregate." @@ -64,7 +67,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -75,7 +82,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -86,7 +97,7 @@ tests: "encryptedDecimal": { "$gt": { "$binary": { - "base64": "CgljAAADcGF5bG9hZADZYgAABGcAnWIAAAMwAH0AAAAFZAAgAAAAAJu2KgiI8vM+kz9qD3ZQzFQY5qbgYqCqHG5R4jAlnlwXBXMAIAAAAAAAUXxFXsz764T79sGCdhxvNd5b6E/9p61FonsHyEIhogVjACAAAAAAeEtIU/sp7zwjA/VArYkCzkUUiRiQOmlTaVZvfUqbEp0AAzEAfQAAAAVkACAAAAAAPQPvL0ARjujSv2Rkm8r7spVsgeC1K3FWcskGGZ3OdDIFcwAgAAAAACgNn660GmefR8jLqzgR1u5O+Uocx9GyEHiBqVGko5FZBWMAIAAAAACMKx1UyNAN4yVafFht8jp4w4LNaJcLhnozfSMzDHD3owADMgB9AAAABWQAIAAAAAD2Zi6kcxmaD2mY3VWrP+wYJMPg6cSBIYPapxaFQxYFdQVzACAAAAAAM/cV36BLBY3xFBXsXJY8M9EHHOc/qrmdc2CJmj3M89gFYwAgAAAAAHdek2wlAQ5BQORVjudyziRTV0EKx8qbsnoDiw4HG2xaAAMzAH0AAAAFZAAgAAAAAOaNqI9srQ/mI9gwbk+VkizGBBH/PPWOVusgnfPk3tY1BXMAIAAAAAAc96O/pwKCmHCagT6T/QV/wz4vqO+R22GsZ1dse2Vg6QVjACAAAAAA3tld+twIlVWnCTXElspau5k02CwxXDjh+3u1CQtV/W4AAzQAfQAAAAVkACAAAAAA0XlQgy/Yu97EQOjronl9b3dcR1DFn3deuVhtTLbJZHkFcwAgAAAAACoMnpVl6EFJak8A+t5N4RFnQhkQEBnNAx8wDqmq5U/dBWMAIAAAAABRmXIchytMNz4J439viiY5FZ1OB3AXJBkZ3udUqHpsqAADNQB9AAAABWQAIAAAAAAOtpNexRxfv0yRFvZO9DhlkpU4mDuAb8ykdLnE5Vf1VAVzACAAAAAAeblFKm/30orP16uQpZslvsoS8s0xfNPIBlw3VkHeekYFYwAgAAAAAJQdzGMO2s1AkdKz3ZPytivrQphUFVhvhMcXjvUGzHBDAAM2AH0AAAAFZAAgAAAAAIr8xAFm1zPmrvW4Vy5Ct0W8FxMmyPmFzdWVzesBhAJFBXMAIAAAAABYeeXjJEzTHwxab6pUiCRiZjxgtN59a1y8Szy3hfkg+gVjACAAAAAA161loizzKvXS1Po/3bxeNICmKpsPSK9Q+EpkISK3jVoAAzcAfQAAAAVkACAAAAAAl+ibLk0/+EwoqeC8S8cGgAtjtpQWGEZDsybMPnrrkwEFcwAgAAAAAHPPBudWgQ+HUorLDpJMqhS9VBF2VF5aLcxgrM1s+yU7BWMAIAAAAAACyakB3CZWEjbxK0u9Sflc3+EafBAQFbvBpCxKvxuEQAADOAB9AAAABWQAIAAAAACR9erwLTb+tcWFZgJ2MEfM0PKI9uuwIjDTHADRFgD+SQVzACAAAAAAcOop8TXsGUVQoKhzUllMYWxL93xCOkwtIpV8Q6hiSYYFYwAgAAAAAJ43998lzKoVqWm99ZzKHJLeVscGNCVoKDvpUtt2rDI9AAM5AH0AAAAFZAAgAAAAALv0vCPgh7QpmM8Ug6ad5ioZJCh7pLMdT8FYyQioBQ6KBXMAIAAAAADsCPyIG8t6ApQkRk1fX/sfc1kpuWCWP8gAEpnYoBSHrQVjACAAAAAAj2h1WLr0EFEFZ31djx3BtLIbAtdE05ax54opkJo4/bQAAzEwAH0AAAAFZAAgAAAAAIW4AxJgYoM0pcNTwk1RSbyjZGIqgKL1hcTJmNrnZmoPBXMAIAAAAAAZpfx3EFO0vY0f1eHnE0PazgqeNDTaj+pPJMUNW8lFrAVjACAAAAAAqFD83E8POmo6pdqg7D+jWtngbgcV99mbQBxkbpX7ds4AAzExAH0AAAAFZAAgAAAAAKliO6L9zgeuufjj174hvmQGNRbmYYs9yAirL7OxwEW3BXMAIAAAAAAqU7vs3DWUQ95Eq8OejwWnD0GuXd+ASi/uD6S0l8MM1QVjACAAAAAAp0cwpAh7SiuStPwYNVvp83N5GWyWRWA7UGRxHDDj8g8AAzEyAH0AAAAFZAAgAAAAAOGQcCBkk+j/Kzjt/Cs6g3BZPJG81wIHBS8JewHGpgk+BXMAIAAAAABjrxZXWCkdzrExwCgyHaafuPSQ4V4x2k9kUCAqUaYKDQVjACAAAAAAJr/tEuxScrJJEMECy4itHf1y5+gO2l5sBjgM/EOLXw0AAzEzAH0AAAAFZAAgAAAAAPmCNEt4t97waOSd5hNi2fNCdWEkmcFJ37LI9k4Az4/5BXMAIAAAAABX7DuDPNg+duvELf3NbLWkPMFw2HGLgWGHyVWcPvSNCAVjACAAAAAAeNCEn5Z3A5CgCnIgaBl2N8p0t+5rRY06vGg6ePVMaTUAAzE0AH0AAAAFZAAgAAAAAD4vtVUYRNB+FD9yoQ2FVJH3nMeJeKbi6eZfth638YqbBXMAIAAAAAANCuUB4OdmuD6LaDK2f3vaqfgYYvg40wDXOBbcFjTqLwVjACAAAAAATozHsao4er/Uk5H/pKe/1JEo/3h4Pgah2RgJHXf2PeYAAzE1AH0AAAAFZAAgAAAAAJPIRzjmTjbdIvshG6UslbEOd797ZSIdjGAhGWxVQvK1BXMAIAAAAABgmJ0Jh8WLs9IYs/a7DBjDWd8J3thW/AGJK7zDnMeYOAVjACAAAAAAtD48LbnCkTvacfz8DvR1ixsbYceiJxJRV7tLqDUWvVwAAzE2AH0AAAAFZAAgAAAAAABQyKQPoW8wGPIqnsTv69+DzIdRkohRhOhDmyVHkw9WBXMAIAAAAAAqWA2X4tB/h3O1Xlawtz6ndI6WaTwgU1QYflL35opu5gVjACAAAAAAphx2eRFGSW/tm3HxilqRv5Zj5TtOy4KSGiCIm6yJANYAAzE3AH0AAAAFZAAgAAAAACB7NOyGQ1Id3MYnxtBXqyZ5Ul/lHH6p1b10U63DfT6bBXMAIAAAAADpOryIcndxztkHSfLN3Kzq29sD8djS0PspDSqERMqokQVjACAAAAAAIHYCljTfUu/Ox23Nrjz2Z6cqQpn4s0sJV/SxkC9UtIEAAzE4AH0AAAAFZAAgAAAAAKVfXLfs8XA14CRTB56oZwV+bFJN5BHraTXbqEXZDmTkBXMAIAAAAAASRWTsfGOpqdffiOodoqIgBzG/yzFyjR5CfUsIUIWGpgVjACAAAAAAGs7l0GsFNkQMcJBUuUsEOMuQupbEvi7MDcezspsCY3EAAzE5AH0AAAAFZAAgAAAAAH/aZr4EuS0/noQR9rcF8vwoaxnxrwgOsSJ0ys8PkHhGBXMAIAAAAACd7ObGQW7qfddcvyxRTkPuvq/PHu7+6I5dxwS1Lzy5XAVjACAAAAAAWnfsbdu5Qvb62rEpBR90qh+3jdYfp266dmuNPLoXys4AAzIwAH0AAAAFZAAgAAAAAKvlcpFFNq0oA+urq3w6d80PK1HHHw0H0yVWvU9aHijXBXMAIAAAAADWnAHQ5Fhlcjawki7kWzdqjM2f6IdGJblojrYElWjsZgVjACAAAAAAdNxswYIMjqVta8sLC8wniMm6gOgIgG5eWTPAxItBJJ4AAzIxAH0AAAAFZAAgAAAAAH/2aMezEOddrq+dNOkDrdqf13h2ttOnexZsJxG1G6PNBXMAIAAAAABNtgnibjC4VKy5poYjvdsBBnVvDTF/4mmEAxsXVgZVKgVjACAAAAAArWXr2Myg8T8/0eYBCUXDOsIR//zqZtlE6Lb1YZJkvYcAAzIyAH0AAAAFZAAgAAAAAF2wZoDL6/V59QqO8vdRZWDpXpkV4h4KOCSn5e7x7nmzBXMAIAAAAADLZBu7LCYjbThaVUqMK14H/elrVOYIKJQCx4C9Yjw37gVjACAAAAAAWeF6F8pBeCm27PWjQGrlyN7JHp9WqCNnwFzqZ+V51t0AAzIzAH0AAAAFZAAgAAAAAH27yYaLn9zh2CpvaoomUPercSfJRUmBY6XFqmhcXi9QBXMAIAAAAAAUwumVlIYIs9JhDhSj0R0+59psCMsFk94E62VxkPt42QVjACAAAAAAjbsmlVrfexmULer3RtGwnOf20pF0xv4Z46OHOaIwgzEAAzI0AH0AAAAFZAAgAAAAALMg2kNAO4AFFs/mW3In04yFeN4AP6Vo0klyUoT06RquBXMAIAAAAAAgGWJbeIdwlpqXCyVIYSs0dt54Rfc8JF4b8uYc+YUj0AVjACAAAAAAsNKKan8cMW1oYFmQiyPZbepuJlDD0/CQUS21++CP16kAAzI1AH0AAAAFZAAgAAAAALas/i1T2DFCEmrrLEi7O2ngJZyFHialOoedVXS+OjenBXMAIAAAAAA1kK0QxY4REcGxHeMkgumyF7iwlsRFtw9MlbSSoQY7uAVjACAAAAAAUGZ/BwF6Q9hcFBC8F5ed6RwYWJ1/OahRwljBloMgkFAAAzI2AH0AAAAFZAAgAAAAAP1TejmWg1CEuNSMt6NUgeQ5lT+oBoeyF7d2l5xQrbXWBXMAIAAAAABPX0kj6obggdJShmqtVfueKHplH4ZrXusiwrRDHMOKeQVjACAAAAAApU9zcPAQ2xOspmwhjWN90v2m2clQ4rk7WBmm/nUyOHQAAzI3AH0AAAAFZAAgAAAAAMrKn+QPa/NxYezNhlOX9nyEkN1kE/gW7EuZkVqYl0b8BXMAIAAAAABUoZMSPUywRGfX2EEencJEKH5x/P9ySUVrhStAwgR/LgVjACAAAAAAl/hDhauhBkda/I/kLr/XLxyso2guawC2isj+tDQhfMoAAzI4AH0AAAAFZAAgAAAAAMmD1+a+oVbiUZd1HuZqdgtdVsVKwuWAn3/M1B6QGBM3BXMAIAAAAACLyytOYuZ9WEsIrrtJbXUx4QgipbaAbmlJvSZVkGi0CAVjACAAAAAAVKbMdxvkfCBoqJCUVjLrtKF90a80LocckdFc7LvYuWIAAzI5AH0AAAAFZAAgAAAAAOVKV7IuFwmYP1qVv8h0NvJmfPICu8yQhzjG7oJdTLDoBXMAIAAAAABL70XLfQLKRsw1deJ2MUvxSWKxpF/Ez73jqtbLvqbuogVjACAAAAAARiC2ShVCKMjjiqB6xg+jDIYJNhhWqmceO07EC8rEXkoAAzMwAH0AAAAFZAAgAAAAAKS/1RSAQma+xV9rz04IcdzmavtrBDjOKPM+Z2NEyYfPBXMAIAAAAAAOJDWGORDgfRv8+w5nunh41wXb2hCA0MRzwnLnQtIqPgVjACAAAAAAWU2Vw2Rta8oHxPPfOWQa86tjYWndb0E3RNlZ9gthHt0AAzMxAH0AAAAFZAAgAAAAAFeq8o82uNY1X8cH6OhdTzHNBUnCChsEDs5tm0kPBz3qBXMAIAAAAABaxMBbsaeEj/EDtr8nZfrhhhirBRPJwVamDo5WwbgvTQVjACAAAAAAqcsCyJuLvxG8STrUI1Ccaga760+4fbmIPc6bXodea0gAAzMyAH0AAAAFZAAgAAAAAI8IKIfDrohHh2cjspJHCovqroSr5N3QyVtNzFvT5+FzBXMAIAAAAABXHXteKG0DoOMmECKp6ro1MZNQvXGzqTDdZ0DUc8QfFAVjACAAAAAA47u7VuqtYJ4jt5CcGrUacopqApwpqEpIpkalvCRtul0AAzMzAH0AAAAFZAAgAAAAAJkHvm15kIu1OtAiaByj5ieWqzxiu/epK6c/9+KYIrB0BXMAIAAAAACzg5TcyANk0nes/wCJudd1BwlkWWF6zw3nGclq5v3SJQVjACAAAAAAvTQRtEYdIj2+bfJNamvrgWRoBsYYjMQx9MyuFwSfKmYAAzM0AH0AAAAFZAAgAAAAAAYSOvEWWuSg1Aym7EssNLR+xsY7e9BcwsX4JKlnSHJcBXMAIAAAAABT48eY3PXVDOjw7JpNjOe1j2JyI3LjDnQoqZ8Je5B2KgVjACAAAAAA4eLwHqiSgM1pMqjK3QFgYauOnI3emnlOK3rgQPuec4AAAzM1AH0AAAAFZAAgAAAAAGQxC9L1e9DfO5XZvX1yvc3hTLtQEdKO9FPMkyg0Y9ZABXMAIAAAAADtmcMNJwdWLxQEArMGZQyzpnu+Z5yMmPAkvgq4eAKwNQVjACAAAAAAxA7XR9Cp6671TiZesvqE/EPKVPXfGDaYuDnce+MaZw0AAzM2AH0AAAAFZAAgAAAAADLHK2LNCNRO0pv8n4fAsxwtUqCNnVK8rRgNiQfXpHSdBXMAIAAAAACf16EBIHRKD3SzjRW+LMOl+47QXA3CJhMzlcqyFRW22AVjACAAAAAA9F9Ztm39rTapXoeRmzk6eUoa3MDPatwoQD5PvD+3xGEAAzM3AH0AAAAFZAAgAAAAAHiZJiXKNF8bbukQGsdYkEi95I+FSBHy1I5/hK2uEZruBXMAIAAAAADE+lZBa8HDUJPN+bF6xI9x4N7GF9pj3vBR7y0BcfFhBAVjACAAAAAArsw2CrSC2y02EiWJxViVxlYRIXtWARpUTwO/mfqS7O4AAzM4AH0AAAAFZAAgAAAAAI1oa2OIw5TvhT14tYCGmhanUoYcCZtNbrVbeoMldHNZBXMAIAAAAAAx2nS0Ipblf2XOgBiUOuJFBupBhe7nb6QPLZlA4aMPCgVjACAAAAAAaVoGxV5YH4urmcw3Zocg+QHoq+hA0u6nIkconI0MtZoAAzM5AH0AAAAFZAAgAAAAABgTWS3Yap7Q59hii/uPPimHWXsr+DUmsqfwt/X73qsOBXMAIAAAAACKK05liW5KrmEAvtpCB1WUltruzUylDDpjea//UlWoOAVjACAAAAAAAI3iOqWSA+DpgNr/neIRpt85Q8pLS/81ZKDnbC4io/0AAzQwAH0AAAAFZAAgAAAAABOAnBPXDp6i9TISQXvcNKwGDLepZTu3cKrB4vKnSCjBBXMAIAAAAADjjzZO7UowAAvpwyG8BNOVqLCccMFk3aDK4unUeft5ywVjACAAAAAAIZqKrUcbtze/K3CUKdchuVXQYkgj6jQfIeMZ3GrtYeEAAzQxAH0AAAAFZAAgAAAAAHN8hyvT1lYrAsdiV5GBdd5jhtrAYE/KnSjw2Ka9hjz9BXMAIAAAAAD794JK7EeXBs+D7yOVK7nWF8SbZ/7U8gZ7nnT9JFNwTAVjACAAAAAAQ4K5cDGLM/2L7KbMt537tsG1eiWrboKMj6pXVfBDIBoAAzQyAH0AAAAFZAAgAAAAAO93bPrq8bsnp1AtNd9ETnXIz0lH/2HYN/vuw9wA3fyFBXMAIAAAAABHlls5fbaF2oAGqptC481XQ4eYxInTC29aElfmVZgDUgVjACAAAAAAe0oLBk+ZaipcPgjjHvACAyA42cLY8i+s+V0EsarulZkAAzQzAH0AAAAFZAAgAAAAAL1YsAZm1SA0ztU6ySIrQgCCA74V6rr0/4iIygCcaJL6BXMAIAAAAADTXWTHWovGmUR1Zg9l/Aqq9H5mOCJQQrb/Dfae7e3wKAVjACAAAAAAAeXpcv0oCp9iY/U81w+WJjFjGn60KLQEURJd/pSoaJgAAzQ0AH0AAAAFZAAgAAAAAEoFAeHk0RZ9kD+cJRD3j7PcE5gzWKnyBrF1I/MDNp5mBXMAIAAAAACgHtc2hMBRSZjKw8RAdDHK+Pi1HeyjiBuAslGVNcW5tAVjACAAAAAAmSJiRfjmuwq5cYfadeYQRkPqbGYYrMNTQeyhBEAYClEAAzQ1AH0AAAAFZAAgAAAAAAW+7DmSN/LX+/0uBVJDHIc2dhxAGz4+ehyyz8fAnNGoBXMAIAAAAAA6Ilw42EvvfLJ3Eq8Afd+FjPoPcQutZO6ltmCLEr8kxQVjACAAAAAAUHZ9EwBIvDI4PUeCOzZCuVN9g9h5Zvq2mSpmejPLBE8AAzQ2AH0AAAAFZAAgAAAAANBdV7M7kuYO3EMoQItAbXv4t2cIhfaT9V6+s4cg9djlBXMAIAAAAABvz4MIvZWxxrcJCL5qxLfFhXiUYB1OLHdKEjco94SgDgVjACAAAAAAvz1koQRh+weAUstLWz/iHxFrLReN4KtQjdEU/zrKOFsAAzQ3AH0AAAAFZAAgAAAAAMoAoiAn1kc79j5oPZtlMWHMhhgwNhLUnvqkqIFvcH1NBXMAIAAAAADcJTW7WiCyW0Z9YDUYwppXhLj4Ac1povpJvcAq+i48MQVjACAAAAAARL0k3Uwe7OUwTejHMoNwK+twNHQznmo1vVUZaH7h8BIAAzQ4AH0AAAAFZAAgAAAAACI3j5QP7dWHpcT6WO/OhsWwRJNASBYqIBDNzW8IorEyBXMAIAAAAABxUpBSjXwCKDdGP9hYU+RvyR+96kChfvyyRC4jZmztqAVjACAAAAAA93/qZqyku1BJePH5hmMWsMfuMlF7TOFZp7z2wLJNdeYAAzQ5AH0AAAAFZAAgAAAAAKsbycEuQSeNrF8Qnxqw3x3og8JmQabwGqnDbqzFRVrrBXMAIAAAAACno/3ef2JZJS93SVVzmOZSN+jjJHT8s0XYq2M46d2sLAVjACAAAAAARUbN9bbz8Balyw8pmBLg3L5dy3aQ9s8IgqpVLeQzTkUAAzUwAH0AAAAFZAAgAAAAAPXIcoO8TiULqlxzb74NFg+I8kWX5uXIDUPnh2DobIoMBXMAIAAAAADR6/drkdTpnr9g1XNvKDwtBRBdKn7c2c4ZNUVK5CThdQVjACAAAAAAnJXYvnjljaE6ajRF+h90FXzJ9EJY26D0oG9t61ZMzqgAAzUxAH0AAAAFZAAgAAAAAEa03ZOJmfHT6/nVadvIw71jVxEuIloyvxXraYEW7u7pBXMAIAAAAADzRlBJK75FLiKjz3djqcgjCLo/e3yntI3MnPS48OORhgVjACAAAAAAbFS6SmTSDF3AsPceQrRwMMRoAnzL/k5avSHlIji5zYIAAzUyAH0AAAAFZAAgAAAAAKx8NLSZUU04pSSGmHa5fh2oLHsEN5mmNMNHL95/tuC9BXMAIAAAAAA59hcXVaN3MNdHoo11OcH1aPRzHCwpVjO9mGfMz4xh3QVjACAAAAAAoIvo/iOb/eXJv7w4KSQo74DOyO4uAIO09Ba7JZw9ousAAzUzAH0AAAAFZAAgAAAAAHNKAUxUqBFNS9Ea9NgCZoXMWgwhP4x0/OvoaPRWMquXBXMAIAAAAABUZ551mnP4ZjX+PXU9ttomzuOpo427MVynpkyq+nsYCQVjACAAAAAAlCTphL00SYotztwI+txbHAFTusjh1nNbiya24rM1dUEAAzU0AH0AAAAFZAAgAAAAALfGXDlyDVcGaqtyHkLT0qpuRhJQLgCxtznazhFtuyn/BXMAIAAAAABipxlXDq14C62pXhwAeen5+syA+/C6bN4rtZYcO4zKwAVjACAAAAAA431CBTW4s3IQde024W2cz5iEnj8EJ7p07esepoVDxmQAAzU1AH0AAAAFZAAgAAAAANoEr8sheJjg4UCfBkuUzarU9NFoy1xwbXjs5ifVDeA9BXMAIAAAAABPoyTf6M+xeZVGES4aNzVlq7LgjqZXJ/QunjYVusGUEAVjACAAAAAAywuLdjJJuXewOXMAr25GMBEif8+P74+pGaENUn+XIB8AAzU2AH0AAAAFZAAgAAAAAKvDiK+xjlBe1uQ3SZTNQl2lClIIvpP/5CHwY6Kb3WlgBXMAIAAAAAANnxImq5MFbWaRBHdJp+yD09bVlcFtiFDYsy1eDZj+iQVjACAAAAAAvwntX1RgeNfp5oiD+r3sAKIbMGy1yEblODDYWec1xuIAAzU3AH0AAAAFZAAgAAAAAF49IlFH9RmSUSvUQpEPUedEksrQUcjsOv44nMkwXhjzBXMAIAAAAADJtWGbk0bZzmk20obz+mNsp86UCu/nLLlbg7ppxYn7PgVjACAAAAAAKW8QTERGXwh6fE4gJsULcmu80B4BMkMaZR9USn/C6RgAAzU4AH0AAAAFZAAgAAAAAOuSJyuvz50lp3BzXlFKnq62QkN2quNU1Gq1IDsnFoJCBXMAIAAAAAAqavH1d93XV3IzshWlMnzznucadBF0ND092/2ApI1AcAVjACAAAAAAmVcLt9hf9/q+2O3K4cvFAsvnCsSofHZ5iTzIaR5YKFIAAzU5AH0AAAAFZAAgAAAAALtgC4Whb4ZdkCiI30zY6fwlsxSa7lEaOAU3SfUXr02XBXMAIAAAAACgdZ6U1ZVgUaZZwbIaCdlANpCw6TZV0bwg3DS1NC/mnAVjACAAAAAAYJOnAwVW0gPl1jkCxAL1ZAhKkJ1+ShfwyaIaipflCegAAzYwAH0AAAAFZAAgAAAAAF6PfplcGp6vek1ThwenMHVkbZgrc/dHgdsgx1VdPqZ5BXMAIAAAAACha3qhWkqmuwJSEXPozDO8y1ZdRLyzt9Crt2vjGnT7AAVjACAAAAAA1JYhGDDvtf8b2Pup5jdzQmy325tGo27n/5thjZ/GI4sAAzYxAH0AAAAFZAAgAAAAAKoLEhLvLjKc7lhOJfx+VrGJCx9tXlOSa9bxQzGR6rfbBXMAIAAAAAAIDK5wNnjRMBzET7x/KAMExL/zi1IumJM92XTgXfoPoAVjACAAAAAAhoVLxo0hXbxAzz2BWbeRxAzk08TbVuHxJkdsbCISswkAAzYyAH0AAAAFZAAgAAAAADoQv6lutRmh5scQFvIW6K5JBquLxszuygM1tzBiGknIBXMAIAAAAADAD+JjW7FoBQ76/rsECmmcL76bmyfXpUU/awqIsZdO+wVjACAAAAAAwLIixR/svwADNhtiOZ9mDF2rUqEvuPx2tUSQbhOpersAAzYzAH0AAAAFZAAgAAAAAJNHUGAgn56KekghO19d11nai3lAh0JAlWfeP+6w4lJBBXMAIAAAAAD9XGJlvz59msJvA6St9fKW9CG4JoHV61rlWWnkdBRLzwVjACAAAAAAKMVXumaH5QOt5WVHFip9xWx9e5I/5Y9uEO0UAX6dy7QAAzY0AH0AAAAFZAAgAAAAAHgYoMGjEE6fAlAhICv0+doHcVX8CmMVxyq7+jlyGrvmBXMAIAAAAAC/5MQZgTHuIr/O5Z3mXPvqrom5JTQ8IeSpQGhO9sB+8gVjACAAAAAAPMgD8Rqnd94atKnMyPjlTwthUQ710MKJVqgtwNXLFWwAAzY1AH0AAAAFZAAgAAAAANpIljbxHOM7pydY877gpRQvYY2TGK7igqgGsavqGPBABXMAIAAAAAAqHyEu9gpurPOulApPnr0x9wrygY/7mXe9rAC+tPK80wVjACAAAAAAezZExYLAbjyMsVuSrWGQ+LsXYEcp6AFMANkQ496mTXcAAzY2AH0AAAAFZAAgAAAAAGR+gEaZTeGNgG9BuM1bX2R9ed4FCxBA9F9QvdQDAjZwBXMAIAAAAABSkrYFQ6pf8MZ1flgmeIRkxaSh/Eep4Btdx4QYnGGnwAVjACAAAAAAQ011358xXtVDOyGPpF2+zGoMdiQ8PGCB1QfDcluYUF4AAzY3AH0AAAAFZAAgAAAAAFNprhQ3ZwIcYbuzLolAT5n/vc14P9kUUQComDu6eFyKBXMAIAAAAAAcx9z9pk32YbPV/sfPZl9ALIEVsqoLXgqWLVK/tP+heAVjACAAAAAA3eqduo9CWE5BePqBq7KCh5++QqXnyjCwybB15Zoeiu4AAzY4AH0AAAAFZAAgAAAAADgyPqQdqQrgfmJjRFAILTHzXbdw5kpKyfeoEcy6YYG/BXMAIAAAAAAE+3XsBQ8VAxAkN81au+f3FDeCD/s7KoZD+fnM1MJSSAVjACAAAAAAWHAiQNo6HBkIhf72fVQxJLaCtNeTqn2OuMmYZjT7PRkAAzY5AH0AAAAFZAAgAAAAAI0CT7JNngTCTUSei1Arw7eHWCD0jumv2rb7imjWIlWABXMAIAAAAABSP8t6ya0SyCphXMwnru6ZUDXWElN0NfBvEOhDvW9bJQVjACAAAAAAbfZdoqMQ71hNIsycNHl6JpSmAFDPSfgzdWkGqFZNoScAAzcwAH0AAAAFZAAgAAAAAD/FIrGYFDjyYmVb7oTMVwweWP7A6F9LnyIuNO4MjBnXBXMAIAAAAACIZgJCQRZu7NhuNMyOqCn1tf+DfU1qm10TPCfj5JYV3wVjACAAAAAAZCaO06/DjOBjNrTxB8jdxM7X4XvNu9QTa2Y00kZJwgEAAzcxAH0AAAAFZAAgAAAAAHIkVuNDkSS1cHIThKc/O0r2/ubaABTOi8Q1r/dvBAsEBXMAIAAAAADdHYqchEiJLM340c3Q4vJABmmth3+MKzwLYlsG6GS7sQVjACAAAAAA4byh4Re83zUZxvEH4iJSsnFei+ohsWxjSpB5YoAPawIAAzcyAH0AAAAFZAAgAAAAAJmoDILNhC6kn3masElfnjIjP1VjsjRavGk1gSUIjh1NBXMAIAAAAAD97Ilvp3XF8T6MmVVcxMPcdL80RgQ09UoC6PnoOvZ1IQVjACAAAAAAx9wkqPvtzrL9EFDmFvktI129ti8IAdHn0oudubGPWAAAAzczAH0AAAAFZAAgAAAAAI5bm3YO0Xgf0VT+qjVTTfvckecM3Cwqj7DTKZXf8/NXBXMAIAAAAAD/m+h8fBhWaHm6Ykuz0WX1xL4Eme3ErLObyEVJf8NCywVjACAAAAAAiGdTXD22l1zDxHeF4NXUTiBnNTsdpzJRwM6riTPuOogAAzc0AH0AAAAFZAAgAAAAANqo4+p6qdtCzcB4BX1wQ6llU7eFBnuu4MtZwp4B6mDlBXMAIAAAAAAGiz+VaukMZ+6IH4jtn4KWWdKK4/W+O+gRioQDrfzpMgVjACAAAAAAl/wlBjSJW/hKkll8HSBCGw4Ce1pJ5rZuhVE09hKq4jQAAzc1AH0AAAAFZAAgAAAAAPrFXmHP2Y4YAm7b/aqsdn/DPoDkv7B8egWkfe23XsM1BXMAIAAAAAAGhwpKAr7skeqHm3oseSbO7qKNhmYsuUrECBxJ5k+D2AVjACAAAAAAKSsxERy2OBhb5MiH9/H2BET2oeU6yVihiAoWi/16FroAAzc2AH0AAAAFZAAgAAAAABzjYxwAjXxXc0Uxv18rH8I3my0Aguow0kTwKyxbrm+cBXMAIAAAAADVbqJVr6IdokuhXkEtXF0C2gINLiAjMVN20lE20Vmp2QVjACAAAAAACJ+VAwl9X88mjC76yzkWeL4K6AamNYjbNkpS9B6fQE4AAzc3AH0AAAAFZAAgAAAAAFMm2feF2fFCm/UC6AfIyepX/xJDSmnnolQIBnHcPmb5BXMAIAAAAABLI11kFrQoaNVZFmq/38aRNImPOjdJh0Lo6irI8M/AaAVjACAAAAAAZFgYh4nV8YN+/AAqe/QhKDkNBPg8KraCG7y3bOzhPV4AAzc4AH0AAAAFZAAgAAAAAJvXB3KyNiNtQko4SSzo/9b2qmM2zU9CQTTDfLSBWMgRBXMAIAAAAAAvjuVP7KsLRDeqVqRziTKpBrjVyqKiIbO9Gw8Wl2wFTAVjACAAAAAARuBn3JdQ8so7Gy0XVH0CtlvtRoJWhSrJP6eRIqbyWh8AAzc5AH0AAAAFZAAgAAAAAPGdcxDiid8z8XYnfdDivNMYVPgBKdGOUw6UStU+48CdBXMAIAAAAAARj6g1Ap0eEfuCZ4X2TsEw+Djrhto3fA5nLwPaY0vCTgVjACAAAAAATxLY6SkcLPaoOBJpQsggEqoxgJgiY9seP3fBQM05PckAAzgwAH0AAAAFZAAgAAAAAP5rGPrYGt3aKob5f/ldP0qrW7bmWvqnKY4QwdDWz400BXMAIAAAAADTQkW2ymaaf/bhteOOGmSrIR97bAnJx+yN3yMj1bTeewVjACAAAAAA6O+wIMt3xMITuCxVrqOCNBPX5F122G+/Is+EYkzUvVYAAzgxAH0AAAAFZAAgAAAAAAlz6wJze5UkIxKpJOZFGCOf3v2KByWyI6NB6JM9wNcBBXMAIAAAAABUC7P/neUIHHoZtq0jFVBHY75tSFYr1Y5S16YN5XxC1QVjACAAAAAANv97jMd9JmZ7lMeCzK7jaZHZYsZJNl6N9g9WXzd2om0AAzgyAH0AAAAFZAAgAAAAAFJ8AtHcjia/9Y5pLEc3qVgH5xKiXw12G9Kn2A1EY8McBXMAIAAAAAAxe7Bdw7eUSBk/oAawa7uicTEDgXLymRNhBy1LAxhDvwVjACAAAAAAtOUZ2NeUWwESpq95O92dhqYBt8RtFnjT43E7k9mQF3oAAzgzAH0AAAAFZAAgAAAAAO8uwQUaKFb6vqR3Sv3Wn4QAonC2exOC9lGG1juqP5DtBXMAIAAAAABZf1KyJgQg8/Rf5c02DgDK2aQu0rNCOvaL60ohDHyY+gVjACAAAAAA5nAWj1Ek8p0MLYZEB3yoVFDfBYZ+/ZpIo71u+W9hprcAAzg0AH0AAAAFZAAgAAAAAE8YtqyRsGCeiR6hhiyisR/hccmK4nZqIMzO4lUBmEFzBXMAIAAAAAC1UYOSKqAeG1UJiKjWFVskRhuFKpj9Ezy+lICZvFlN5AVjACAAAAAA37oP30mTzVftI2FL9Uwyls/jqLqbmRDQk7p7nlx44uYAAzg1AH0AAAAFZAAgAAAAAPhCrMausDx1QUIEqp9rUdRKyM6a9AAx7jQ3ILIu8wNIBXMAIAAAAACmH8lotGCiF2q9VQxhsS+7LAZv79VUAsOUALaGxE/EpAVjACAAAAAAhVIkl8p19VZ0gpg32s++Jda3qsVSVB5tV4CKrtjhE3IAAzg2AH0AAAAFZAAgAAAAAOBi/GAYFcstMSJPgp3VkMiuuUUCrZytvqYaU8dwm8v2BXMAIAAAAACEZSZVyD3pKzGlbdwlYmWQhHHTV5SnNLknl2Gw8IaUTQVjACAAAAAAriBex2kK//RPhyVpCYJDBng4l5w8jD3m8BF7dVAP0p8AAzg3AH0AAAAFZAAgAAAAAIQuup+YGfH3mflzWopN8J1X8o8a0d9CSGIvrA5HOzraBXMAIAAAAADYvNLURXsC2ITMqK14LABQBI+hZZ5wNf24JMcKLW+84AVjACAAAAAAGeUzdG2kQrx5XypXJezZmPVzMYuqYZw7Bhbl4EPT0SMAAzg4AH0AAAAFZAAgAAAAAKsh0ADyOnVocFrOrf6MpTrNvAj8iaiE923DPryu124gBXMAIAAAAADg24a8NVE1GyScc6tmnTbmu5ulzO+896fE92lN08MeswVjACAAAAAAbO+DEBY3STVMQN7CbxmHDUBYrDR+80e797u/VmiK4vcAAzg5AH0AAAAFZAAgAAAAAKkVC2Y6HtRmv72tDnPUSjJBvse7SxLqnr09/Uuj9sVVBXMAIAAAAABYNFUkH7ylPMN+Bc3HWX1e0flGYNbtJNCY9SltJCW/UAVjACAAAAAAHlE3RLKcXpXto7Mr8nRgzEsmhjfGh4vcgPxashCSMg4AAzkwAH0AAAAFZAAgAAAAADDggcwcb/Yn1Kk39sOHsv7BO/MfP3m/AJzjGH506Wf9BXMAIAAAAAAYZIsdjICS0+BDyRUPnrSAZfPrwtuMaEDEn0/ijLNQmAVjACAAAAAA1c/sy3255NofBQrnBNSmVkSzMgsGPaaOUJShddVrnuQAAzkxAH0AAAAFZAAgAAAAAEWY7dEUOJBgjOoWVht1wLehsWAzB3rSOBtLgTuM2HC8BXMAIAAAAAAAoswiHRROurjwUW8u8D5EUT+67yvrgpB/j6PzBDAfVwVjACAAAAAALBvQumG8m7/bzJjGWN2cHSAncdN8jMtOSmEhEhGom24AAzkyAH0AAAAFZAAgAAAAAPZaapeAUUFPA7JTCMOWHJa9lnPFh0/gXfAPjA1ezm4ZBXMAIAAAAACmJvLY2nivw7/b3DOKH/X7bBXjJwoowqb1GtEFO3OYgAVjACAAAAAAGCDHuUJusGHKQ+r9nrFChmUUsRcqZKPGsRiLSk5gbFcAAzkzAH0AAAAFZAAgAAAAAK00u6jadxCZAiA+fTsPVDsnW5p5LCr4+kZZZOTDuZlfBXMAIAAAAAAote4zTEYMDgaaQbAdN8Dzv93ljPLdGjJzvnRn3KXgtQVjACAAAAAApZ6l+OrocOqgFek7WOqOP9JruTWZ+iW+5zdL3DZwzhkAAzk0AH0AAAAFZAAgAAAAAFbgabdyymiEVYYwtJSWa7lfl/oYuj/SukzJeDOR6wPVBXMAIAAAAADAFGFjS1vPbN6mQEhkDYTD6V2V23Ys9gUEUMGNvMPkaAVjACAAAAAAeICKly5Xtwmfd4JFD+5e1UWpu4V3KoRim7jeBICDs+UAAzk1AH0AAAAFZAAgAAAAABNMR6UBv2E627CqLtQ/eDYx7OEwQ7JrR4mSHFa1N8tLBXMAIAAAAAAxH4gucI4UmNVB7625C6hFSVCuIpJO3lusJlPuL8H5EQVjACAAAAAA0/uh1hrAlGGna/njCVaCqBwubxkifzF0zjCDQrIJOtUAAzk2AH0AAAAFZAAgAAAAAG8cd6WBneNunlqrQ2EmNf35W7OGObGq9WL4ePX+LUDmBXMAIAAAAAAjJ2+sX87NSis9hBsgb1QprVRnO7Bf+GObCGoUqyPE4wVjACAAAAAAAoJ4Ufdq45T9Aun5FupHlaBCIUsmUn6dXgV9KorpFikAAzk3AH0AAAAFZAAgAAAAAFWOUGkUpy8yf6gB3dio/aOfRKh7XuhvsUj48iESFJrGBXMAIAAAAAAY7sCDMcrUXvNuL6dO0m11WyijzXZvPIcOKob6IpC4PQVjACAAAAAAO/QFjczoznH95Iu0YEVMsU1GA1yxSGL8bcwSweNzAtkAAzk4AH0AAAAFZAAgAAAAAGUrIdKxOihwNmo6B+aG+Ag1qa0+iqdksHOjQj+Oy9bZBXMAIAAAAABwa5dbI2KmzBDNBTQBEkjZv4sPaeRkRNejcjdVymRFKQVjACAAAAAAUtdJucZoQvvPJiy65RonQrxBCcvtfHslpbgLbtWirCYAAzk5AH0AAAAFZAAgAAAAAOx89xV/hRk64/CkM9N2EMK6aldII0c8smdcsZ46NbP8BXMAIAAAAADBF6tfQ+7q9kTuLyuyrSnDgmrdmrXkdhl980i1KHuGHgVjACAAAAAAVc5njbLX1afpsuG662U/OBo4LanEk06lKbQtd95fswgAAzEwMAB9AAAABWQAIAAAAADJDdC9aEFl4Y8J/awHbnXGHjfP+VXQilPHJg7ewaJI7AVzACAAAAAAE+tqRl6EcBMXvbr4GDiNIYObTsYpa1n6BJk9EjIJVicFYwAgAAAAAITXOaLyGsYLUvOemQXxvnAHBwM/t3sMwfQus2aGoII+AAMxMDEAfQAAAAVkACAAAAAA3F9rjEKhpoHuTULVGgfUsGGwJs3bISrXkFP1v6KoQLgFcwAgAAAAAIBf0tXw96Z/Ds0XSIHX/zk3MzUR/7WZR/J6FpxRWChtBWMAIAAAAABzRshE3O7JzVjSGQSabvFXpen8sGUCAyr8hIIevROrjgADMTAyAH0AAAAFZAAgAAAAAOYIYoWkX7dGuyKfi3XssUlc7u/gWzqrR9KMkikKVdmSBXMAIAAAAABVF2OYjRTGi9Tw8XCAwZWLpX35Yl271TlNWp6N/nROhAVjACAAAAAAyGLbmHhe8eTQC8L2eDRcezUWULLZ9E8DPic7Mfp8/+4AAzEwMwB9AAAABWQAIAAAAACMtPm12YtdEAvqu6Eji1yuRXnu1RJP6h0l7pH3lSH4MwVzACAAAAAAENyCFfyUAh1veQBGx+cxiB7Sasrj41jzCGflZkB5cRMFYwAgAAAAAA92s3DQ3aIVOrRzusqU+xdK7cnQGCDJiyLF+su3F1ZDAAMxMDQAfQAAAAVkACAAAAAAvlI4lDcs6GB1cnm/Tzo014CXWqidCdyE5t2lknWQd4QFcwAgAAAAAD60SpNc4O2KT7J0llKdSpcX1/Xxs97N715a1HsTFkmBBWMAIAAAAABR3y/ZE3VZqBhqfUttX4bzW9wkpDoexfYZIG2fMwftcQADMTA1AH0AAAAFZAAgAAAAAKl8zcHJRDjSjJeV/WvMxulW1zrTFtaeBy/aKKhadc6UBXMAIAAAAADBdWQl5SBIvtZZLIHszePwkO14W1mQ0izUk2Ov21cPNAVjACAAAAAAHvQLJ2X4ncjVv1BsOd/jbVouRPwf4222WlGSzPyAfnsAAzEwNgB9AAAABWQAIAAAAABb6LXDWqCp1beQgQjj8I3sRTtFhlrmiBi+h/+ikmrvugVzACAAAAAA9stpgTecT7uTyaGNs3K9Bp0A7R0QaIAOfscyMXHBPX8FYwAgAAAAANTp7TwMatTSeLH3GvsrB3IOvJo+0B8HRGUFcRzJVesdAAMxMDcAfQAAAAVkACAAAAAA97NTT+81PhDhgptNtp4epzA0tP4iNb9j1AWkiiiKGM8FcwAgAAAAAKPbHg7ise16vxmdPCzksA/2Mn/qST0L9Xe8vnQugVkcBWMAIAAAAACVl3ds8xRdGFPAtZ+WtC4ojC1q5OzB6dSJoOLaJsdhhgADMTA4AH0AAAAFZAAgAAAAAMqpayM2XotEFmm0gwQd9rIzApy0X+7HfOhNk6VU7F5lBXMAIAAAAACJR9+q5T9qFHXFNgGbZnPubG8rkO6cwWhzITQTmd6VgwVjACAAAAAAEEOCUo+ihRGl1kuHlabWBWUFyJPAXSXzpQB4od57cMEAAzEwOQB9AAAABWQAIAAAAADQnslvt6Hm2kJPmqsTVYQHE/wWeZ4bE1XSkt7TKy0r1gVzACAAAAAA8URTA4ZMrhHPvlp53TH6FDCzS+0+61qHm5XK6UiOrKEFYwAgAAAAANSmECEsZgXBpoAzAIwaU3H0K/6HIutCn+ELRGBFzykkAAMxMTAAfQAAAAVkACAAAAAAhujlgFPFczsdCGXtQ/002Ck8YWQHHzvWvUHrkbjv4rwFcwAgAAAAALbV0lLGcSGfE7mDM3n/fgEvi+ifjl7WZ5b3aqjDNvx9BWMAIAAAAACKzGyyRT/3KSmGlKMHpswxj7pNu3rCmOETZ2DUhQsCBgADMTExAH0AAAAFZAAgAAAAABm/6pF96j26Jm7z5KkY1y33zcAEXLx2n0DwC03bs/ixBXMAIAAAAAD01OMvTZI/mqMgxIhA5nLs068mW+GKl3OW3ilf2D8+LgVjACAAAAAAnPa8PTfcaSSAdzNAC54IMTicbShbtt/cSnFHz7u7g8wAAzExMgB9AAAABWQAIAAAAACfw9/te4GkHZAapC9sDMHHHZgmlTrccyJDPFciOMSOcwVzACAAAAAAIIC1ZpHObvmMwUfqDRPl4C1aeuHwujM1G/yJbvybMNAFYwAgAAAAAKeagivKdHk7wAFEPHyDhsm9mDNWH5UPB+oIIlmfbzSOAAMxMTMAfQAAAAVkACAAAAAAkxHJRbnShpPOylLoDdNShfILeA1hChKFQY9qQyZ5VmsFcwAgAAAAAKidrY+rC3hTY+YWu2a7fuMH2RD/XaiTIBW1hrxNCQOJBWMAIAAAAADH8s64PC+lo8Ul4oujBlb/irtJSwu12V0HbvNzj/9qUQADMTE0AH0AAAAFZAAgAAAAAByuYl8dBvfaZ0LO/81JW4hYypeNmvLMaxsIdvqMPrWoBXMAIAAAAABNddwobOUJzm9HOUD8BMZJqkNCUCqstHZkC76FIdNg9AVjACAAAAAA4SEPY+qEZ2dCu/9485IEVybiM3Z7szXYM+izxklNm14AAzExNQB9AAAABWQAIAAAAABxMy7X5hf7AXGDz3Y/POu1ZpkMlNcSvSP92NOO/Gs7wAVzACAAAAAAHJshWo2T5wU2zvqCyJzcJQKQaHFHpCpMc9oWBXkpUPoFYwAgAAAAANeYm2aAT9f6T9uwQyH4sw2DUQyKTpo/CHt+Bb67ao4TAAMxMTYAfQAAAAVkACAAAAAAlqbslixl7Zw3bRlibZbe/WmKw23k8uKeIzPKYEtbIy0FcwAgAAAAAHEKwpUxkxOfef5HYvulXPmdbzTivwdwrSYIHDeNRcpcBWMAIAAAAAASUKCgxGu2U42qfKiyMimLPiZBMurf5v8lQJUSrhAWIAADMTE3AH0AAAAFZAAgAAAAAMXrXx0saZ+5gORmwM2FLuZG6iuO2YS+1IGPoAtDKoKBBXMAIAAAAADIQsxCr8CfFKaBcx8kIeSywnGh7JHjKRJ9vJd9x79y7wVjACAAAAAAPzOTJ088k9QdGUpZ1ubUdkw3pTGkNF8bui4a9wqeo08AAzExOAB9AAAABWQAIAAAAAAm83FA9yDUpwkbKTihe7m53u+DivS9BU2b4vQMtCVQ2AVzACAAAAAAz3m1UB/AbZPa4QSKFDnUgHaT78+6iGOFAtouiBorEgEFYwAgAAAAAHuM0gIDED47c9rdQ4MVtVzpVfn5zTF7eokO4lew4EM+AAMxMTkAfQAAAAVkACAAAAAAJRzYK0PUwr9RPG2/7yID0WgcTJPB2Xjccp5LAPDYunkFcwAgAAAAAIIh24h3DrltAzNFhF+MEmPrZtzr1PhCofhChZqfCW+jBWMAIAAAAAD6LppOeSiLHFI7EOBAz9pZPKU7LWbRWIINTlqw4zmJ5gADMTIwAH0AAAAFZAAgAAAAAHSaHWs/dnmI9sc7nB50VB2Bzs0kHapMHCQdyVEYY30TBXMAIAAAAACkV22lhEjWv/9/DubfHBAcwJggKI5mIbSK5L2nyqloqQVjACAAAAAA1I5pLbnlFf/EfJlhE0RFIioDjzKrWNh4TO8H/ksrbMcAAzEyMQB9AAAABWQAIAAAAAAC/I4TQRtCl12YZmdGz17X4GqSQgfwCPgRBwdHmdwu+QVzACAAAAAAx8f3z2ut/RAZhleari4vCEE+tNIn4ikjoUwzitfQ588FYwAgAAAAADX1qVlsvYru6YDBMtmlQoIBhLSbVykBtft8wn/A+ohJAAMxMjIAfQAAAAVkACAAAAAADGB5yU2XT0fse/MPWgvBvZikVxrl5pf3S5K1hceKWooFcwAgAAAAAIxTmlLHMjNaVDEfJbXvRez0SEPWFREBJCT6qTHsrljoBWMAIAAAAAD+RiUk9IWfaDWXtzLp2NX/vCCeU9amFnQgpJjy56dQXQADMTIzAH0AAAAFZAAgAAAAABmO7QD9vxWMmFjIHz13lyOeV6vHT6mYCsWxF7hb/yOjBXMAIAAAAACT9lmgkiqzuWG24afuzYiCeK9gmJqacmxAruIukd0xEAVjACAAAAAAyiZxtuOZQNSit4wFMJS3jnqLbDzyJ0OtDTKs6r0EO0cAAzEyNAB9AAAABWQAIAAAAAAfPUoy7QyZKhIIURso+mkP9qr1izbjETqF5s22GwjCjAVzACAAAAAAvLMsIDQ/go4VUxeh50UHmsvMvfx51cwyONnRD2odvC0FYwAgAAAAAMwY/KvSIOLoSHOq4TQ1V9ZSCPZc34SdPooPL4gYXa7UAAMxMjUAfQAAAAVkACAAAAAAE3ZqUar0Bq2zWbARE0bAv98jBlK9UJ73/xcwdMWWlSkFcwAgAAAAAK4M+MmC+9sFiFsumMyJZQKxWmmJiuG9H7IzKw083xxkBWMAIAAAAACpywsi9vc979ApgH3BDzph7Y4VIAkQhji97sswzdLAYwADMTI2AH0AAAAFZAAgAAAAAMo8FJiOq63cAmyk2O7eI7GcbQh/1j4RrMTqly3rexftBXMAIAAAAADjVmpd0WiRGTw/gAqEgGolt2EI7Csv14vKdmYoMD0aAgVjACAAAAAAEPmYjDZnMZdkaYlLZLlRgist9kCrSQe0mSuKxPwwYbQAAzEyNwB9AAAABWQAIAAAAACu5IGaIx7A3Jvly/kzlCsSA4s3iJwuIl8jEdRH0k93NwVzACAAAAAA9NRUyxYE+t0Xyosyt6vIfMFW/vBoYg6sR+jBNs4JAxIFYwAgAAAAAKMbsr4JCCodHmkBvfhOe7BJigKiJik95wLeu3PjLSpXAAMxMjgAfQAAAAVkACAAAAAALkRy0GJInXYLA+cgjs6Myb0a+Gu9hgXhHvhLNoGWfckFcwAgAAAAANbALyt9zCSvwnLaWCd2/y2eoB7qkWTvv1Ldu8r40JPuBWMAIAAAAABi0dME7AUSoeMv4yIgnGMgrX9G+Uk6HV+I9/9zzABcZwADMTI5AH0AAAAFZAAgAAAAAGoUK/DSWhT8LZhszSUqDbTrp8cSA7rdqmADKL+MILtTBXMAIAAAAABHnEE9bVa6lvhfhEMkkV2kzSSxH/sMW/FIJuw3CzWs6wVjACAAAAAAagOaxIb7co+HxAXhfI9ahHLUNqmGuN/4JxeR9sm6PdkAAzEzMAB9AAAABWQAIAAAAAC922ZDQE3h2fQKibGMZ9hV0WNlmrPYYSdtaSyYxsWYqgVzACAAAAAAagMovciKK6WVjIc2cCj8nK5O/gVOFFVeVAJpRp89tmQFYwAgAAAAAAMsWoODZkTvKSNWzloogbp/AiFFspENp49RwKjWpMLHAAMxMzEAfQAAAAVkACAAAAAAtK+JqX3K/z2txjAU15DgX4y90DS2YLfIJFolCOkJJJwFcwAgAAAAAMnR5V7gfX7MNqqUdL5AkWlkhyFXaBRVNej+Rcn8lrQkBWMAIAAAAADOmDHuXwsu9uwKYYSN5XIb/TG3K5DB5aci/X1ATKTaSwADMTMyAH0AAAAFZAAgAAAAAAeGrIMK/bac6kPczxbvRYqKMkcpeI2FjdMpD91FDWIvBXMAIAAAAAAix62z1LeS8yvSXCl5gHSIomjyx76fF3S1lp9k900hygVjACAAAAAAUK1Gzlm0meDhYPL7ZgKaCD0qz7Zb5rtnPn1EEW3UZxoAAzEzMwB9AAAABWQAIAAAAACphf298InM0Us4HT8o1W1MGw0D/02vd7Jh+U0h7qaFaQVzACAAAAAAFXtk7YpqsOJxsqGWSIL+YcBE96G3Zz9D31gPqDW94y8FYwAgAAAAAEA5mnfyccI1SjyYftq3qCuORs/NxzzqUA+fxn3FRjVsAAMxMzQAfQAAAAVkACAAAAAATLDS2cuDVM3yDMuWNgk2iGKBTzPpfJMbvxVOSY39ZfcFcwAgAAAAAPT5wRi2cLHIUflXzm6EQB/m7xdThP80ir1VV/JBBqvxBWMAIAAAAABTj7i+CxxNVkbnAkSYga/v2X3/NH3nD/FRiOo2FPkzvAADMTM1AH0AAAAFZAAgAAAAAJNjExiZVX7jfFGfYpQu16qxLN0YPqVU/5CQ/Y67YSinBXMAIAAAAABMpm2+6KrkRUlXzQoMPHrQmIO6dkQz66tYdfTeA3dKqQVjACAAAAAATkNGECGUFzo34ItVnTEmxbe8dbde8QzMgEEdjMhRLz8AAzEzNgB9AAAABWQAIAAAAABlFkYtLCx901X6QVVMkSn6Z7k30UF4xHaA0OZJJ9bdyQVzACAAAAAATez+F9GHcGzTp7jjv4feboUNb8JCkIp4EqcPFisnq7MFYwAgAAAAAOHfn3eVRhSWn61AfZuAnCGleSLluJ3sF9IFGUWrCsBaAAMxMzcAfQAAAAVkACAAAAAA8NJKN0IxZnruhswGQkiruv8Ih0EMwDcSZx/Xasup9dkFcwAgAAAAAKaJZRxzA+Igeydvuk6cSwUHXcrmT4PjhuPu//FslpdnBWMAIAAAAAC3Cs/mKg+sbU/BC9QWFglOau6FWAP1sJqqSZOEOdzh+gADMTM4AH0AAAAFZAAgAAAAABHxHdEClz7hbSSgE58+dWLlSMJnoPz+jFxp4bB1GmLQBXMAIAAAAAD3nSvT6aGD+A110J/NwEfp0nPutlmuB5B+wA3CC3noGAVjACAAAAAA56lpvjMjFzR6NMGiSF1es1X01dwINWqaxyq3UUL8XJ0AAzEzOQB9AAAABWQAIAAAAAC/o8qW/ifk3KuJ01VFkyNLgQafxB5/bGs2G5VyyVafOwVzACAAAAAA1bMqAFGDHSl6BYNLbxApvkAv2K1/oafywiX0MDz1dGUFYwAgAAAAAMjJrrd6E5DyUuXkWZsAqo020Qvom6xqAluQLod5SmtdAAMxNDAAfQAAAAVkACAAAAAAY2V8I1bz3a1AxTtmED6UhdhA09huFkuuEX8R+d/WDPUFcwAgAAAAAPTVoNRiI76tcRKqd+JBBVyy4+YcKST42p0QX2BtmQ2VBWMAIAAAAAD0kQXxOWWOfIzqhJk5Rnvp/h5VXcGxxLZ8HMvVVH++YQADMTQxAH0AAAAFZAAgAAAAAO2kSsW0WGN9AOtK4xK2SHrGhWiaAbMEKT4iZkRpaDN/BXMAIAAAAABKGzQcPM8LT2dwOggxoWjv/1imYWabbG/G4kBw8OWaxAVjACAAAAAAT3sD7PTrAPtOtiSRrG4IKmSNr/uWgX8TvIyZpZzj4wkAAzE0MgB9AAAABWQAIAAAAAAiCwzNEEaH/mDam68IdDftnhthyUFdb+ZCNSBQ91WlHQVzACAAAAAA7tHyHcxCzmbJeFYZyPm4mEgkTGKOvwY4MX82OvH0Jn8FYwAgAAAAAP4A3nd470CwvaGzyJLfzbrw7ePJV35V3Cw6tuiPFlsCAAMxNDMAfQAAAAVkACAAAAAAGuCHVNJSuoVkpPOnS5s89GuA+BLi2IPBUr2Bg1sWEPIFcwAgAAAAAEl1gncS5/xO7bQ/KQSstRV3rOT2SW6nV92ZANeG2SR6BWMAIAAAAADi6/zM3xSBXEfpRpvEdeycRDl08SmwwLey72qgZaY7FwADMTQ0AH0AAAAFZAAgAAAAAMfxz7gEaoCdPvXrubDhCZUS0ARLZc1svgbXgMDlVBPgBXMAIAAAAAB6a5dDA3fuT5Vz2KvAcbUEFX/+B7Nw2p1QqbPoQ5TTuAVjACAAAAAAB4Kp5ewWpFMJ2T+QCrfw2eJV70L4M+GM8khV1JwqOqQAAzE0NQB9AAAABWQAIAAAAACnYsqF/VzmjIImC9+dqrHO1TM6lJ6fRwM0mM6Wf6paOwVzACAAAAAA5tgZzch8uDCR1ky3SllVaKVpxAlbrhvlNDTazZZRZOAFYwAgAAAAALDJDWkmExE4L8hf2CDNLuF+dgivUGXb7ZvBp90EALm+AAMxNDYAfQAAAAVkACAAAAAAEC0sIVmadtW4YMuRXH7RpAhXclsd+3bmqGXCMeaT014FcwAgAAAAABPpXh0uzpsJJB+IRUNajmMB9WGwswfpw5T9xk3Xj6ANBWMAIAAAAABauth7HsI90juToIGY6149TbrxP8Emoa+5Izilj9zeWQADMTQ3AH0AAAAFZAAgAAAAABaIB3n49Xm9cOafSrQsE0WCcYp8rMIO/qVwIlMF5YLRBXMAIAAAAAC9EyWJV3xOu9bzgdJ/yX+ko7qLf1u3AxNMataW2C9EzQVjACAAAAAAy7IAYSgbqP7CyvurHMoEq1eO08TIPNO/XcS1L5RKXHkAAzE0OAB9AAAABWQAIAAAAAA5rZItA/cocRnngYqcJ3nBXQ+l688aKz3EQyLbYYunPAVzACAAAAAAwKyA+L7TgxztPClLrIMk2JXR+w7c04N3ZOqPgjvrIvsFYwAgAAAAABaAwqPDgkTaz4888/3PeF64+vtHbLtAZgFgEurWT5kbAAMxNDkAfQAAAAVkACAAAAAA3htn7oHJ0YYpIrs+Mzyh85Ys67HwAdv5LQl1mCdoMWkFcwAgAAAAAEHjCtNNLenHuSIYux6ezAHsXDaj2DlTF67ToDhDDe6HBWMAIAAAAAAVwUZIMifuPtmiFSEctLt+bOFo1T4fgGiWNsZAP0ddeQADMTUwAH0AAAAFZAAgAAAAAEnt18Km/nqggfIJWxzTr9r3hnXNaueG6XO9A5G11LnGBXMAIAAAAAD7QxzGMN/ard5TfFLecE6uusMmXG2+RBsBR+/NCQHUwAVjACAAAAAAIPTDniUskKj5mEzULdlKtIDquwjcqkwunJyhmBazNNcAAzE1MQB9AAAABWQAIAAAAAAVAKK/GoY8AACu/hyMpO4hdLq6JnEyWNzkyci9sbaD/wVzACAAAAAA2HmeqpMlvvBpV2zQTYIRmsc4MFlfHRwLof0ycJgMg/MFYwAgAAAAACHHCAszhxijRN9Es+XRQxXo7JQ6g2qM6f5sIdMy0VKbAAMxNTIAfQAAAAVkACAAAAAAhHyq1GQC/GiMwpYjcsfkNxolJ10ARKjIjfkW1Wipzi0FcwAgAAAAAD/uaGWxTDq87F8XZ6CrFI+RNa8yMqfSZdqK00Kj833BBWMAIAAAAAD1mJD7i29l2xloVHwS1n0CbkyodWeFfZM1KF1r4HqNIgADMTUzAH0AAAAFZAAgAAAAABAJJxHoZD+MQBWqm9UM9Dd3z5ZohIZGWRaRVRsMptKQBXMAIAAAAADrE/ca+gqj/SH4oao4wE4qn2ovoTydzcMbDbrfnUs3zAVjACAAAAAAAIUpF9mApVCMCckE7S+K9RL6TPtlTTYuzUsj9E6oIpUAAzE1NAB9AAAABWQAIAAAAAAv01wz7VG9mTepjXQi6Zma+7b/OVBaKVkWNbgDLr1mFgVzACAAAAAA0I5sxz8r6wkCp5Tgvr+iL4p6MxSOq5d3e1kZG+0b7NkFYwAgAAAAAKwOaUTAOw6Y1F5vFrbDf8yVua5Fm6hKhkdIrzejuaatAAMxNTUAfQAAAAVkACAAAAAAWXecRwxSon68xaa9THXnRDw5ZfzARKnvvjTjtbae6T0FcwAgAAAAAPh0UfUMEo7eILCMv2tiJQe1bF9qtXq7GJtC6H5Va4fIBWMAIAAAAAASanx14WR2rmomuZ2x/nuD8j75pYzz+ZAt/v7uRcoOEQADMTU2AH0AAAAFZAAgAAAAAEY5WL8/LpX36iAB1wlQrMO/xHVjoO9BePVzbUlBYo+bBXMAIAAAAABoKcpadDXUARedDvTmzUzWPe1jTuvD0z9oIcZmKuiSXwVjACAAAAAAWhdUWP186dYAIDx6RtC0o/lzwYNvYBXm4RWFMbcU3YkAAzE1NwB9AAAABWQAIAAAAADnJnWqsfx0xqNnqfFGCxIplVu8mXjaHTViJT9+y2RuTgVzACAAAAAAWAaSCwIXDwdYxWf2NZTly/iKVfG/KDjHUcA1BokN5sMFYwAgAAAAAPS3GDNsHgCuTgEWTHiEStoY0VMzlopZ8L7wPUquK7ayAAMxNTgAfQAAAAVkACAAAAAAsPG7LaIpJvcwqcbtfFUpIjj+vpNj70Zjaw3eV9T+QYsFcwAgAAAAAJQ71zi0NlCyY8ZQs3IasJ4gB1PmWx57HpnlCf3+hmhqBWMAIAAAAABmDfQAQeyeutCB+wSn4RmErwYEkYpyBeCYA+3iutFzAQADMTU5AH0AAAAFZAAgAAAAAAGiSqKaQDakMi1W87rFAhkogfRAevnwQ41onWNUJKtuBXMAIAAAAAASgiDpXfGh7E47KkOD8MAcX8+BnDShlnU5JAGdnPdqOAVjACAAAAAAucFM6KF1YHEhH9vn16ox+VSqNEykMGaJzhLPsLI7qk8AAzE2MAB9AAAABWQAIAAAAAB7L4EnhjKA5xJD3ORhH2wOA1BvpnQ+7IjRYi+jjVEaJAVzACAAAAAAuhBIm0nL3FJnVJId+7CKDASEo+l2E89Z9/5aWSITK4AFYwAgAAAAALxpb5qDek038/wz4E//KIrwwo0voaqwlB7cTXSQ44EHAAMxNjEAfQAAAAVkACAAAAAAaROn1NaDZFOGEWw724dsXBAm6bgmL5i0cki6QZQNrOoFcwAgAAAAANVT8R6UvhrAlyqYlxtmnvkR4uYK/hlvyQmBu/LP6/3ZBWMAIAAAAAC1ONheOA57vNof7iZM3b82xk1abqT0bppzjQnHCmnUIwADMTYyAH0AAAAFZAAgAAAAADqSR5e0/Th59LrauDA7OnGD1Xr3H3NokfVxzDWOFaN7BXMAIAAAAACt30faNwTWRbvmykDpiDYUOCwA6QDbBBYBFWS7rdOB4AVjACAAAAAAQZlvU/gzOwW2rcr5+W43Q6EkfX8oX1TpRJWEAZAULx4AAzE2MwB9AAAABWQAIAAAAADmtb1ZgpZjSeodPG/hIVlsnS8hoRRwRbrTVx89VwL62AVzACAAAAAAi38e1g6sEyVfSDkzZbaZXGxKI/zKNbMasOl2LYoWrq8FYwAgAAAAAFWkHkxPA28FjeUjeRILC0tX8OJcanhPP68QxM5s1kcMAAMxNjQAfQAAAAVkACAAAAAAf/f7AWVgBxoKjr7YsEQ4w/fqSvuQWV2HMiA3rQ7ur0sFcwAgAAAAADkkeJozP6FFhUdRIN74H4UhIHue+eVbOs1NvbdWYFQrBWMAIAAAAADS3qKdAEzPtGMFpsisEhnOfF0zfxmLXjHKKaNCzX4PhQADMTY1AH0AAAAFZAAgAAAAAI2WEOymtuFpdKi4ctanPLnlQud+yMKKb8p/nfKmIy56BXMAIAAAAADVKrJmhjr1rfF3p+T+tl7UFd1B7+BfJRk0e7a4im7ozgVjACAAAAAAxeqPjcgDwae+AgEXg3hU118dHoy6rcHd6jAJrGik+EcAAzE2NgB9AAAABWQAIAAAAAAuHU9Qd79hjyvKOujGanSGDIQlxzsql8JytTZhEnPw+AVzACAAAAAAjF2gV/4+sOHVgDd/oR5wDi9zL7NGpGD+NsEpGXy/a4QFYwAgAAAAAIFYquIAyUn3D2peptolq3s8oIgSLEIctqBYx6qEeQTxAAMxNjcAfQAAAAVkACAAAAAA7dQDkt3iyWYCT94d7yqUtPPwp4qkC0ddu+HFdHgVKEkFcwAgAAAAANuYvtvZBTEq4Rm9+5eb7VuFopowkrAuv86PGP8Q8/QvBWMAIAAAAABBO8poBKW/RWjdHBARy3Rzghz3frQIZEPE/nSKWYlVPgADMTY4AH0AAAAFZAAgAAAAAAwnZSDhL4tNGYxlHPhKYB8s28dY5ScSwiKZm3UhT8U3BXMAIAAAAABDoY6dhivufTURQExyC9Gx3ocpl09bgbbQLChj3qVGbgVjACAAAAAA4JWOs1XAUgX3rGBBGEDDIC9/i9khJ+FgIcwfvj/SnS0AAzE2OQB9AAAABWQAIAAAAAC2VuRdaC4ZJmLdNOvD6R2tnvkyARteqXouJmI46V306QVzACAAAAAAMn1Z6B35wFTX9mEYAPM+IiJ5hauEwfD0CyIvBrxHg7IFYwAgAAAAAMRQnV65wbGlT+7Wj/HvGPLKg8NBnhkQ8Hx/MIyWvynTAAMxNzAAfQAAAAVkACAAAAAAVerb7qVNy457rNOHOgDSKyWl5ojun7iWrv1uHPXrIZQFcwAgAAAAAIDcYS9j5z+gx0xdJj09L7876r/vjvKTi/d3bXDE3PhyBWMAIAAAAAANDuaZsI2eS6/qiSdG9pCLjcPdKS96xWUJr84V/1La7gADMTcxAH0AAAAFZAAgAAAAAH/E44yLxKCJjuSmU9A8SEhbmkDOx1PqqtYcZtgOzJdrBXMAIAAAAABgLh9v2HjBbogrRoQ82LS6KjZQnzjxyJH4PH+F3jupSAVjACAAAAAAd93miIlZ/uuhzjVBt0BPjzeewxih6TSzkzbsNhpMjA8AAzE3MgB9AAAABWQAIAAAAAAlNUdDL+f/SSQ5074mrq0JNh7CTXwTbbhsQyDwWeDVMwVzACAAAAAANIH2IlSNG0kUw4qz0budjcWn8mNR9cJlYUqPYdonucAFYwAgAAAAAF9OccClBu1j8BVMDmMtJyCxnhWCbg9FuY/8nhuFGJknAAMxNzMAfQAAAAVkACAAAAAASZ+CvUDtlk/R4HAQ3a+PHrKeY/8ifAfh0oXYFqliu80FcwAgAAAAAJelpzPgM65OZFt/mvGGpwibclQ49wH+1gbUGzd9OindBWMAIAAAAAC63uwNXVbd1VP5If++NprVRsHCbomU9Iq3GxCttNAweAADMTc0AH0AAAAFZAAgAAAAAN9fkoUVbvFV2vMNMAkak4gYfEnzwKI3eDM3pnDK5q3lBXMAIAAAAACnDkgVNVNUlbQ9RhR6Aot2nVy+U4km6+GHPkLr631jEAVjACAAAAAA5d2REAVBZI9rUfeqJ9jhePmRXvrGolUbMTFiC78k/OIAAzE3NQB9AAAABWQAIAAAAAASyK3b1nmNCMptVEGOjwoxYLLS9fYWm/Zxilqea0jpEQVzACAAAAAADDHsGrbqlKGEpxlvfyqOJKQJjwJrzsrB7k3HG0AUJbkFYwAgAAAAACZHzWGY3PEL4uPSKPezGGJkyScYw/Vt3bKmX5inVMaOAAMxNzYAfQAAAAVkACAAAAAAlSP9iK31GlcG9MKGbLmq+VXMslURr+As736rrVNXcsUFcwAgAAAAAAvbj0zfq9zzi8XReheKFbCB+h9IsOLgXPPpI5vrEJNZBWMAIAAAAABb9Y2Ox3MeHCfGnMxpR7nk6hwIJg0J8/tT8fZHwxMCBQADMTc3AH0AAAAFZAAgAAAAAOJNORH8Bev97gVU7y6bznOxJ+E6Qoykur1QP76hG1/7BXMAIAAAAAC+C1PtOOrSZgzBAGhr+dPe/kR0JUw9GTwLVNr61xC1aAVjACAAAAAA4rDk/UlRHudzR12i5Ivcb5Pxwn63JX2Sjf2xrlALL7kAAzE3OAB9AAAABWQAIAAAAAAKcHzLUomavInN6upPkyWhAqYQACP/vdVCIYpiy6U6HgVzACAAAAAATsR4KItY6R2+U7Gg6sJdaEcf58gjd1OulyWovIqfxKcFYwAgAAAAAEac2NX3v0n87D/eUM/a3Gj+5Axs896SCidMyOMaOz6zAAMxNzkAfQAAAAVkACAAAAAAqTOLiMpCdR59tLZzzIPqJvbCNvz2XQL9ust0qYaehtcFcwAgAAAAAArefox/3k5xGOeiw2m6NUdzuGxmPwcu5IFcj+jMwHgHBWMAIAAAAABK08kOnYjDWhLJ70iVhd3XEj0Q0srEQttJTawBm6GuOwADMTgwAH0AAAAFZAAgAAAAAIPSqSeVzSRgNVNmrPYHmUMgykCY27NbdDUNhE5kx/SgBXMAIAAAAAAhX90nNfxyXmZe/+btZ7q6xMX4PFyj0paM1ccJ/5IUUQVjACAAAAAA/9XTbq8UVKJbIeYtfNg8pdDrpDqbCwVr5Rq3Tb4dFwwAAzE4MQB9AAAABWQAIAAAAACLn5DxiqAosHGXIAY96FwFKjeqrzXWf3VJIQMwx1fl4gVzACAAAAAAindvU27nveutopdvuHmzdENBbeGFtI3Qcsr07jxmvm8FYwAgAAAAAJ3GMHBeIaCyFV5zBgcUMpjfIDcdoaTYQSyQN2shVJMWAAMxODIAfQAAAAVkACAAAAAA7i2S6rHRSPBwZEn59yxaS7HiYBOmObIkeyCcFU42kf8FcwAgAAAAAGb3RSEyBmgarkTvyLWtOLJcPwCKbCRkESG4RZjVmY4iBWMAIAAAAABKIdcHRSJ3dnevw5Pj0BfULPqmDOA7KBDREMGv5rXUEAADMTgzAH0AAAAFZAAgAAAAAFz+M+H/Z94mdPW5oP51B4HWptp1rxcMWAjnlHvWJDWrBXMAIAAAAACBFEOQyL7ZHu4Cq33QvXkmKuH5ibG/Md3RaED9CtG5HwVjACAAAAAABf2EvIMumZohp2E/sHWEJW4VMU3ez95hwWU9pjKGRCYAAzE4NAB9AAAABWQAIAAAAAAE7c2x3Z3aM1XGfLNk/XQ9jCazNRbGhVm7H8c2NjS5ywVzACAAAAAARJ9h8fdcwA19velF3L/Wcvi2rCzewlKZ2nA0p8bT9uwFYwAgAAAAAPl1vH6JaJGZSJ8au0NK58X4gqleryub8slUhA0HRtzmAAMxODUAfQAAAAVkACAAAAAAVoRt9B9dNVvIMGN+ea5TzRzQC+lqSZ8dd/170zU5o9cFcwAgAAAAAEwM95XZin5mv2yhCI8+ugtKuvRVmNgzzIQN0yi1+9aIBWMAIAAAAAA+rgph1avVrhyHl/6G+HtASJSiJkv7u2UPYj+25fwtLAADMTg2AH0AAAAFZAAgAAAAAKRDkjyWv/etlYT4GyoXrmBED2FgZHnhc+l9Wsl06cH2BXMAIAAAAABohlpm3K850Vndf3NmNE0hHqDlNbSR8/IvMidQ3LnIZAVjACAAAAAAKw6WqWDrI7ZfyndtrftL3uh7BaF/FQxhkh8b0Zl547gAAzE4NwB9AAAABWQAIAAAAAC3DFh5oklLCNLY90bgWm68dFXz65JpAZSp1K99MBTPAQVzACAAAAAAQgZecmxEUZVHoptEQClDwAf8smI3WynQ/i+JBP0g+kQFYwAgAAAAADmUpVNJUwrydIllFvI/aau1703nk6tF2sfzkgFlWsKCAAMxODgAfQAAAAVkACAAAAAAQ5WKvWSB503qeNlOI2Tpjd5blheNr6OBO8pfJfPNstcFcwAgAAAAAKwHgQLSDJ5NwLBQbY5OnblQIsVDpGV7q3RCbFLD1U4/BWMAIAAAAADKOab9GTEiaybo9o4J72Pb9LiQU/CuKoQm6Jtlmo/IeQADMTg5AH0AAAAFZAAgAAAAAGfhFY3RGRm5ZgWRQef1tXxHBq5Y6fXaLAR4yJhrTBplBXMAIAAAAACKEF0ApLoB6lP2UqTFsTQYNc9OdDrs/vziPGzttGVLKQVjACAAAAAAvJcnqBNCZgHe+Q7Cqk+yiAaIuSJIRItXEsAlKy9PES4AAzE5MAB9AAAABWQAIAAAAAAIM73gPcgzgotYHLeMa2zAU4mFsr7CbILUZWfnuKSwagVzACAAAAAAJCSu98uV8xv88f2BIOWzt6p+6EjQStMBdkGPUkgN79cFYwAgAAAAABQqDP10DcaqEY7vxFS7U/9xcadDASONow1+xmVsPZ11AAMxOTEAfQAAAAVkACAAAAAAkWmHCUsiMy1pwZTHxVPBzPTrWFBUDqHNrVqcyyt7nO8FcwAgAAAAAMv2CebFRG/br7USELR98sIdgE9OQCRBGV5JZCO+uPMgBWMAIAAAAABxLEFq6nFVI3WaYhggo1d6oeDsBSm4wCW0T6D0yE/puwADMTkyAH0AAAAFZAAgAAAAAGInUYv0lP/rK7McM8taEHXRefk8Q2AunrvWqdfSV7UaBXMAIAAAAACE+WPxJ3gan7iRTbIxXXx+bKVcaf8kP4JD8DcwU0aL7wVjACAAAAAAiThzC5jAZ5D6xuCIsUD1aLy7KxmT2Fb8ybTkLxvj17MAAzE5MwB9AAAABWQAIAAAAACmHlg2ud3cplXlTsNTpvNnY6Qm1Fce0m899COamoDjaQVzACAAAAAArtJQeJIlepBWRU2aYar7+YGYVQ7dfDc1oxgTmA8r9q0FYwAgAAAAADZpcxtBFv18EdmhxsoxlDs253tNB5zqVY/h+nFD1isIAAMxOTQAfQAAAAVkACAAAAAApzcWSAbZWV/Rq+ylRNqqlJqNVR4fhXrz4633/MQOQgcFcwAgAAAAAN/jz/bsEleiuCl+li83EWlG6UMHA8CyaOMRKCkXkSCPBWMAIAAAAADSTHgALJugYsjIvSK1o0UwG6VizNokXQ/tnPNjOpGB1AAABWUAIAAAAADrmnP3kS2GpCl+gdL2da90KHTkBX46iQ/sZRoj7uPz7BJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", + "base64": "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", "subType": "06" } } @@ -98,7 +109,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: aggregate outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml similarity index 96% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Correctness.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml index 03df38175f..6307bea62a 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml @@ -1,15 +1,18 @@ # Test correctness results. # Does not include command monitoring expectations or outcome assertions to make tests more readable. -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. topology: [ "replicaset" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml similarity index 65% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Delete.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml index fa2daa78e6..d017530cb0 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml @@ -1,13 +1,16 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. topology: [ "replicaset" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. Delete." @@ -65,7 +68,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -76,7 +83,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -87,7 +98,7 @@ tests: "encryptedDecimal": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -96,28 +107,15 @@ tests: "limit": 1 } ] + ordered: true encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDecimal": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: delete outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml similarity index 75% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-FindOneAndUpdate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml index 3b772cd6d7..32a35875a4 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml @@ -1,13 +1,16 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. topology: [ "replicaset" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. FindOneAndUpdate." @@ -66,7 +69,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -77,7 +84,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -86,7 +97,7 @@ tests: "encryptedDecimal": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -96,25 +107,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDecimal": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: findAndModify outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml similarity index 75% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-InsertFind.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml index 48d44264a8..d1bb287696 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml @@ -1,13 +1,16 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. topology: [ "replicaset" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. Insert and Find." @@ -64,7 +67,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -75,7 +82,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -84,7 +95,7 @@ tests: "encryptedDecimal": { "$gt": { "$binary": { - "base64": "CgljAAADcGF5bG9hZADZYgAABGcAnWIAAAMwAH0AAAAFZAAgAAAAAJu2KgiI8vM+kz9qD3ZQzFQY5qbgYqCqHG5R4jAlnlwXBXMAIAAAAAAAUXxFXsz764T79sGCdhxvNd5b6E/9p61FonsHyEIhogVjACAAAAAAeEtIU/sp7zwjA/VArYkCzkUUiRiQOmlTaVZvfUqbEp0AAzEAfQAAAAVkACAAAAAAPQPvL0ARjujSv2Rkm8r7spVsgeC1K3FWcskGGZ3OdDIFcwAgAAAAACgNn660GmefR8jLqzgR1u5O+Uocx9GyEHiBqVGko5FZBWMAIAAAAACMKx1UyNAN4yVafFht8jp4w4LNaJcLhnozfSMzDHD3owADMgB9AAAABWQAIAAAAAD2Zi6kcxmaD2mY3VWrP+wYJMPg6cSBIYPapxaFQxYFdQVzACAAAAAAM/cV36BLBY3xFBXsXJY8M9EHHOc/qrmdc2CJmj3M89gFYwAgAAAAAHdek2wlAQ5BQORVjudyziRTV0EKx8qbsnoDiw4HG2xaAAMzAH0AAAAFZAAgAAAAAOaNqI9srQ/mI9gwbk+VkizGBBH/PPWOVusgnfPk3tY1BXMAIAAAAAAc96O/pwKCmHCagT6T/QV/wz4vqO+R22GsZ1dse2Vg6QVjACAAAAAA3tld+twIlVWnCTXElspau5k02CwxXDjh+3u1CQtV/W4AAzQAfQAAAAVkACAAAAAA0XlQgy/Yu97EQOjronl9b3dcR1DFn3deuVhtTLbJZHkFcwAgAAAAACoMnpVl6EFJak8A+t5N4RFnQhkQEBnNAx8wDqmq5U/dBWMAIAAAAABRmXIchytMNz4J439viiY5FZ1OB3AXJBkZ3udUqHpsqAADNQB9AAAABWQAIAAAAAAOtpNexRxfv0yRFvZO9DhlkpU4mDuAb8ykdLnE5Vf1VAVzACAAAAAAeblFKm/30orP16uQpZslvsoS8s0xfNPIBlw3VkHeekYFYwAgAAAAAJQdzGMO2s1AkdKz3ZPytivrQphUFVhvhMcXjvUGzHBDAAM2AH0AAAAFZAAgAAAAAIr8xAFm1zPmrvW4Vy5Ct0W8FxMmyPmFzdWVzesBhAJFBXMAIAAAAABYeeXjJEzTHwxab6pUiCRiZjxgtN59a1y8Szy3hfkg+gVjACAAAAAA161loizzKvXS1Po/3bxeNICmKpsPSK9Q+EpkISK3jVoAAzcAfQAAAAVkACAAAAAAl+ibLk0/+EwoqeC8S8cGgAtjtpQWGEZDsybMPnrrkwEFcwAgAAAAAHPPBudWgQ+HUorLDpJMqhS9VBF2VF5aLcxgrM1s+yU7BWMAIAAAAAACyakB3CZWEjbxK0u9Sflc3+EafBAQFbvBpCxKvxuEQAADOAB9AAAABWQAIAAAAACR9erwLTb+tcWFZgJ2MEfM0PKI9uuwIjDTHADRFgD+SQVzACAAAAAAcOop8TXsGUVQoKhzUllMYWxL93xCOkwtIpV8Q6hiSYYFYwAgAAAAAJ43998lzKoVqWm99ZzKHJLeVscGNCVoKDvpUtt2rDI9AAM5AH0AAAAFZAAgAAAAALv0vCPgh7QpmM8Ug6ad5ioZJCh7pLMdT8FYyQioBQ6KBXMAIAAAAADsCPyIG8t6ApQkRk1fX/sfc1kpuWCWP8gAEpnYoBSHrQVjACAAAAAAj2h1WLr0EFEFZ31djx3BtLIbAtdE05ax54opkJo4/bQAAzEwAH0AAAAFZAAgAAAAAIW4AxJgYoM0pcNTwk1RSbyjZGIqgKL1hcTJmNrnZmoPBXMAIAAAAAAZpfx3EFO0vY0f1eHnE0PazgqeNDTaj+pPJMUNW8lFrAVjACAAAAAAqFD83E8POmo6pdqg7D+jWtngbgcV99mbQBxkbpX7ds4AAzExAH0AAAAFZAAgAAAAAKliO6L9zgeuufjj174hvmQGNRbmYYs9yAirL7OxwEW3BXMAIAAAAAAqU7vs3DWUQ95Eq8OejwWnD0GuXd+ASi/uD6S0l8MM1QVjACAAAAAAp0cwpAh7SiuStPwYNVvp83N5GWyWRWA7UGRxHDDj8g8AAzEyAH0AAAAFZAAgAAAAAOGQcCBkk+j/Kzjt/Cs6g3BZPJG81wIHBS8JewHGpgk+BXMAIAAAAABjrxZXWCkdzrExwCgyHaafuPSQ4V4x2k9kUCAqUaYKDQVjACAAAAAAJr/tEuxScrJJEMECy4itHf1y5+gO2l5sBjgM/EOLXw0AAzEzAH0AAAAFZAAgAAAAAPmCNEt4t97waOSd5hNi2fNCdWEkmcFJ37LI9k4Az4/5BXMAIAAAAABX7DuDPNg+duvELf3NbLWkPMFw2HGLgWGHyVWcPvSNCAVjACAAAAAAeNCEn5Z3A5CgCnIgaBl2N8p0t+5rRY06vGg6ePVMaTUAAzE0AH0AAAAFZAAgAAAAAD4vtVUYRNB+FD9yoQ2FVJH3nMeJeKbi6eZfth638YqbBXMAIAAAAAANCuUB4OdmuD6LaDK2f3vaqfgYYvg40wDXOBbcFjTqLwVjACAAAAAATozHsao4er/Uk5H/pKe/1JEo/3h4Pgah2RgJHXf2PeYAAzE1AH0AAAAFZAAgAAAAAJPIRzjmTjbdIvshG6UslbEOd797ZSIdjGAhGWxVQvK1BXMAIAAAAABgmJ0Jh8WLs9IYs/a7DBjDWd8J3thW/AGJK7zDnMeYOAVjACAAAAAAtD48LbnCkTvacfz8DvR1ixsbYceiJxJRV7tLqDUWvVwAAzE2AH0AAAAFZAAgAAAAAABQyKQPoW8wGPIqnsTv69+DzIdRkohRhOhDmyVHkw9WBXMAIAAAAAAqWA2X4tB/h3O1Xlawtz6ndI6WaTwgU1QYflL35opu5gVjACAAAAAAphx2eRFGSW/tm3HxilqRv5Zj5TtOy4KSGiCIm6yJANYAAzE3AH0AAAAFZAAgAAAAACB7NOyGQ1Id3MYnxtBXqyZ5Ul/lHH6p1b10U63DfT6bBXMAIAAAAADpOryIcndxztkHSfLN3Kzq29sD8djS0PspDSqERMqokQVjACAAAAAAIHYCljTfUu/Ox23Nrjz2Z6cqQpn4s0sJV/SxkC9UtIEAAzE4AH0AAAAFZAAgAAAAAKVfXLfs8XA14CRTB56oZwV+bFJN5BHraTXbqEXZDmTkBXMAIAAAAAASRWTsfGOpqdffiOodoqIgBzG/yzFyjR5CfUsIUIWGpgVjACAAAAAAGs7l0GsFNkQMcJBUuUsEOMuQupbEvi7MDcezspsCY3EAAzE5AH0AAAAFZAAgAAAAAH/aZr4EuS0/noQR9rcF8vwoaxnxrwgOsSJ0ys8PkHhGBXMAIAAAAACd7ObGQW7qfddcvyxRTkPuvq/PHu7+6I5dxwS1Lzy5XAVjACAAAAAAWnfsbdu5Qvb62rEpBR90qh+3jdYfp266dmuNPLoXys4AAzIwAH0AAAAFZAAgAAAAAKvlcpFFNq0oA+urq3w6d80PK1HHHw0H0yVWvU9aHijXBXMAIAAAAADWnAHQ5Fhlcjawki7kWzdqjM2f6IdGJblojrYElWjsZgVjACAAAAAAdNxswYIMjqVta8sLC8wniMm6gOgIgG5eWTPAxItBJJ4AAzIxAH0AAAAFZAAgAAAAAH/2aMezEOddrq+dNOkDrdqf13h2ttOnexZsJxG1G6PNBXMAIAAAAABNtgnibjC4VKy5poYjvdsBBnVvDTF/4mmEAxsXVgZVKgVjACAAAAAArWXr2Myg8T8/0eYBCUXDOsIR//zqZtlE6Lb1YZJkvYcAAzIyAH0AAAAFZAAgAAAAAF2wZoDL6/V59QqO8vdRZWDpXpkV4h4KOCSn5e7x7nmzBXMAIAAAAADLZBu7LCYjbThaVUqMK14H/elrVOYIKJQCx4C9Yjw37gVjACAAAAAAWeF6F8pBeCm27PWjQGrlyN7JHp9WqCNnwFzqZ+V51t0AAzIzAH0AAAAFZAAgAAAAAH27yYaLn9zh2CpvaoomUPercSfJRUmBY6XFqmhcXi9QBXMAIAAAAAAUwumVlIYIs9JhDhSj0R0+59psCMsFk94E62VxkPt42QVjACAAAAAAjbsmlVrfexmULer3RtGwnOf20pF0xv4Z46OHOaIwgzEAAzI0AH0AAAAFZAAgAAAAALMg2kNAO4AFFs/mW3In04yFeN4AP6Vo0klyUoT06RquBXMAIAAAAAAgGWJbeIdwlpqXCyVIYSs0dt54Rfc8JF4b8uYc+YUj0AVjACAAAAAAsNKKan8cMW1oYFmQiyPZbepuJlDD0/CQUS21++CP16kAAzI1AH0AAAAFZAAgAAAAALas/i1T2DFCEmrrLEi7O2ngJZyFHialOoedVXS+OjenBXMAIAAAAAA1kK0QxY4REcGxHeMkgumyF7iwlsRFtw9MlbSSoQY7uAVjACAAAAAAUGZ/BwF6Q9hcFBC8F5ed6RwYWJ1/OahRwljBloMgkFAAAzI2AH0AAAAFZAAgAAAAAP1TejmWg1CEuNSMt6NUgeQ5lT+oBoeyF7d2l5xQrbXWBXMAIAAAAABPX0kj6obggdJShmqtVfueKHplH4ZrXusiwrRDHMOKeQVjACAAAAAApU9zcPAQ2xOspmwhjWN90v2m2clQ4rk7WBmm/nUyOHQAAzI3AH0AAAAFZAAgAAAAAMrKn+QPa/NxYezNhlOX9nyEkN1kE/gW7EuZkVqYl0b8BXMAIAAAAABUoZMSPUywRGfX2EEencJEKH5x/P9ySUVrhStAwgR/LgVjACAAAAAAl/hDhauhBkda/I/kLr/XLxyso2guawC2isj+tDQhfMoAAzI4AH0AAAAFZAAgAAAAAMmD1+a+oVbiUZd1HuZqdgtdVsVKwuWAn3/M1B6QGBM3BXMAIAAAAACLyytOYuZ9WEsIrrtJbXUx4QgipbaAbmlJvSZVkGi0CAVjACAAAAAAVKbMdxvkfCBoqJCUVjLrtKF90a80LocckdFc7LvYuWIAAzI5AH0AAAAFZAAgAAAAAOVKV7IuFwmYP1qVv8h0NvJmfPICu8yQhzjG7oJdTLDoBXMAIAAAAABL70XLfQLKRsw1deJ2MUvxSWKxpF/Ez73jqtbLvqbuogVjACAAAAAARiC2ShVCKMjjiqB6xg+jDIYJNhhWqmceO07EC8rEXkoAAzMwAH0AAAAFZAAgAAAAAKS/1RSAQma+xV9rz04IcdzmavtrBDjOKPM+Z2NEyYfPBXMAIAAAAAAOJDWGORDgfRv8+w5nunh41wXb2hCA0MRzwnLnQtIqPgVjACAAAAAAWU2Vw2Rta8oHxPPfOWQa86tjYWndb0E3RNlZ9gthHt0AAzMxAH0AAAAFZAAgAAAAAFeq8o82uNY1X8cH6OhdTzHNBUnCChsEDs5tm0kPBz3qBXMAIAAAAABaxMBbsaeEj/EDtr8nZfrhhhirBRPJwVamDo5WwbgvTQVjACAAAAAAqcsCyJuLvxG8STrUI1Ccaga760+4fbmIPc6bXodea0gAAzMyAH0AAAAFZAAgAAAAAI8IKIfDrohHh2cjspJHCovqroSr5N3QyVtNzFvT5+FzBXMAIAAAAABXHXteKG0DoOMmECKp6ro1MZNQvXGzqTDdZ0DUc8QfFAVjACAAAAAA47u7VuqtYJ4jt5CcGrUacopqApwpqEpIpkalvCRtul0AAzMzAH0AAAAFZAAgAAAAAJkHvm15kIu1OtAiaByj5ieWqzxiu/epK6c/9+KYIrB0BXMAIAAAAACzg5TcyANk0nes/wCJudd1BwlkWWF6zw3nGclq5v3SJQVjACAAAAAAvTQRtEYdIj2+bfJNamvrgWRoBsYYjMQx9MyuFwSfKmYAAzM0AH0AAAAFZAAgAAAAAAYSOvEWWuSg1Aym7EssNLR+xsY7e9BcwsX4JKlnSHJcBXMAIAAAAABT48eY3PXVDOjw7JpNjOe1j2JyI3LjDnQoqZ8Je5B2KgVjACAAAAAA4eLwHqiSgM1pMqjK3QFgYauOnI3emnlOK3rgQPuec4AAAzM1AH0AAAAFZAAgAAAAAGQxC9L1e9DfO5XZvX1yvc3hTLtQEdKO9FPMkyg0Y9ZABXMAIAAAAADtmcMNJwdWLxQEArMGZQyzpnu+Z5yMmPAkvgq4eAKwNQVjACAAAAAAxA7XR9Cp6671TiZesvqE/EPKVPXfGDaYuDnce+MaZw0AAzM2AH0AAAAFZAAgAAAAADLHK2LNCNRO0pv8n4fAsxwtUqCNnVK8rRgNiQfXpHSdBXMAIAAAAACf16EBIHRKD3SzjRW+LMOl+47QXA3CJhMzlcqyFRW22AVjACAAAAAA9F9Ztm39rTapXoeRmzk6eUoa3MDPatwoQD5PvD+3xGEAAzM3AH0AAAAFZAAgAAAAAHiZJiXKNF8bbukQGsdYkEi95I+FSBHy1I5/hK2uEZruBXMAIAAAAADE+lZBa8HDUJPN+bF6xI9x4N7GF9pj3vBR7y0BcfFhBAVjACAAAAAArsw2CrSC2y02EiWJxViVxlYRIXtWARpUTwO/mfqS7O4AAzM4AH0AAAAFZAAgAAAAAI1oa2OIw5TvhT14tYCGmhanUoYcCZtNbrVbeoMldHNZBXMAIAAAAAAx2nS0Ipblf2XOgBiUOuJFBupBhe7nb6QPLZlA4aMPCgVjACAAAAAAaVoGxV5YH4urmcw3Zocg+QHoq+hA0u6nIkconI0MtZoAAzM5AH0AAAAFZAAgAAAAABgTWS3Yap7Q59hii/uPPimHWXsr+DUmsqfwt/X73qsOBXMAIAAAAACKK05liW5KrmEAvtpCB1WUltruzUylDDpjea//UlWoOAVjACAAAAAAAI3iOqWSA+DpgNr/neIRpt85Q8pLS/81ZKDnbC4io/0AAzQwAH0AAAAFZAAgAAAAABOAnBPXDp6i9TISQXvcNKwGDLepZTu3cKrB4vKnSCjBBXMAIAAAAADjjzZO7UowAAvpwyG8BNOVqLCccMFk3aDK4unUeft5ywVjACAAAAAAIZqKrUcbtze/K3CUKdchuVXQYkgj6jQfIeMZ3GrtYeEAAzQxAH0AAAAFZAAgAAAAAHN8hyvT1lYrAsdiV5GBdd5jhtrAYE/KnSjw2Ka9hjz9BXMAIAAAAAD794JK7EeXBs+D7yOVK7nWF8SbZ/7U8gZ7nnT9JFNwTAVjACAAAAAAQ4K5cDGLM/2L7KbMt537tsG1eiWrboKMj6pXVfBDIBoAAzQyAH0AAAAFZAAgAAAAAO93bPrq8bsnp1AtNd9ETnXIz0lH/2HYN/vuw9wA3fyFBXMAIAAAAABHlls5fbaF2oAGqptC481XQ4eYxInTC29aElfmVZgDUgVjACAAAAAAe0oLBk+ZaipcPgjjHvACAyA42cLY8i+s+V0EsarulZkAAzQzAH0AAAAFZAAgAAAAAL1YsAZm1SA0ztU6ySIrQgCCA74V6rr0/4iIygCcaJL6BXMAIAAAAADTXWTHWovGmUR1Zg9l/Aqq9H5mOCJQQrb/Dfae7e3wKAVjACAAAAAAAeXpcv0oCp9iY/U81w+WJjFjGn60KLQEURJd/pSoaJgAAzQ0AH0AAAAFZAAgAAAAAEoFAeHk0RZ9kD+cJRD3j7PcE5gzWKnyBrF1I/MDNp5mBXMAIAAAAACgHtc2hMBRSZjKw8RAdDHK+Pi1HeyjiBuAslGVNcW5tAVjACAAAAAAmSJiRfjmuwq5cYfadeYQRkPqbGYYrMNTQeyhBEAYClEAAzQ1AH0AAAAFZAAgAAAAAAW+7DmSN/LX+/0uBVJDHIc2dhxAGz4+ehyyz8fAnNGoBXMAIAAAAAA6Ilw42EvvfLJ3Eq8Afd+FjPoPcQutZO6ltmCLEr8kxQVjACAAAAAAUHZ9EwBIvDI4PUeCOzZCuVN9g9h5Zvq2mSpmejPLBE8AAzQ2AH0AAAAFZAAgAAAAANBdV7M7kuYO3EMoQItAbXv4t2cIhfaT9V6+s4cg9djlBXMAIAAAAABvz4MIvZWxxrcJCL5qxLfFhXiUYB1OLHdKEjco94SgDgVjACAAAAAAvz1koQRh+weAUstLWz/iHxFrLReN4KtQjdEU/zrKOFsAAzQ3AH0AAAAFZAAgAAAAAMoAoiAn1kc79j5oPZtlMWHMhhgwNhLUnvqkqIFvcH1NBXMAIAAAAADcJTW7WiCyW0Z9YDUYwppXhLj4Ac1povpJvcAq+i48MQVjACAAAAAARL0k3Uwe7OUwTejHMoNwK+twNHQznmo1vVUZaH7h8BIAAzQ4AH0AAAAFZAAgAAAAACI3j5QP7dWHpcT6WO/OhsWwRJNASBYqIBDNzW8IorEyBXMAIAAAAABxUpBSjXwCKDdGP9hYU+RvyR+96kChfvyyRC4jZmztqAVjACAAAAAA93/qZqyku1BJePH5hmMWsMfuMlF7TOFZp7z2wLJNdeYAAzQ5AH0AAAAFZAAgAAAAAKsbycEuQSeNrF8Qnxqw3x3og8JmQabwGqnDbqzFRVrrBXMAIAAAAACno/3ef2JZJS93SVVzmOZSN+jjJHT8s0XYq2M46d2sLAVjACAAAAAARUbN9bbz8Balyw8pmBLg3L5dy3aQ9s8IgqpVLeQzTkUAAzUwAH0AAAAFZAAgAAAAAPXIcoO8TiULqlxzb74NFg+I8kWX5uXIDUPnh2DobIoMBXMAIAAAAADR6/drkdTpnr9g1XNvKDwtBRBdKn7c2c4ZNUVK5CThdQVjACAAAAAAnJXYvnjljaE6ajRF+h90FXzJ9EJY26D0oG9t61ZMzqgAAzUxAH0AAAAFZAAgAAAAAEa03ZOJmfHT6/nVadvIw71jVxEuIloyvxXraYEW7u7pBXMAIAAAAADzRlBJK75FLiKjz3djqcgjCLo/e3yntI3MnPS48OORhgVjACAAAAAAbFS6SmTSDF3AsPceQrRwMMRoAnzL/k5avSHlIji5zYIAAzUyAH0AAAAFZAAgAAAAAKx8NLSZUU04pSSGmHa5fh2oLHsEN5mmNMNHL95/tuC9BXMAIAAAAAA59hcXVaN3MNdHoo11OcH1aPRzHCwpVjO9mGfMz4xh3QVjACAAAAAAoIvo/iOb/eXJv7w4KSQo74DOyO4uAIO09Ba7JZw9ousAAzUzAH0AAAAFZAAgAAAAAHNKAUxUqBFNS9Ea9NgCZoXMWgwhP4x0/OvoaPRWMquXBXMAIAAAAABUZ551mnP4ZjX+PXU9ttomzuOpo427MVynpkyq+nsYCQVjACAAAAAAlCTphL00SYotztwI+txbHAFTusjh1nNbiya24rM1dUEAAzU0AH0AAAAFZAAgAAAAALfGXDlyDVcGaqtyHkLT0qpuRhJQLgCxtznazhFtuyn/BXMAIAAAAABipxlXDq14C62pXhwAeen5+syA+/C6bN4rtZYcO4zKwAVjACAAAAAA431CBTW4s3IQde024W2cz5iEnj8EJ7p07esepoVDxmQAAzU1AH0AAAAFZAAgAAAAANoEr8sheJjg4UCfBkuUzarU9NFoy1xwbXjs5ifVDeA9BXMAIAAAAABPoyTf6M+xeZVGES4aNzVlq7LgjqZXJ/QunjYVusGUEAVjACAAAAAAywuLdjJJuXewOXMAr25GMBEif8+P74+pGaENUn+XIB8AAzU2AH0AAAAFZAAgAAAAAKvDiK+xjlBe1uQ3SZTNQl2lClIIvpP/5CHwY6Kb3WlgBXMAIAAAAAANnxImq5MFbWaRBHdJp+yD09bVlcFtiFDYsy1eDZj+iQVjACAAAAAAvwntX1RgeNfp5oiD+r3sAKIbMGy1yEblODDYWec1xuIAAzU3AH0AAAAFZAAgAAAAAF49IlFH9RmSUSvUQpEPUedEksrQUcjsOv44nMkwXhjzBXMAIAAAAADJtWGbk0bZzmk20obz+mNsp86UCu/nLLlbg7ppxYn7PgVjACAAAAAAKW8QTERGXwh6fE4gJsULcmu80B4BMkMaZR9USn/C6RgAAzU4AH0AAAAFZAAgAAAAAOuSJyuvz50lp3BzXlFKnq62QkN2quNU1Gq1IDsnFoJCBXMAIAAAAAAqavH1d93XV3IzshWlMnzznucadBF0ND092/2ApI1AcAVjACAAAAAAmVcLt9hf9/q+2O3K4cvFAsvnCsSofHZ5iTzIaR5YKFIAAzU5AH0AAAAFZAAgAAAAALtgC4Whb4ZdkCiI30zY6fwlsxSa7lEaOAU3SfUXr02XBXMAIAAAAACgdZ6U1ZVgUaZZwbIaCdlANpCw6TZV0bwg3DS1NC/mnAVjACAAAAAAYJOnAwVW0gPl1jkCxAL1ZAhKkJ1+ShfwyaIaipflCegAAzYwAH0AAAAFZAAgAAAAAF6PfplcGp6vek1ThwenMHVkbZgrc/dHgdsgx1VdPqZ5BXMAIAAAAACha3qhWkqmuwJSEXPozDO8y1ZdRLyzt9Crt2vjGnT7AAVjACAAAAAA1JYhGDDvtf8b2Pup5jdzQmy325tGo27n/5thjZ/GI4sAAzYxAH0AAAAFZAAgAAAAAKoLEhLvLjKc7lhOJfx+VrGJCx9tXlOSa9bxQzGR6rfbBXMAIAAAAAAIDK5wNnjRMBzET7x/KAMExL/zi1IumJM92XTgXfoPoAVjACAAAAAAhoVLxo0hXbxAzz2BWbeRxAzk08TbVuHxJkdsbCISswkAAzYyAH0AAAAFZAAgAAAAADoQv6lutRmh5scQFvIW6K5JBquLxszuygM1tzBiGknIBXMAIAAAAADAD+JjW7FoBQ76/rsECmmcL76bmyfXpUU/awqIsZdO+wVjACAAAAAAwLIixR/svwADNhtiOZ9mDF2rUqEvuPx2tUSQbhOpersAAzYzAH0AAAAFZAAgAAAAAJNHUGAgn56KekghO19d11nai3lAh0JAlWfeP+6w4lJBBXMAIAAAAAD9XGJlvz59msJvA6St9fKW9CG4JoHV61rlWWnkdBRLzwVjACAAAAAAKMVXumaH5QOt5WVHFip9xWx9e5I/5Y9uEO0UAX6dy7QAAzY0AH0AAAAFZAAgAAAAAHgYoMGjEE6fAlAhICv0+doHcVX8CmMVxyq7+jlyGrvmBXMAIAAAAAC/5MQZgTHuIr/O5Z3mXPvqrom5JTQ8IeSpQGhO9sB+8gVjACAAAAAAPMgD8Rqnd94atKnMyPjlTwthUQ710MKJVqgtwNXLFWwAAzY1AH0AAAAFZAAgAAAAANpIljbxHOM7pydY877gpRQvYY2TGK7igqgGsavqGPBABXMAIAAAAAAqHyEu9gpurPOulApPnr0x9wrygY/7mXe9rAC+tPK80wVjACAAAAAAezZExYLAbjyMsVuSrWGQ+LsXYEcp6AFMANkQ496mTXcAAzY2AH0AAAAFZAAgAAAAAGR+gEaZTeGNgG9BuM1bX2R9ed4FCxBA9F9QvdQDAjZwBXMAIAAAAABSkrYFQ6pf8MZ1flgmeIRkxaSh/Eep4Btdx4QYnGGnwAVjACAAAAAAQ011358xXtVDOyGPpF2+zGoMdiQ8PGCB1QfDcluYUF4AAzY3AH0AAAAFZAAgAAAAAFNprhQ3ZwIcYbuzLolAT5n/vc14P9kUUQComDu6eFyKBXMAIAAAAAAcx9z9pk32YbPV/sfPZl9ALIEVsqoLXgqWLVK/tP+heAVjACAAAAAA3eqduo9CWE5BePqBq7KCh5++QqXnyjCwybB15Zoeiu4AAzY4AH0AAAAFZAAgAAAAADgyPqQdqQrgfmJjRFAILTHzXbdw5kpKyfeoEcy6YYG/BXMAIAAAAAAE+3XsBQ8VAxAkN81au+f3FDeCD/s7KoZD+fnM1MJSSAVjACAAAAAAWHAiQNo6HBkIhf72fVQxJLaCtNeTqn2OuMmYZjT7PRkAAzY5AH0AAAAFZAAgAAAAAI0CT7JNngTCTUSei1Arw7eHWCD0jumv2rb7imjWIlWABXMAIAAAAABSP8t6ya0SyCphXMwnru6ZUDXWElN0NfBvEOhDvW9bJQVjACAAAAAAbfZdoqMQ71hNIsycNHl6JpSmAFDPSfgzdWkGqFZNoScAAzcwAH0AAAAFZAAgAAAAAD/FIrGYFDjyYmVb7oTMVwweWP7A6F9LnyIuNO4MjBnXBXMAIAAAAACIZgJCQRZu7NhuNMyOqCn1tf+DfU1qm10TPCfj5JYV3wVjACAAAAAAZCaO06/DjOBjNrTxB8jdxM7X4XvNu9QTa2Y00kZJwgEAAzcxAH0AAAAFZAAgAAAAAHIkVuNDkSS1cHIThKc/O0r2/ubaABTOi8Q1r/dvBAsEBXMAIAAAAADdHYqchEiJLM340c3Q4vJABmmth3+MKzwLYlsG6GS7sQVjACAAAAAA4byh4Re83zUZxvEH4iJSsnFei+ohsWxjSpB5YoAPawIAAzcyAH0AAAAFZAAgAAAAAJmoDILNhC6kn3masElfnjIjP1VjsjRavGk1gSUIjh1NBXMAIAAAAAD97Ilvp3XF8T6MmVVcxMPcdL80RgQ09UoC6PnoOvZ1IQVjACAAAAAAx9wkqPvtzrL9EFDmFvktI129ti8IAdHn0oudubGPWAAAAzczAH0AAAAFZAAgAAAAAI5bm3YO0Xgf0VT+qjVTTfvckecM3Cwqj7DTKZXf8/NXBXMAIAAAAAD/m+h8fBhWaHm6Ykuz0WX1xL4Eme3ErLObyEVJf8NCywVjACAAAAAAiGdTXD22l1zDxHeF4NXUTiBnNTsdpzJRwM6riTPuOogAAzc0AH0AAAAFZAAgAAAAANqo4+p6qdtCzcB4BX1wQ6llU7eFBnuu4MtZwp4B6mDlBXMAIAAAAAAGiz+VaukMZ+6IH4jtn4KWWdKK4/W+O+gRioQDrfzpMgVjACAAAAAAl/wlBjSJW/hKkll8HSBCGw4Ce1pJ5rZuhVE09hKq4jQAAzc1AH0AAAAFZAAgAAAAAPrFXmHP2Y4YAm7b/aqsdn/DPoDkv7B8egWkfe23XsM1BXMAIAAAAAAGhwpKAr7skeqHm3oseSbO7qKNhmYsuUrECBxJ5k+D2AVjACAAAAAAKSsxERy2OBhb5MiH9/H2BET2oeU6yVihiAoWi/16FroAAzc2AH0AAAAFZAAgAAAAABzjYxwAjXxXc0Uxv18rH8I3my0Aguow0kTwKyxbrm+cBXMAIAAAAADVbqJVr6IdokuhXkEtXF0C2gINLiAjMVN20lE20Vmp2QVjACAAAAAACJ+VAwl9X88mjC76yzkWeL4K6AamNYjbNkpS9B6fQE4AAzc3AH0AAAAFZAAgAAAAAFMm2feF2fFCm/UC6AfIyepX/xJDSmnnolQIBnHcPmb5BXMAIAAAAABLI11kFrQoaNVZFmq/38aRNImPOjdJh0Lo6irI8M/AaAVjACAAAAAAZFgYh4nV8YN+/AAqe/QhKDkNBPg8KraCG7y3bOzhPV4AAzc4AH0AAAAFZAAgAAAAAJvXB3KyNiNtQko4SSzo/9b2qmM2zU9CQTTDfLSBWMgRBXMAIAAAAAAvjuVP7KsLRDeqVqRziTKpBrjVyqKiIbO9Gw8Wl2wFTAVjACAAAAAARuBn3JdQ8so7Gy0XVH0CtlvtRoJWhSrJP6eRIqbyWh8AAzc5AH0AAAAFZAAgAAAAAPGdcxDiid8z8XYnfdDivNMYVPgBKdGOUw6UStU+48CdBXMAIAAAAAARj6g1Ap0eEfuCZ4X2TsEw+Djrhto3fA5nLwPaY0vCTgVjACAAAAAATxLY6SkcLPaoOBJpQsggEqoxgJgiY9seP3fBQM05PckAAzgwAH0AAAAFZAAgAAAAAP5rGPrYGt3aKob5f/ldP0qrW7bmWvqnKY4QwdDWz400BXMAIAAAAADTQkW2ymaaf/bhteOOGmSrIR97bAnJx+yN3yMj1bTeewVjACAAAAAA6O+wIMt3xMITuCxVrqOCNBPX5F122G+/Is+EYkzUvVYAAzgxAH0AAAAFZAAgAAAAAAlz6wJze5UkIxKpJOZFGCOf3v2KByWyI6NB6JM9wNcBBXMAIAAAAABUC7P/neUIHHoZtq0jFVBHY75tSFYr1Y5S16YN5XxC1QVjACAAAAAANv97jMd9JmZ7lMeCzK7jaZHZYsZJNl6N9g9WXzd2om0AAzgyAH0AAAAFZAAgAAAAAFJ8AtHcjia/9Y5pLEc3qVgH5xKiXw12G9Kn2A1EY8McBXMAIAAAAAAxe7Bdw7eUSBk/oAawa7uicTEDgXLymRNhBy1LAxhDvwVjACAAAAAAtOUZ2NeUWwESpq95O92dhqYBt8RtFnjT43E7k9mQF3oAAzgzAH0AAAAFZAAgAAAAAO8uwQUaKFb6vqR3Sv3Wn4QAonC2exOC9lGG1juqP5DtBXMAIAAAAABZf1KyJgQg8/Rf5c02DgDK2aQu0rNCOvaL60ohDHyY+gVjACAAAAAA5nAWj1Ek8p0MLYZEB3yoVFDfBYZ+/ZpIo71u+W9hprcAAzg0AH0AAAAFZAAgAAAAAE8YtqyRsGCeiR6hhiyisR/hccmK4nZqIMzO4lUBmEFzBXMAIAAAAAC1UYOSKqAeG1UJiKjWFVskRhuFKpj9Ezy+lICZvFlN5AVjACAAAAAA37oP30mTzVftI2FL9Uwyls/jqLqbmRDQk7p7nlx44uYAAzg1AH0AAAAFZAAgAAAAAPhCrMausDx1QUIEqp9rUdRKyM6a9AAx7jQ3ILIu8wNIBXMAIAAAAACmH8lotGCiF2q9VQxhsS+7LAZv79VUAsOUALaGxE/EpAVjACAAAAAAhVIkl8p19VZ0gpg32s++Jda3qsVSVB5tV4CKrtjhE3IAAzg2AH0AAAAFZAAgAAAAAOBi/GAYFcstMSJPgp3VkMiuuUUCrZytvqYaU8dwm8v2BXMAIAAAAACEZSZVyD3pKzGlbdwlYmWQhHHTV5SnNLknl2Gw8IaUTQVjACAAAAAAriBex2kK//RPhyVpCYJDBng4l5w8jD3m8BF7dVAP0p8AAzg3AH0AAAAFZAAgAAAAAIQuup+YGfH3mflzWopN8J1X8o8a0d9CSGIvrA5HOzraBXMAIAAAAADYvNLURXsC2ITMqK14LABQBI+hZZ5wNf24JMcKLW+84AVjACAAAAAAGeUzdG2kQrx5XypXJezZmPVzMYuqYZw7Bhbl4EPT0SMAAzg4AH0AAAAFZAAgAAAAAKsh0ADyOnVocFrOrf6MpTrNvAj8iaiE923DPryu124gBXMAIAAAAADg24a8NVE1GyScc6tmnTbmu5ulzO+896fE92lN08MeswVjACAAAAAAbO+DEBY3STVMQN7CbxmHDUBYrDR+80e797u/VmiK4vcAAzg5AH0AAAAFZAAgAAAAAKkVC2Y6HtRmv72tDnPUSjJBvse7SxLqnr09/Uuj9sVVBXMAIAAAAABYNFUkH7ylPMN+Bc3HWX1e0flGYNbtJNCY9SltJCW/UAVjACAAAAAAHlE3RLKcXpXto7Mr8nRgzEsmhjfGh4vcgPxashCSMg4AAzkwAH0AAAAFZAAgAAAAADDggcwcb/Yn1Kk39sOHsv7BO/MfP3m/AJzjGH506Wf9BXMAIAAAAAAYZIsdjICS0+BDyRUPnrSAZfPrwtuMaEDEn0/ijLNQmAVjACAAAAAA1c/sy3255NofBQrnBNSmVkSzMgsGPaaOUJShddVrnuQAAzkxAH0AAAAFZAAgAAAAAEWY7dEUOJBgjOoWVht1wLehsWAzB3rSOBtLgTuM2HC8BXMAIAAAAAAAoswiHRROurjwUW8u8D5EUT+67yvrgpB/j6PzBDAfVwVjACAAAAAALBvQumG8m7/bzJjGWN2cHSAncdN8jMtOSmEhEhGom24AAzkyAH0AAAAFZAAgAAAAAPZaapeAUUFPA7JTCMOWHJa9lnPFh0/gXfAPjA1ezm4ZBXMAIAAAAACmJvLY2nivw7/b3DOKH/X7bBXjJwoowqb1GtEFO3OYgAVjACAAAAAAGCDHuUJusGHKQ+r9nrFChmUUsRcqZKPGsRiLSk5gbFcAAzkzAH0AAAAFZAAgAAAAAK00u6jadxCZAiA+fTsPVDsnW5p5LCr4+kZZZOTDuZlfBXMAIAAAAAAote4zTEYMDgaaQbAdN8Dzv93ljPLdGjJzvnRn3KXgtQVjACAAAAAApZ6l+OrocOqgFek7WOqOP9JruTWZ+iW+5zdL3DZwzhkAAzk0AH0AAAAFZAAgAAAAAFbgabdyymiEVYYwtJSWa7lfl/oYuj/SukzJeDOR6wPVBXMAIAAAAADAFGFjS1vPbN6mQEhkDYTD6V2V23Ys9gUEUMGNvMPkaAVjACAAAAAAeICKly5Xtwmfd4JFD+5e1UWpu4V3KoRim7jeBICDs+UAAzk1AH0AAAAFZAAgAAAAABNMR6UBv2E627CqLtQ/eDYx7OEwQ7JrR4mSHFa1N8tLBXMAIAAAAAAxH4gucI4UmNVB7625C6hFSVCuIpJO3lusJlPuL8H5EQVjACAAAAAA0/uh1hrAlGGna/njCVaCqBwubxkifzF0zjCDQrIJOtUAAzk2AH0AAAAFZAAgAAAAAG8cd6WBneNunlqrQ2EmNf35W7OGObGq9WL4ePX+LUDmBXMAIAAAAAAjJ2+sX87NSis9hBsgb1QprVRnO7Bf+GObCGoUqyPE4wVjACAAAAAAAoJ4Ufdq45T9Aun5FupHlaBCIUsmUn6dXgV9KorpFikAAzk3AH0AAAAFZAAgAAAAAFWOUGkUpy8yf6gB3dio/aOfRKh7XuhvsUj48iESFJrGBXMAIAAAAAAY7sCDMcrUXvNuL6dO0m11WyijzXZvPIcOKob6IpC4PQVjACAAAAAAO/QFjczoznH95Iu0YEVMsU1GA1yxSGL8bcwSweNzAtkAAzk4AH0AAAAFZAAgAAAAAGUrIdKxOihwNmo6B+aG+Ag1qa0+iqdksHOjQj+Oy9bZBXMAIAAAAABwa5dbI2KmzBDNBTQBEkjZv4sPaeRkRNejcjdVymRFKQVjACAAAAAAUtdJucZoQvvPJiy65RonQrxBCcvtfHslpbgLbtWirCYAAzk5AH0AAAAFZAAgAAAAAOx89xV/hRk64/CkM9N2EMK6aldII0c8smdcsZ46NbP8BXMAIAAAAADBF6tfQ+7q9kTuLyuyrSnDgmrdmrXkdhl980i1KHuGHgVjACAAAAAAVc5njbLX1afpsuG662U/OBo4LanEk06lKbQtd95fswgAAzEwMAB9AAAABWQAIAAAAADJDdC9aEFl4Y8J/awHbnXGHjfP+VXQilPHJg7ewaJI7AVzACAAAAAAE+tqRl6EcBMXvbr4GDiNIYObTsYpa1n6BJk9EjIJVicFYwAgAAAAAITXOaLyGsYLUvOemQXxvnAHBwM/t3sMwfQus2aGoII+AAMxMDEAfQAAAAVkACAAAAAA3F9rjEKhpoHuTULVGgfUsGGwJs3bISrXkFP1v6KoQLgFcwAgAAAAAIBf0tXw96Z/Ds0XSIHX/zk3MzUR/7WZR/J6FpxRWChtBWMAIAAAAABzRshE3O7JzVjSGQSabvFXpen8sGUCAyr8hIIevROrjgADMTAyAH0AAAAFZAAgAAAAAOYIYoWkX7dGuyKfi3XssUlc7u/gWzqrR9KMkikKVdmSBXMAIAAAAABVF2OYjRTGi9Tw8XCAwZWLpX35Yl271TlNWp6N/nROhAVjACAAAAAAyGLbmHhe8eTQC8L2eDRcezUWULLZ9E8DPic7Mfp8/+4AAzEwMwB9AAAABWQAIAAAAACMtPm12YtdEAvqu6Eji1yuRXnu1RJP6h0l7pH3lSH4MwVzACAAAAAAENyCFfyUAh1veQBGx+cxiB7Sasrj41jzCGflZkB5cRMFYwAgAAAAAA92s3DQ3aIVOrRzusqU+xdK7cnQGCDJiyLF+su3F1ZDAAMxMDQAfQAAAAVkACAAAAAAvlI4lDcs6GB1cnm/Tzo014CXWqidCdyE5t2lknWQd4QFcwAgAAAAAD60SpNc4O2KT7J0llKdSpcX1/Xxs97N715a1HsTFkmBBWMAIAAAAABR3y/ZE3VZqBhqfUttX4bzW9wkpDoexfYZIG2fMwftcQADMTA1AH0AAAAFZAAgAAAAAKl8zcHJRDjSjJeV/WvMxulW1zrTFtaeBy/aKKhadc6UBXMAIAAAAADBdWQl5SBIvtZZLIHszePwkO14W1mQ0izUk2Ov21cPNAVjACAAAAAAHvQLJ2X4ncjVv1BsOd/jbVouRPwf4222WlGSzPyAfnsAAzEwNgB9AAAABWQAIAAAAABb6LXDWqCp1beQgQjj8I3sRTtFhlrmiBi+h/+ikmrvugVzACAAAAAA9stpgTecT7uTyaGNs3K9Bp0A7R0QaIAOfscyMXHBPX8FYwAgAAAAANTp7TwMatTSeLH3GvsrB3IOvJo+0B8HRGUFcRzJVesdAAMxMDcAfQAAAAVkACAAAAAA97NTT+81PhDhgptNtp4epzA0tP4iNb9j1AWkiiiKGM8FcwAgAAAAAKPbHg7ise16vxmdPCzksA/2Mn/qST0L9Xe8vnQugVkcBWMAIAAAAACVl3ds8xRdGFPAtZ+WtC4ojC1q5OzB6dSJoOLaJsdhhgADMTA4AH0AAAAFZAAgAAAAAMqpayM2XotEFmm0gwQd9rIzApy0X+7HfOhNk6VU7F5lBXMAIAAAAACJR9+q5T9qFHXFNgGbZnPubG8rkO6cwWhzITQTmd6VgwVjACAAAAAAEEOCUo+ihRGl1kuHlabWBWUFyJPAXSXzpQB4od57cMEAAzEwOQB9AAAABWQAIAAAAADQnslvt6Hm2kJPmqsTVYQHE/wWeZ4bE1XSkt7TKy0r1gVzACAAAAAA8URTA4ZMrhHPvlp53TH6FDCzS+0+61qHm5XK6UiOrKEFYwAgAAAAANSmECEsZgXBpoAzAIwaU3H0K/6HIutCn+ELRGBFzykkAAMxMTAAfQAAAAVkACAAAAAAhujlgFPFczsdCGXtQ/002Ck8YWQHHzvWvUHrkbjv4rwFcwAgAAAAALbV0lLGcSGfE7mDM3n/fgEvi+ifjl7WZ5b3aqjDNvx9BWMAIAAAAACKzGyyRT/3KSmGlKMHpswxj7pNu3rCmOETZ2DUhQsCBgADMTExAH0AAAAFZAAgAAAAABm/6pF96j26Jm7z5KkY1y33zcAEXLx2n0DwC03bs/ixBXMAIAAAAAD01OMvTZI/mqMgxIhA5nLs068mW+GKl3OW3ilf2D8+LgVjACAAAAAAnPa8PTfcaSSAdzNAC54IMTicbShbtt/cSnFHz7u7g8wAAzExMgB9AAAABWQAIAAAAACfw9/te4GkHZAapC9sDMHHHZgmlTrccyJDPFciOMSOcwVzACAAAAAAIIC1ZpHObvmMwUfqDRPl4C1aeuHwujM1G/yJbvybMNAFYwAgAAAAAKeagivKdHk7wAFEPHyDhsm9mDNWH5UPB+oIIlmfbzSOAAMxMTMAfQAAAAVkACAAAAAAkxHJRbnShpPOylLoDdNShfILeA1hChKFQY9qQyZ5VmsFcwAgAAAAAKidrY+rC3hTY+YWu2a7fuMH2RD/XaiTIBW1hrxNCQOJBWMAIAAAAADH8s64PC+lo8Ul4oujBlb/irtJSwu12V0HbvNzj/9qUQADMTE0AH0AAAAFZAAgAAAAAByuYl8dBvfaZ0LO/81JW4hYypeNmvLMaxsIdvqMPrWoBXMAIAAAAABNddwobOUJzm9HOUD8BMZJqkNCUCqstHZkC76FIdNg9AVjACAAAAAA4SEPY+qEZ2dCu/9485IEVybiM3Z7szXYM+izxklNm14AAzExNQB9AAAABWQAIAAAAABxMy7X5hf7AXGDz3Y/POu1ZpkMlNcSvSP92NOO/Gs7wAVzACAAAAAAHJshWo2T5wU2zvqCyJzcJQKQaHFHpCpMc9oWBXkpUPoFYwAgAAAAANeYm2aAT9f6T9uwQyH4sw2DUQyKTpo/CHt+Bb67ao4TAAMxMTYAfQAAAAVkACAAAAAAlqbslixl7Zw3bRlibZbe/WmKw23k8uKeIzPKYEtbIy0FcwAgAAAAAHEKwpUxkxOfef5HYvulXPmdbzTivwdwrSYIHDeNRcpcBWMAIAAAAAASUKCgxGu2U42qfKiyMimLPiZBMurf5v8lQJUSrhAWIAADMTE3AH0AAAAFZAAgAAAAAMXrXx0saZ+5gORmwM2FLuZG6iuO2YS+1IGPoAtDKoKBBXMAIAAAAADIQsxCr8CfFKaBcx8kIeSywnGh7JHjKRJ9vJd9x79y7wVjACAAAAAAPzOTJ088k9QdGUpZ1ubUdkw3pTGkNF8bui4a9wqeo08AAzExOAB9AAAABWQAIAAAAAAm83FA9yDUpwkbKTihe7m53u+DivS9BU2b4vQMtCVQ2AVzACAAAAAAz3m1UB/AbZPa4QSKFDnUgHaT78+6iGOFAtouiBorEgEFYwAgAAAAAHuM0gIDED47c9rdQ4MVtVzpVfn5zTF7eokO4lew4EM+AAMxMTkAfQAAAAVkACAAAAAAJRzYK0PUwr9RPG2/7yID0WgcTJPB2Xjccp5LAPDYunkFcwAgAAAAAIIh24h3DrltAzNFhF+MEmPrZtzr1PhCofhChZqfCW+jBWMAIAAAAAD6LppOeSiLHFI7EOBAz9pZPKU7LWbRWIINTlqw4zmJ5gADMTIwAH0AAAAFZAAgAAAAAHSaHWs/dnmI9sc7nB50VB2Bzs0kHapMHCQdyVEYY30TBXMAIAAAAACkV22lhEjWv/9/DubfHBAcwJggKI5mIbSK5L2nyqloqQVjACAAAAAA1I5pLbnlFf/EfJlhE0RFIioDjzKrWNh4TO8H/ksrbMcAAzEyMQB9AAAABWQAIAAAAAAC/I4TQRtCl12YZmdGz17X4GqSQgfwCPgRBwdHmdwu+QVzACAAAAAAx8f3z2ut/RAZhleari4vCEE+tNIn4ikjoUwzitfQ588FYwAgAAAAADX1qVlsvYru6YDBMtmlQoIBhLSbVykBtft8wn/A+ohJAAMxMjIAfQAAAAVkACAAAAAADGB5yU2XT0fse/MPWgvBvZikVxrl5pf3S5K1hceKWooFcwAgAAAAAIxTmlLHMjNaVDEfJbXvRez0SEPWFREBJCT6qTHsrljoBWMAIAAAAAD+RiUk9IWfaDWXtzLp2NX/vCCeU9amFnQgpJjy56dQXQADMTIzAH0AAAAFZAAgAAAAABmO7QD9vxWMmFjIHz13lyOeV6vHT6mYCsWxF7hb/yOjBXMAIAAAAACT9lmgkiqzuWG24afuzYiCeK9gmJqacmxAruIukd0xEAVjACAAAAAAyiZxtuOZQNSit4wFMJS3jnqLbDzyJ0OtDTKs6r0EO0cAAzEyNAB9AAAABWQAIAAAAAAfPUoy7QyZKhIIURso+mkP9qr1izbjETqF5s22GwjCjAVzACAAAAAAvLMsIDQ/go4VUxeh50UHmsvMvfx51cwyONnRD2odvC0FYwAgAAAAAMwY/KvSIOLoSHOq4TQ1V9ZSCPZc34SdPooPL4gYXa7UAAMxMjUAfQAAAAVkACAAAAAAE3ZqUar0Bq2zWbARE0bAv98jBlK9UJ73/xcwdMWWlSkFcwAgAAAAAK4M+MmC+9sFiFsumMyJZQKxWmmJiuG9H7IzKw083xxkBWMAIAAAAACpywsi9vc979ApgH3BDzph7Y4VIAkQhji97sswzdLAYwADMTI2AH0AAAAFZAAgAAAAAMo8FJiOq63cAmyk2O7eI7GcbQh/1j4RrMTqly3rexftBXMAIAAAAADjVmpd0WiRGTw/gAqEgGolt2EI7Csv14vKdmYoMD0aAgVjACAAAAAAEPmYjDZnMZdkaYlLZLlRgist9kCrSQe0mSuKxPwwYbQAAzEyNwB9AAAABWQAIAAAAACu5IGaIx7A3Jvly/kzlCsSA4s3iJwuIl8jEdRH0k93NwVzACAAAAAA9NRUyxYE+t0Xyosyt6vIfMFW/vBoYg6sR+jBNs4JAxIFYwAgAAAAAKMbsr4JCCodHmkBvfhOe7BJigKiJik95wLeu3PjLSpXAAMxMjgAfQAAAAVkACAAAAAALkRy0GJInXYLA+cgjs6Myb0a+Gu9hgXhHvhLNoGWfckFcwAgAAAAANbALyt9zCSvwnLaWCd2/y2eoB7qkWTvv1Ldu8r40JPuBWMAIAAAAABi0dME7AUSoeMv4yIgnGMgrX9G+Uk6HV+I9/9zzABcZwADMTI5AH0AAAAFZAAgAAAAAGoUK/DSWhT8LZhszSUqDbTrp8cSA7rdqmADKL+MILtTBXMAIAAAAABHnEE9bVa6lvhfhEMkkV2kzSSxH/sMW/FIJuw3CzWs6wVjACAAAAAAagOaxIb7co+HxAXhfI9ahHLUNqmGuN/4JxeR9sm6PdkAAzEzMAB9AAAABWQAIAAAAAC922ZDQE3h2fQKibGMZ9hV0WNlmrPYYSdtaSyYxsWYqgVzACAAAAAAagMovciKK6WVjIc2cCj8nK5O/gVOFFVeVAJpRp89tmQFYwAgAAAAAAMsWoODZkTvKSNWzloogbp/AiFFspENp49RwKjWpMLHAAMxMzEAfQAAAAVkACAAAAAAtK+JqX3K/z2txjAU15DgX4y90DS2YLfIJFolCOkJJJwFcwAgAAAAAMnR5V7gfX7MNqqUdL5AkWlkhyFXaBRVNej+Rcn8lrQkBWMAIAAAAADOmDHuXwsu9uwKYYSN5XIb/TG3K5DB5aci/X1ATKTaSwADMTMyAH0AAAAFZAAgAAAAAAeGrIMK/bac6kPczxbvRYqKMkcpeI2FjdMpD91FDWIvBXMAIAAAAAAix62z1LeS8yvSXCl5gHSIomjyx76fF3S1lp9k900hygVjACAAAAAAUK1Gzlm0meDhYPL7ZgKaCD0qz7Zb5rtnPn1EEW3UZxoAAzEzMwB9AAAABWQAIAAAAACphf298InM0Us4HT8o1W1MGw0D/02vd7Jh+U0h7qaFaQVzACAAAAAAFXtk7YpqsOJxsqGWSIL+YcBE96G3Zz9D31gPqDW94y8FYwAgAAAAAEA5mnfyccI1SjyYftq3qCuORs/NxzzqUA+fxn3FRjVsAAMxMzQAfQAAAAVkACAAAAAATLDS2cuDVM3yDMuWNgk2iGKBTzPpfJMbvxVOSY39ZfcFcwAgAAAAAPT5wRi2cLHIUflXzm6EQB/m7xdThP80ir1VV/JBBqvxBWMAIAAAAABTj7i+CxxNVkbnAkSYga/v2X3/NH3nD/FRiOo2FPkzvAADMTM1AH0AAAAFZAAgAAAAAJNjExiZVX7jfFGfYpQu16qxLN0YPqVU/5CQ/Y67YSinBXMAIAAAAABMpm2+6KrkRUlXzQoMPHrQmIO6dkQz66tYdfTeA3dKqQVjACAAAAAATkNGECGUFzo34ItVnTEmxbe8dbde8QzMgEEdjMhRLz8AAzEzNgB9AAAABWQAIAAAAABlFkYtLCx901X6QVVMkSn6Z7k30UF4xHaA0OZJJ9bdyQVzACAAAAAATez+F9GHcGzTp7jjv4feboUNb8JCkIp4EqcPFisnq7MFYwAgAAAAAOHfn3eVRhSWn61AfZuAnCGleSLluJ3sF9IFGUWrCsBaAAMxMzcAfQAAAAVkACAAAAAA8NJKN0IxZnruhswGQkiruv8Ih0EMwDcSZx/Xasup9dkFcwAgAAAAAKaJZRxzA+Igeydvuk6cSwUHXcrmT4PjhuPu//FslpdnBWMAIAAAAAC3Cs/mKg+sbU/BC9QWFglOau6FWAP1sJqqSZOEOdzh+gADMTM4AH0AAAAFZAAgAAAAABHxHdEClz7hbSSgE58+dWLlSMJnoPz+jFxp4bB1GmLQBXMAIAAAAAD3nSvT6aGD+A110J/NwEfp0nPutlmuB5B+wA3CC3noGAVjACAAAAAA56lpvjMjFzR6NMGiSF1es1X01dwINWqaxyq3UUL8XJ0AAzEzOQB9AAAABWQAIAAAAAC/o8qW/ifk3KuJ01VFkyNLgQafxB5/bGs2G5VyyVafOwVzACAAAAAA1bMqAFGDHSl6BYNLbxApvkAv2K1/oafywiX0MDz1dGUFYwAgAAAAAMjJrrd6E5DyUuXkWZsAqo020Qvom6xqAluQLod5SmtdAAMxNDAAfQAAAAVkACAAAAAAY2V8I1bz3a1AxTtmED6UhdhA09huFkuuEX8R+d/WDPUFcwAgAAAAAPTVoNRiI76tcRKqd+JBBVyy4+YcKST42p0QX2BtmQ2VBWMAIAAAAAD0kQXxOWWOfIzqhJk5Rnvp/h5VXcGxxLZ8HMvVVH++YQADMTQxAH0AAAAFZAAgAAAAAO2kSsW0WGN9AOtK4xK2SHrGhWiaAbMEKT4iZkRpaDN/BXMAIAAAAABKGzQcPM8LT2dwOggxoWjv/1imYWabbG/G4kBw8OWaxAVjACAAAAAAT3sD7PTrAPtOtiSRrG4IKmSNr/uWgX8TvIyZpZzj4wkAAzE0MgB9AAAABWQAIAAAAAAiCwzNEEaH/mDam68IdDftnhthyUFdb+ZCNSBQ91WlHQVzACAAAAAA7tHyHcxCzmbJeFYZyPm4mEgkTGKOvwY4MX82OvH0Jn8FYwAgAAAAAP4A3nd470CwvaGzyJLfzbrw7ePJV35V3Cw6tuiPFlsCAAMxNDMAfQAAAAVkACAAAAAAGuCHVNJSuoVkpPOnS5s89GuA+BLi2IPBUr2Bg1sWEPIFcwAgAAAAAEl1gncS5/xO7bQ/KQSstRV3rOT2SW6nV92ZANeG2SR6BWMAIAAAAADi6/zM3xSBXEfpRpvEdeycRDl08SmwwLey72qgZaY7FwADMTQ0AH0AAAAFZAAgAAAAAMfxz7gEaoCdPvXrubDhCZUS0ARLZc1svgbXgMDlVBPgBXMAIAAAAAB6a5dDA3fuT5Vz2KvAcbUEFX/+B7Nw2p1QqbPoQ5TTuAVjACAAAAAAB4Kp5ewWpFMJ2T+QCrfw2eJV70L4M+GM8khV1JwqOqQAAzE0NQB9AAAABWQAIAAAAACnYsqF/VzmjIImC9+dqrHO1TM6lJ6fRwM0mM6Wf6paOwVzACAAAAAA5tgZzch8uDCR1ky3SllVaKVpxAlbrhvlNDTazZZRZOAFYwAgAAAAALDJDWkmExE4L8hf2CDNLuF+dgivUGXb7ZvBp90EALm+AAMxNDYAfQAAAAVkACAAAAAAEC0sIVmadtW4YMuRXH7RpAhXclsd+3bmqGXCMeaT014FcwAgAAAAABPpXh0uzpsJJB+IRUNajmMB9WGwswfpw5T9xk3Xj6ANBWMAIAAAAABauth7HsI90juToIGY6149TbrxP8Emoa+5Izilj9zeWQADMTQ3AH0AAAAFZAAgAAAAABaIB3n49Xm9cOafSrQsE0WCcYp8rMIO/qVwIlMF5YLRBXMAIAAAAAC9EyWJV3xOu9bzgdJ/yX+ko7qLf1u3AxNMataW2C9EzQVjACAAAAAAy7IAYSgbqP7CyvurHMoEq1eO08TIPNO/XcS1L5RKXHkAAzE0OAB9AAAABWQAIAAAAAA5rZItA/cocRnngYqcJ3nBXQ+l688aKz3EQyLbYYunPAVzACAAAAAAwKyA+L7TgxztPClLrIMk2JXR+w7c04N3ZOqPgjvrIvsFYwAgAAAAABaAwqPDgkTaz4888/3PeF64+vtHbLtAZgFgEurWT5kbAAMxNDkAfQAAAAVkACAAAAAA3htn7oHJ0YYpIrs+Mzyh85Ys67HwAdv5LQl1mCdoMWkFcwAgAAAAAEHjCtNNLenHuSIYux6ezAHsXDaj2DlTF67ToDhDDe6HBWMAIAAAAAAVwUZIMifuPtmiFSEctLt+bOFo1T4fgGiWNsZAP0ddeQADMTUwAH0AAAAFZAAgAAAAAEnt18Km/nqggfIJWxzTr9r3hnXNaueG6XO9A5G11LnGBXMAIAAAAAD7QxzGMN/ard5TfFLecE6uusMmXG2+RBsBR+/NCQHUwAVjACAAAAAAIPTDniUskKj5mEzULdlKtIDquwjcqkwunJyhmBazNNcAAzE1MQB9AAAABWQAIAAAAAAVAKK/GoY8AACu/hyMpO4hdLq6JnEyWNzkyci9sbaD/wVzACAAAAAA2HmeqpMlvvBpV2zQTYIRmsc4MFlfHRwLof0ycJgMg/MFYwAgAAAAACHHCAszhxijRN9Es+XRQxXo7JQ6g2qM6f5sIdMy0VKbAAMxNTIAfQAAAAVkACAAAAAAhHyq1GQC/GiMwpYjcsfkNxolJ10ARKjIjfkW1Wipzi0FcwAgAAAAAD/uaGWxTDq87F8XZ6CrFI+RNa8yMqfSZdqK00Kj833BBWMAIAAAAAD1mJD7i29l2xloVHwS1n0CbkyodWeFfZM1KF1r4HqNIgADMTUzAH0AAAAFZAAgAAAAABAJJxHoZD+MQBWqm9UM9Dd3z5ZohIZGWRaRVRsMptKQBXMAIAAAAADrE/ca+gqj/SH4oao4wE4qn2ovoTydzcMbDbrfnUs3zAVjACAAAAAAAIUpF9mApVCMCckE7S+K9RL6TPtlTTYuzUsj9E6oIpUAAzE1NAB9AAAABWQAIAAAAAAv01wz7VG9mTepjXQi6Zma+7b/OVBaKVkWNbgDLr1mFgVzACAAAAAA0I5sxz8r6wkCp5Tgvr+iL4p6MxSOq5d3e1kZG+0b7NkFYwAgAAAAAKwOaUTAOw6Y1F5vFrbDf8yVua5Fm6hKhkdIrzejuaatAAMxNTUAfQAAAAVkACAAAAAAWXecRwxSon68xaa9THXnRDw5ZfzARKnvvjTjtbae6T0FcwAgAAAAAPh0UfUMEo7eILCMv2tiJQe1bF9qtXq7GJtC6H5Va4fIBWMAIAAAAAASanx14WR2rmomuZ2x/nuD8j75pYzz+ZAt/v7uRcoOEQADMTU2AH0AAAAFZAAgAAAAAEY5WL8/LpX36iAB1wlQrMO/xHVjoO9BePVzbUlBYo+bBXMAIAAAAABoKcpadDXUARedDvTmzUzWPe1jTuvD0z9oIcZmKuiSXwVjACAAAAAAWhdUWP186dYAIDx6RtC0o/lzwYNvYBXm4RWFMbcU3YkAAzE1NwB9AAAABWQAIAAAAADnJnWqsfx0xqNnqfFGCxIplVu8mXjaHTViJT9+y2RuTgVzACAAAAAAWAaSCwIXDwdYxWf2NZTly/iKVfG/KDjHUcA1BokN5sMFYwAgAAAAAPS3GDNsHgCuTgEWTHiEStoY0VMzlopZ8L7wPUquK7ayAAMxNTgAfQAAAAVkACAAAAAAsPG7LaIpJvcwqcbtfFUpIjj+vpNj70Zjaw3eV9T+QYsFcwAgAAAAAJQ71zi0NlCyY8ZQs3IasJ4gB1PmWx57HpnlCf3+hmhqBWMAIAAAAABmDfQAQeyeutCB+wSn4RmErwYEkYpyBeCYA+3iutFzAQADMTU5AH0AAAAFZAAgAAAAAAGiSqKaQDakMi1W87rFAhkogfRAevnwQ41onWNUJKtuBXMAIAAAAAASgiDpXfGh7E47KkOD8MAcX8+BnDShlnU5JAGdnPdqOAVjACAAAAAAucFM6KF1YHEhH9vn16ox+VSqNEykMGaJzhLPsLI7qk8AAzE2MAB9AAAABWQAIAAAAAB7L4EnhjKA5xJD3ORhH2wOA1BvpnQ+7IjRYi+jjVEaJAVzACAAAAAAuhBIm0nL3FJnVJId+7CKDASEo+l2E89Z9/5aWSITK4AFYwAgAAAAALxpb5qDek038/wz4E//KIrwwo0voaqwlB7cTXSQ44EHAAMxNjEAfQAAAAVkACAAAAAAaROn1NaDZFOGEWw724dsXBAm6bgmL5i0cki6QZQNrOoFcwAgAAAAANVT8R6UvhrAlyqYlxtmnvkR4uYK/hlvyQmBu/LP6/3ZBWMAIAAAAAC1ONheOA57vNof7iZM3b82xk1abqT0bppzjQnHCmnUIwADMTYyAH0AAAAFZAAgAAAAADqSR5e0/Th59LrauDA7OnGD1Xr3H3NokfVxzDWOFaN7BXMAIAAAAACt30faNwTWRbvmykDpiDYUOCwA6QDbBBYBFWS7rdOB4AVjACAAAAAAQZlvU/gzOwW2rcr5+W43Q6EkfX8oX1TpRJWEAZAULx4AAzE2MwB9AAAABWQAIAAAAADmtb1ZgpZjSeodPG/hIVlsnS8hoRRwRbrTVx89VwL62AVzACAAAAAAi38e1g6sEyVfSDkzZbaZXGxKI/zKNbMasOl2LYoWrq8FYwAgAAAAAFWkHkxPA28FjeUjeRILC0tX8OJcanhPP68QxM5s1kcMAAMxNjQAfQAAAAVkACAAAAAAf/f7AWVgBxoKjr7YsEQ4w/fqSvuQWV2HMiA3rQ7ur0sFcwAgAAAAADkkeJozP6FFhUdRIN74H4UhIHue+eVbOs1NvbdWYFQrBWMAIAAAAADS3qKdAEzPtGMFpsisEhnOfF0zfxmLXjHKKaNCzX4PhQADMTY1AH0AAAAFZAAgAAAAAI2WEOymtuFpdKi4ctanPLnlQud+yMKKb8p/nfKmIy56BXMAIAAAAADVKrJmhjr1rfF3p+T+tl7UFd1B7+BfJRk0e7a4im7ozgVjACAAAAAAxeqPjcgDwae+AgEXg3hU118dHoy6rcHd6jAJrGik+EcAAzE2NgB9AAAABWQAIAAAAAAuHU9Qd79hjyvKOujGanSGDIQlxzsql8JytTZhEnPw+AVzACAAAAAAjF2gV/4+sOHVgDd/oR5wDi9zL7NGpGD+NsEpGXy/a4QFYwAgAAAAAIFYquIAyUn3D2peptolq3s8oIgSLEIctqBYx6qEeQTxAAMxNjcAfQAAAAVkACAAAAAA7dQDkt3iyWYCT94d7yqUtPPwp4qkC0ddu+HFdHgVKEkFcwAgAAAAANuYvtvZBTEq4Rm9+5eb7VuFopowkrAuv86PGP8Q8/QvBWMAIAAAAABBO8poBKW/RWjdHBARy3Rzghz3frQIZEPE/nSKWYlVPgADMTY4AH0AAAAFZAAgAAAAAAwnZSDhL4tNGYxlHPhKYB8s28dY5ScSwiKZm3UhT8U3BXMAIAAAAABDoY6dhivufTURQExyC9Gx3ocpl09bgbbQLChj3qVGbgVjACAAAAAA4JWOs1XAUgX3rGBBGEDDIC9/i9khJ+FgIcwfvj/SnS0AAzE2OQB9AAAABWQAIAAAAAC2VuRdaC4ZJmLdNOvD6R2tnvkyARteqXouJmI46V306QVzACAAAAAAMn1Z6B35wFTX9mEYAPM+IiJ5hauEwfD0CyIvBrxHg7IFYwAgAAAAAMRQnV65wbGlT+7Wj/HvGPLKg8NBnhkQ8Hx/MIyWvynTAAMxNzAAfQAAAAVkACAAAAAAVerb7qVNy457rNOHOgDSKyWl5ojun7iWrv1uHPXrIZQFcwAgAAAAAIDcYS9j5z+gx0xdJj09L7876r/vjvKTi/d3bXDE3PhyBWMAIAAAAAANDuaZsI2eS6/qiSdG9pCLjcPdKS96xWUJr84V/1La7gADMTcxAH0AAAAFZAAgAAAAAH/E44yLxKCJjuSmU9A8SEhbmkDOx1PqqtYcZtgOzJdrBXMAIAAAAABgLh9v2HjBbogrRoQ82LS6KjZQnzjxyJH4PH+F3jupSAVjACAAAAAAd93miIlZ/uuhzjVBt0BPjzeewxih6TSzkzbsNhpMjA8AAzE3MgB9AAAABWQAIAAAAAAlNUdDL+f/SSQ5074mrq0JNh7CTXwTbbhsQyDwWeDVMwVzACAAAAAANIH2IlSNG0kUw4qz0budjcWn8mNR9cJlYUqPYdonucAFYwAgAAAAAF9OccClBu1j8BVMDmMtJyCxnhWCbg9FuY/8nhuFGJknAAMxNzMAfQAAAAVkACAAAAAASZ+CvUDtlk/R4HAQ3a+PHrKeY/8ifAfh0oXYFqliu80FcwAgAAAAAJelpzPgM65OZFt/mvGGpwibclQ49wH+1gbUGzd9OindBWMAIAAAAAC63uwNXVbd1VP5If++NprVRsHCbomU9Iq3GxCttNAweAADMTc0AH0AAAAFZAAgAAAAAN9fkoUVbvFV2vMNMAkak4gYfEnzwKI3eDM3pnDK5q3lBXMAIAAAAACnDkgVNVNUlbQ9RhR6Aot2nVy+U4km6+GHPkLr631jEAVjACAAAAAA5d2REAVBZI9rUfeqJ9jhePmRXvrGolUbMTFiC78k/OIAAzE3NQB9AAAABWQAIAAAAAASyK3b1nmNCMptVEGOjwoxYLLS9fYWm/Zxilqea0jpEQVzACAAAAAADDHsGrbqlKGEpxlvfyqOJKQJjwJrzsrB7k3HG0AUJbkFYwAgAAAAACZHzWGY3PEL4uPSKPezGGJkyScYw/Vt3bKmX5inVMaOAAMxNzYAfQAAAAVkACAAAAAAlSP9iK31GlcG9MKGbLmq+VXMslURr+As736rrVNXcsUFcwAgAAAAAAvbj0zfq9zzi8XReheKFbCB+h9IsOLgXPPpI5vrEJNZBWMAIAAAAABb9Y2Ox3MeHCfGnMxpR7nk6hwIJg0J8/tT8fZHwxMCBQADMTc3AH0AAAAFZAAgAAAAAOJNORH8Bev97gVU7y6bznOxJ+E6Qoykur1QP76hG1/7BXMAIAAAAAC+C1PtOOrSZgzBAGhr+dPe/kR0JUw9GTwLVNr61xC1aAVjACAAAAAA4rDk/UlRHudzR12i5Ivcb5Pxwn63JX2Sjf2xrlALL7kAAzE3OAB9AAAABWQAIAAAAAAKcHzLUomavInN6upPkyWhAqYQACP/vdVCIYpiy6U6HgVzACAAAAAATsR4KItY6R2+U7Gg6sJdaEcf58gjd1OulyWovIqfxKcFYwAgAAAAAEac2NX3v0n87D/eUM/a3Gj+5Axs896SCidMyOMaOz6zAAMxNzkAfQAAAAVkACAAAAAAqTOLiMpCdR59tLZzzIPqJvbCNvz2XQL9ust0qYaehtcFcwAgAAAAAArefox/3k5xGOeiw2m6NUdzuGxmPwcu5IFcj+jMwHgHBWMAIAAAAABK08kOnYjDWhLJ70iVhd3XEj0Q0srEQttJTawBm6GuOwADMTgwAH0AAAAFZAAgAAAAAIPSqSeVzSRgNVNmrPYHmUMgykCY27NbdDUNhE5kx/SgBXMAIAAAAAAhX90nNfxyXmZe/+btZ7q6xMX4PFyj0paM1ccJ/5IUUQVjACAAAAAA/9XTbq8UVKJbIeYtfNg8pdDrpDqbCwVr5Rq3Tb4dFwwAAzE4MQB9AAAABWQAIAAAAACLn5DxiqAosHGXIAY96FwFKjeqrzXWf3VJIQMwx1fl4gVzACAAAAAAindvU27nveutopdvuHmzdENBbeGFtI3Qcsr07jxmvm8FYwAgAAAAAJ3GMHBeIaCyFV5zBgcUMpjfIDcdoaTYQSyQN2shVJMWAAMxODIAfQAAAAVkACAAAAAA7i2S6rHRSPBwZEn59yxaS7HiYBOmObIkeyCcFU42kf8FcwAgAAAAAGb3RSEyBmgarkTvyLWtOLJcPwCKbCRkESG4RZjVmY4iBWMAIAAAAABKIdcHRSJ3dnevw5Pj0BfULPqmDOA7KBDREMGv5rXUEAADMTgzAH0AAAAFZAAgAAAAAFz+M+H/Z94mdPW5oP51B4HWptp1rxcMWAjnlHvWJDWrBXMAIAAAAACBFEOQyL7ZHu4Cq33QvXkmKuH5ibG/Md3RaED9CtG5HwVjACAAAAAABf2EvIMumZohp2E/sHWEJW4VMU3ez95hwWU9pjKGRCYAAzE4NAB9AAAABWQAIAAAAAAE7c2x3Z3aM1XGfLNk/XQ9jCazNRbGhVm7H8c2NjS5ywVzACAAAAAARJ9h8fdcwA19velF3L/Wcvi2rCzewlKZ2nA0p8bT9uwFYwAgAAAAAPl1vH6JaJGZSJ8au0NK58X4gqleryub8slUhA0HRtzmAAMxODUAfQAAAAVkACAAAAAAVoRt9B9dNVvIMGN+ea5TzRzQC+lqSZ8dd/170zU5o9cFcwAgAAAAAEwM95XZin5mv2yhCI8+ugtKuvRVmNgzzIQN0yi1+9aIBWMAIAAAAAA+rgph1avVrhyHl/6G+HtASJSiJkv7u2UPYj+25fwtLAADMTg2AH0AAAAFZAAgAAAAAKRDkjyWv/etlYT4GyoXrmBED2FgZHnhc+l9Wsl06cH2BXMAIAAAAABohlpm3K850Vndf3NmNE0hHqDlNbSR8/IvMidQ3LnIZAVjACAAAAAAKw6WqWDrI7ZfyndtrftL3uh7BaF/FQxhkh8b0Zl547gAAzE4NwB9AAAABWQAIAAAAAC3DFh5oklLCNLY90bgWm68dFXz65JpAZSp1K99MBTPAQVzACAAAAAAQgZecmxEUZVHoptEQClDwAf8smI3WynQ/i+JBP0g+kQFYwAgAAAAADmUpVNJUwrydIllFvI/aau1703nk6tF2sfzkgFlWsKCAAMxODgAfQAAAAVkACAAAAAAQ5WKvWSB503qeNlOI2Tpjd5blheNr6OBO8pfJfPNstcFcwAgAAAAAKwHgQLSDJ5NwLBQbY5OnblQIsVDpGV7q3RCbFLD1U4/BWMAIAAAAADKOab9GTEiaybo9o4J72Pb9LiQU/CuKoQm6Jtlmo/IeQADMTg5AH0AAAAFZAAgAAAAAGfhFY3RGRm5ZgWRQef1tXxHBq5Y6fXaLAR4yJhrTBplBXMAIAAAAACKEF0ApLoB6lP2UqTFsTQYNc9OdDrs/vziPGzttGVLKQVjACAAAAAAvJcnqBNCZgHe+Q7Cqk+yiAaIuSJIRItXEsAlKy9PES4AAzE5MAB9AAAABWQAIAAAAAAIM73gPcgzgotYHLeMa2zAU4mFsr7CbILUZWfnuKSwagVzACAAAAAAJCSu98uV8xv88f2BIOWzt6p+6EjQStMBdkGPUkgN79cFYwAgAAAAABQqDP10DcaqEY7vxFS7U/9xcadDASONow1+xmVsPZ11AAMxOTEAfQAAAAVkACAAAAAAkWmHCUsiMy1pwZTHxVPBzPTrWFBUDqHNrVqcyyt7nO8FcwAgAAAAAMv2CebFRG/br7USELR98sIdgE9OQCRBGV5JZCO+uPMgBWMAIAAAAABxLEFq6nFVI3WaYhggo1d6oeDsBSm4wCW0T6D0yE/puwADMTkyAH0AAAAFZAAgAAAAAGInUYv0lP/rK7McM8taEHXRefk8Q2AunrvWqdfSV7UaBXMAIAAAAACE+WPxJ3gan7iRTbIxXXx+bKVcaf8kP4JD8DcwU0aL7wVjACAAAAAAiThzC5jAZ5D6xuCIsUD1aLy7KxmT2Fb8ybTkLxvj17MAAzE5MwB9AAAABWQAIAAAAACmHlg2ud3cplXlTsNTpvNnY6Qm1Fce0m899COamoDjaQVzACAAAAAArtJQeJIlepBWRU2aYar7+YGYVQ7dfDc1oxgTmA8r9q0FYwAgAAAAADZpcxtBFv18EdmhxsoxlDs253tNB5zqVY/h+nFD1isIAAMxOTQAfQAAAAVkACAAAAAApzcWSAbZWV/Rq+ylRNqqlJqNVR4fhXrz4633/MQOQgcFcwAgAAAAAN/jz/bsEleiuCl+li83EWlG6UMHA8CyaOMRKCkXkSCPBWMAIAAAAADSTHgALJugYsjIvSK1o0UwG6VizNokXQ/tnPNjOpGB1AAABWUAIAAAAADrmnP3kS2GpCl+gdL2da90KHTkBX46iQ/sZRoj7uPz7BJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", + "base64": "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", "subType": "06" } } @@ -92,7 +103,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml similarity index 63% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Update.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml index 99ed254d94..d4981ca375 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Decimal-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml @@ -1,13 +1,16 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. topology: [ "replicaset" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. Update." @@ -68,7 +71,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -79,59 +86,45 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command_name: update command: - { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDecimal": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDecimal": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" } } - }, - "u": { - "$set": { - "encryptedDecimal": { $$type: "binData" } - } } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": *encrypted_fields }, - "deleteTokens": { - "default.default": { - "encryptedDecimal": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } + "u": { + "$set": { + "encryptedDecimal": { $$type: "binData" } } } - }, - "$db": "default" - } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Aggregate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml index 26f7994753..98defcd9ed 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DecimalPrecision. Aggregate." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedDecimalPrecision": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -97,7 +108,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: aggregate outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml similarity index 96% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Correctness.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml index 9e2ee80aed..aa7ef7525d 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml @@ -1,14 +1,17 @@ # Test correctness results. # Does not include command monitoring expectations or outcome assertions to make tests more readable. -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml similarity index 70% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Delete.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml index 5603f6d34c..446745fdc8 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DecimalPrecision. Delete." @@ -64,7 +67,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -75,7 +82,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -86,7 +97,7 @@ tests: "encryptedDecimalPrecision": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -95,28 +106,15 @@ tests: "limit": 1 } ] + ordered: true encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDecimalPrecision": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: delete outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml similarity index 76% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-FindOneAndUpdate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml index c79f5b9b7f..f90616a88a 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DecimalPrecision. FindOneAndUpdate." @@ -65,7 +68,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -76,7 +83,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedDecimalPrecision": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -95,25 +106,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDecimalPrecision": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: findAndModify outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-InsertFind.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml index 2bdd29cdb3..e8e76cc95f 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DecimalPrecision. Insert and Find." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -83,7 +94,7 @@ tests: "encryptedDecimalPrecision": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -91,7 +102,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml similarity index 65% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Update.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml index e852a30c61..87dfe70770 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DecimalPrecision-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DecimalPrecision. Update." @@ -67,7 +70,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -78,59 +85,45 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command_name: update command: - { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDecimalPrecision": { $$type: "binData" } + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } } } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": *encrypted_fields }, - "deleteTokens": { - "default.default": { - "encryptedDecimalPrecision": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } + "u": { + "$set": { + "encryptedDecimalPrecision": { $$type: "binData" } } } - }, - "$db": "default" - } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml similarity index 79% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Aggregate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml index 67598094c1..7cc857c3d9 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. Aggregate." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedDouble": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -97,7 +108,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: aggregate outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml similarity index 95% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Correctness.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml index bca46f2c66..38e870a053 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml @@ -1,14 +1,17 @@ # Test correctness results. # Does not include command monitoring expectations or outcome assertions to make tests more readable. -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml similarity index 70% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Delete.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml index 5fc9680ae0..3902b46c61 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. Delete." @@ -64,7 +67,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -75,7 +82,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -86,7 +97,7 @@ tests: "encryptedDouble": { "$gt": { "$binary": { - "base64": "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", + "base64": "DYckAAADcGF5bG9hZABXJAAABGcAQyQAAAMwAH0AAAAFZAAgAAAAAHgYoMGjEE6fAlAhICv0+doHcVX8CmMVxyq7+jlyGrvmBXMAIAAAAAC/5MQZgTHuIr/O5Z3mXPvqrom5JTQ8IeSpQGhO9sB+8gVsACAAAAAAuPSXVmJUAUpTQg/A9Bu1hYczZF58KEhVofakygbsvJQAAzEAfQAAAAVkACAAAAAA2kiWNvEc4zunJ1jzvuClFC9hjZMYruKCqAaxq+oY8EAFcwAgAAAAACofIS72Cm6s866UCk+evTH3CvKBj/uZd72sAL608rzTBWwAIAAAAADuCQ/M2xLeALF0UFZtJb22QGOhHmJv6xoO+kZIHcDeiAADMgB9AAAABWQAIAAAAABkfoBGmU3hjYBvQbjNW19kfXneBQsQQPRfUL3UAwI2cAVzACAAAAAAUpK2BUOqX/DGdX5YJniEZMWkofxHqeAbXceEGJxhp8AFbAAgAAAAAKUaLzIldNIZv6RHE+FwbMjzcNHqPESwF/37mm43VPrsAAMzAH0AAAAFZAAgAAAAAFNprhQ3ZwIcYbuzLolAT5n/vc14P9kUUQComDu6eFyKBXMAIAAAAAAcx9z9pk32YbPV/sfPZl9ALIEVsqoLXgqWLVK/tP+heAVsACAAAAAA/qxvuvJbAHwwhfrPVpmCFzNvg2cU/NXaWgqgYUZpgXwAAzQAfQAAAAVkACAAAAAAODI+pB2pCuB+YmNEUAgtMfNdt3DmSkrJ96gRzLphgb8FcwAgAAAAAAT7dewFDxUDECQ3zVq75/cUN4IP+zsqhkP5+czUwlJIBWwAIAAAAACFGeOtd5zBXTJ4JYonkn/HXZfHipUlqGwIRUcH/VTatwADNQB9AAAABWQAIAAAAACNAk+yTZ4Ewk1EnotQK8O3h1gg9I7pr9q2+4po1iJVgAVzACAAAAAAUj/LesmtEsgqYVzMJ67umVA11hJTdDXwbxDoQ71vWyUFbAAgAAAAABlnhpgTQ0WjLb5u0b/vEydrCeFjVynKd7aqb+UnvVLeAAM2AH0AAAAFZAAgAAAAAD/FIrGYFDjyYmVb7oTMVwweWP7A6F9LnyIuNO4MjBnXBXMAIAAAAACIZgJCQRZu7NhuNMyOqCn1tf+DfU1qm10TPCfj5JYV3wVsACAAAAAA5hmY4ptuNxULGf87SUFXQWGAONsL9U29duh8xqsHtxoAAzcAfQAAAAVkACAAAAAAciRW40ORJLVwchOEpz87Svb+5toAFM6LxDWv928ECwQFcwAgAAAAAN0dipyESIkszfjRzdDi8kAGaa2Hf4wrPAtiWwboZLuxBWwAIAAAAAANr4o/+l1OIbbaX5lZ3fQ/WIeOcEXjNI1F0WbSgQrzaQADOAB9AAAABWQAIAAAAACZqAyCzYQupJ95mrBJX54yIz9VY7I0WrxpNYElCI4dTQVzACAAAAAA/eyJb6d1xfE+jJlVXMTD3HS/NEYENPVKAuj56Dr2dSEFbAAgAAAAANkSt154Or/JKb31VvbZFV46RPgUp8ff/hcPORL7PpFBAAM5AH0AAAAFZAAgAAAAAI5bm3YO0Xgf0VT+qjVTTfvckecM3Cwqj7DTKZXf8/NXBXMAIAAAAAD/m+h8fBhWaHm6Ykuz0WX1xL4Eme3ErLObyEVJf8NCywVsACAAAAAAfb1VZZCqs2ivYbRzX4p5CtaCkKW+g20Pr57FWXzEZi8AAzEwAH0AAAAFZAAgAAAAANqo4+p6qdtCzcB4BX1wQ6llU7eFBnuu4MtZwp4B6mDlBXMAIAAAAAAGiz+VaukMZ+6IH4jtn4KWWdKK4/W+O+gRioQDrfzpMgVsACAAAAAAG4YYkTp80EKo59mlHExDodRQFR7njhR5dmISwUJ6ukAAAzExAH0AAAAFZAAgAAAAAPrFXmHP2Y4YAm7b/aqsdn/DPoDkv7B8egWkfe23XsM1BXMAIAAAAAAGhwpKAr7skeqHm3oseSbO7qKNhmYsuUrECBxJ5k+D2AVsACAAAAAAAqPQi9luYAu3GrFCEsVjd9z2zIDcp6SPTR2w6KQEr+IAAzEyAH0AAAAFZAAgAAAAABzjYxwAjXxXc0Uxv18rH8I3my0Aguow0kTwKyxbrm+cBXMAIAAAAADVbqJVr6IdokuhXkEtXF0C2gINLiAjMVN20lE20Vmp2QVsACAAAAAAD7K1Fx4gFaaizkIUrf+EGXQeG7QX1jadhGc6Ji471H8AAzEzAH0AAAAFZAAgAAAAAFMm2feF2fFCm/UC6AfIyepX/xJDSmnnolQIBnHcPmb5BXMAIAAAAABLI11kFrQoaNVZFmq/38aRNImPOjdJh0Lo6irI8M/AaAVsACAAAAAAOWul0oVqJ9CejD2RqphhTC98DJeRQy5EwbNerU2+4l8AAzE0AH0AAAAFZAAgAAAAAJvXB3KyNiNtQko4SSzo/9b2qmM2zU9CQTTDfLSBWMgRBXMAIAAAAAAvjuVP7KsLRDeqVqRziTKpBrjVyqKiIbO9Gw8Wl2wFTAVsACAAAAAADlE+oc1ins+paNcaOZJhBlKlObDJ4VQORWjFYocM4LgAAzE1AH0AAAAFZAAgAAAAAPGdcxDiid8z8XYnfdDivNMYVPgBKdGOUw6UStU+48CdBXMAIAAAAAARj6g1Ap0eEfuCZ4X2TsEw+Djrhto3fA5nLwPaY0vCTgVsACAAAAAAoHqiwGOUkBu8SX5U1yHho+UIFdSN2MdQN5s6bQ0EsJYAAzE2AH0AAAAFZAAgAAAAAP5rGPrYGt3aKob5f/ldP0qrW7bmWvqnKY4QwdDWz400BXMAIAAAAADTQkW2ymaaf/bhteOOGmSrIR97bAnJx+yN3yMj1bTeewVsACAAAAAADyQnHGH2gF4w4L8axUsSTf6Ubk7L5/eoFOJk12MtZAoAAzE3AH0AAAAFZAAgAAAAAAlz6wJze5UkIxKpJOZFGCOf3v2KByWyI6NB6JM9wNcBBXMAIAAAAABUC7P/neUIHHoZtq0jFVBHY75tSFYr1Y5S16YN5XxC1QVsACAAAAAAgvxRbXDisNnLY3pfsjDdnFLtkvYUC4lhA68eBXc7KAwAAzE4AH0AAAAFZAAgAAAAAFJ8AtHcjia/9Y5pLEc3qVgH5xKiXw12G9Kn2A1EY8McBXMAIAAAAAAxe7Bdw7eUSBk/oAawa7uicTEDgXLymRNhBy1LAxhDvwVsACAAAAAAxKPaIBKVx3jTA+R/el7P7AZ7efrmTGjJs3Hj/YdMddwAAzE5AH0AAAAFZAAgAAAAAO8uwQUaKFb6vqR3Sv3Wn4QAonC2exOC9lGG1juqP5DtBXMAIAAAAABZf1KyJgQg8/Rf5c02DgDK2aQu0rNCOvaL60ohDHyY+gVsACAAAAAAqyEjfKC8lYoIfoXYHUqHZPoaA6EK5BAZy5dxXZmay4kAAzIwAH0AAAAFZAAgAAAAAE8YtqyRsGCeiR6hhiyisR/hccmK4nZqIMzO4lUBmEFzBXMAIAAAAAC1UYOSKqAeG1UJiKjWFVskRhuFKpj9Ezy+lICZvFlN5AVsACAAAAAA6Ct9nNMKyRazn1OKnRKagm746CGu+jyhbL1qJnZxGi0AAzIxAH0AAAAFZAAgAAAAAPhCrMausDx1QUIEqp9rUdRKyM6a9AAx7jQ3ILIu8wNIBXMAIAAAAACmH8lotGCiF2q9VQxhsS+7LAZv79VUAsOUALaGxE/EpAVsACAAAAAAnc1xCKfdvbUEc8F7XZqlNn1C+hZTtC0I9I3LL06iaNkAAzIyAH0AAAAFZAAgAAAAAOBi/GAYFcstMSJPgp3VkMiuuUUCrZytvqYaU8dwm8v2BXMAIAAAAACEZSZVyD3pKzGlbdwlYmWQhHHTV5SnNLknl2Gw8IaUTQVsACAAAAAAfsLZsEDcWSuNsIo/TD1ReyQW75HPMgmuKZuWFOLKRLoAAzIzAH0AAAAFZAAgAAAAAIQuup+YGfH3mflzWopN8J1X8o8a0d9CSGIvrA5HOzraBXMAIAAAAADYvNLURXsC2ITMqK14LABQBI+hZZ5wNf24JMcKLW+84AVsACAAAAAACzfjbTBH7IwDU91OqLAz94RFkoqBOkzKAqQb55gT4/MAAzI0AH0AAAAFZAAgAAAAAKsh0ADyOnVocFrOrf6MpTrNvAj8iaiE923DPryu124gBXMAIAAAAADg24a8NVE1GyScc6tmnTbmu5ulzO+896fE92lN08MeswVsACAAAAAAaPxcOIxnU7But88/yadOuDJDMcCywwrRitaxMODT4msAAzI1AH0AAAAFZAAgAAAAAKkVC2Y6HtRmv72tDnPUSjJBvse7SxLqnr09/Uuj9sVVBXMAIAAAAABYNFUkH7ylPMN+Bc3HWX1e0flGYNbtJNCY9SltJCW/UAVsACAAAAAAZYK/f9H4OeihmpiFMH7Wm7uLvs2s92zNA8wyrNZTsuMAAzI2AH0AAAAFZAAgAAAAADDggcwcb/Yn1Kk39sOHsv7BO/MfP3m/AJzjGH506Wf9BXMAIAAAAAAYZIsdjICS0+BDyRUPnrSAZfPrwtuMaEDEn0/ijLNQmAVsACAAAAAAGPnYVvo2ulO9z4LGd/69NAklfIcZqZvFX2KK0s+FcTUAAzI3AH0AAAAFZAAgAAAAAEWY7dEUOJBgjOoWVht1wLehsWAzB3rSOBtLgTuM2HC8BXMAIAAAAAAAoswiHRROurjwUW8u8D5EUT+67yvrgpB/j6PzBDAfVwVsACAAAAAA6NhRTYFL/Sz4tao7vpPjLNgAJ0FX6P/IyMW65qT6YsMAAzI4AH0AAAAFZAAgAAAAAPZaapeAUUFPA7JTCMOWHJa9lnPFh0/gXfAPjA1ezm4ZBXMAIAAAAACmJvLY2nivw7/b3DOKH/X7bBXjJwoowqb1GtEFO3OYgAVsACAAAAAA/JcUoyKacCB1NfmH8vYqC1f7rd13KShrQqV2r9QBP44AAzI5AH0AAAAFZAAgAAAAAK00u6jadxCZAiA+fTsPVDsnW5p5LCr4+kZZZOTDuZlfBXMAIAAAAAAote4zTEYMDgaaQbAdN8Dzv93ljPLdGjJzvnRn3KXgtQVsACAAAAAAxXd9Mh6R3mnJy8m7UfqMKi6oD5DlZpkaOz6bEjMOdiwAAzMwAH0AAAAFZAAgAAAAAFbgabdyymiEVYYwtJSWa7lfl/oYuj/SukzJeDOR6wPVBXMAIAAAAADAFGFjS1vPbN6mQEhkDYTD6V2V23Ys9gUEUMGNvMPkaAVsACAAAAAAL/D5Sze/ZoEanZLK0IeEkhgVkxEjMWVCfmJaD3a8uNIAAzMxAH0AAAAFZAAgAAAAABNMR6UBv2E627CqLtQ/eDYx7OEwQ7JrR4mSHFa1N8tLBXMAIAAAAAAxH4gucI4UmNVB7625C6hFSVCuIpJO3lusJlPuL8H5EQVsACAAAAAAVLHNg0OUVqZ7WGOP53BkTap9FOw9dr1P4J8HxqFqU04AAzMyAH0AAAAFZAAgAAAAAG8cd6WBneNunlqrQ2EmNf35W7OGObGq9WL4ePX+LUDmBXMAIAAAAAAjJ2+sX87NSis9hBsgb1QprVRnO7Bf+GObCGoUqyPE4wVsACAAAAAAs9c9SM49/pWmyUQKslpt3RTMBNSRppfNO0JBvUqHPg0AAzMzAH0AAAAFZAAgAAAAAFWOUGkUpy8yf6gB3dio/aOfRKh7XuhvsUj48iESFJrGBXMAIAAAAAAY7sCDMcrUXvNuL6dO0m11WyijzXZvPIcOKob6IpC4PQVsACAAAAAAJOP+EHz6awDb1qK2bZQ3kTV7wsj5Daj/IGAWh4g7omAAAzM0AH0AAAAFZAAgAAAAAGUrIdKxOihwNmo6B+aG+Ag1qa0+iqdksHOjQj+Oy9bZBXMAIAAAAABwa5dbI2KmzBDNBTQBEkjZv4sPaeRkRNejcjdVymRFKQVsACAAAAAA4ml/nm0gJNTcJ4vuD+T2Qfq2fQZlibJp/j6MOGDrbHMAAzM1AH0AAAAFZAAgAAAAAOx89xV/hRk64/CkM9N2EMK6aldII0c8smdcsZ46NbP8BXMAIAAAAADBF6tfQ+7q9kTuLyuyrSnDgmrdmrXkdhl980i1KHuGHgVsACAAAAAACUqiFqHZdGbwAA+hN0YUE5zFg+H+dabIB4dj5/75W/YAAzM2AH0AAAAFZAAgAAAAAMkN0L1oQWXhjwn9rAdudcYeN8/5VdCKU8cmDt7BokjsBXMAIAAAAAAT62pGXoRwExe9uvgYOI0hg5tOxilrWfoEmT0SMglWJwVsACAAAAAAlVz4dhiprSbUero6JFfxzSJGclg63oAkAmgbSwbcYxIAAzM3AH0AAAAFZAAgAAAAANxfa4xCoaaB7k1C1RoH1LBhsCbN2yEq15BT9b+iqEC4BXMAIAAAAACAX9LV8Pemfw7NF0iB1/85NzM1Ef+1mUfyehacUVgobQVsACAAAAAAVq4xpbymLk0trPC/a2MvB39I7hRiX8EJsVSI5E5hSBkAAzM4AH0AAAAFZAAgAAAAAOYIYoWkX7dGuyKfi3XssUlc7u/gWzqrR9KMkikKVdmSBXMAIAAAAABVF2OYjRTGi9Tw8XCAwZWLpX35Yl271TlNWp6N/nROhAVsACAAAAAA0nWwYzXQ1+EkDvnGq+SMlq20z+j32Su+i/A95SggPb4AAzM5AH0AAAAFZAAgAAAAAIy0+bXZi10QC+q7oSOLXK5Fee7VEk/qHSXukfeVIfgzBXMAIAAAAAAQ3IIV/JQCHW95AEbH5zGIHtJqyuPjWPMIZ+VmQHlxEwVsACAAAAAAp0jYsyohKv9Pm+4k+DplEGbl9WLZpAJzitrcDj4CNsMAAzQwAH0AAAAFZAAgAAAAAL5SOJQ3LOhgdXJ5v086NNeAl1qonQnchObdpZJ1kHeEBXMAIAAAAAA+tEqTXODtik+ydJZSnUqXF9f18bPeze9eWtR7ExZJgQVsACAAAAAAbrkZCVgB9Qsp4IAbdf+bD4fT6Boqk5UtuA/zhNrh1y0AAzQxAH0AAAAFZAAgAAAAAKl8zcHJRDjSjJeV/WvMxulW1zrTFtaeBy/aKKhadc6UBXMAIAAAAADBdWQl5SBIvtZZLIHszePwkO14W1mQ0izUk2Ov21cPNAVsACAAAAAAHErCYycpqiIcCZHdmPL1hi+ovLQk4TAvENpfLdTRamQAAzQyAH0AAAAFZAAgAAAAAFvotcNaoKnVt5CBCOPwjexFO0WGWuaIGL6H/6KSau+6BXMAIAAAAAD2y2mBN5xPu5PJoY2zcr0GnQDtHRBogA5+xzIxccE9fwVsACAAAAAAdS34xzJesnUfxLCcc1U7XzUqLy8MAzV/tcjbqaD3lkMAAzQzAH0AAAAFZAAgAAAAAPezU0/vNT4Q4YKbTbaeHqcwNLT+IjW/Y9QFpIooihjPBXMAIAAAAACj2x4O4rHter8ZnTws5LAP9jJ/6kk9C/V3vL50LoFZHAVsACAAAAAAQdBDF3747uCVP5lB/zr8VmzxJfTSZHBKeIgm5FyONXwAAzQ0AH0AAAAFZAAgAAAAAMqpayM2XotEFmm0gwQd9rIzApy0X+7HfOhNk6VU7F5lBXMAIAAAAACJR9+q5T9qFHXFNgGbZnPubG8rkO6cwWhzITQTmd6VgwVsACAAAAAAOZLQ6o7e4mVfDzbpQioa4d3RoTvqwgnbmc5Qh2wsZuoAAzQ1AH0AAAAFZAAgAAAAANCeyW+3oebaQk+aqxNVhAcT/BZ5nhsTVdKS3tMrLSvWBXMAIAAAAADxRFMDhkyuEc++WnndMfoUMLNL7T7rWoeblcrpSI6soQVsACAAAAAAdBuBMJ1lxt0DRq9pOZldQqchLs3B/W02txcMLD490FEAAzQ2AH0AAAAFZAAgAAAAAIbo5YBTxXM7HQhl7UP9NNgpPGFkBx871r1B65G47+K8BXMAIAAAAAC21dJSxnEhnxO5gzN5/34BL4von45e1meW92qowzb8fQVsACAAAAAAm3Hk2cvBN0ANaR5jzeZE5TsdxDvJCTOT1I01X7cNVaYAAzQ3AH0AAAAFZAAgAAAAABm/6pF96j26Jm7z5KkY1y33zcAEXLx2n0DwC03bs/ixBXMAIAAAAAD01OMvTZI/mqMgxIhA5nLs068mW+GKl3OW3ilf2D8+LgVsACAAAAAAaLvJDrqBESTNZSdcXsd+8GXPl8ZkUsGpeYuyYVv/kygAAzQ4AH0AAAAFZAAgAAAAAJ/D3+17gaQdkBqkL2wMwccdmCaVOtxzIkM8VyI4xI5zBXMAIAAAAAAggLVmkc5u+YzBR+oNE+XgLVp64fC6MzUb/Ilu/Jsw0AVsACAAAAAACz3HVKdWkx82/kGbVpcbAeZtsj2R5Zr0dEPfle4IErkAAzQ5AH0AAAAFZAAgAAAAAJMRyUW50oaTzspS6A3TUoXyC3gNYQoShUGPakMmeVZrBXMAIAAAAACona2Pqwt4U2PmFrtmu37jB9kQ/12okyAVtYa8TQkDiQVsACAAAAAAltJJKjCMyBTJ+4PkdDCPJdeX695P8P5h7WOZ+kmExMAAAzUwAH0AAAAFZAAgAAAAAByuYl8dBvfaZ0LO/81JW4hYypeNmvLMaxsIdvqMPrWoBXMAIAAAAABNddwobOUJzm9HOUD8BMZJqkNCUCqstHZkC76FIdNg9AVsACAAAAAAQQOkIQtkyNavqCnhQbNg3HfqrJdsAGaoxSJePJl1qXsAAzUxAH0AAAAFZAAgAAAAAHEzLtfmF/sBcYPPdj8867VmmQyU1xK9I/3Y0478azvABXMAIAAAAAAcmyFajZPnBTbO+oLInNwlApBocUekKkxz2hYFeSlQ+gVsACAAAAAAZ6IkrOVRcC8vSA6Vb4fPWZJrYexXhEabIuYIeXNsCSgAAzUyAH0AAAAFZAAgAAAAAJam7JYsZe2cN20ZYm2W3v1pisNt5PLiniMzymBLWyMtBXMAIAAAAABxCsKVMZMTn3n+R2L7pVz5nW804r8HcK0mCBw3jUXKXAVsACAAAAAA7j3JGnNtR64P4dJLeUoScFRGfa8ekjh3dvhw46sRFk0AAzUzAH0AAAAFZAAgAAAAAMXrXx0saZ+5gORmwM2FLuZG6iuO2YS+1IGPoAtDKoKBBXMAIAAAAADIQsxCr8CfFKaBcx8kIeSywnGh7JHjKRJ9vJd9x79y7wVsACAAAAAAcvBV+SykDYhmRFyVYwFYB9oBKBSHr55Jdz2cXeowsUQAAzU0AH0AAAAFZAAgAAAAACbzcUD3INSnCRspOKF7ubne74OK9L0FTZvi9Ay0JVDYBXMAIAAAAADPebVQH8Btk9rhBIoUOdSAdpPvz7qIY4UC2i6IGisSAQVsACAAAAAAiBunJi0mPnnXdnldiq+If8dcb/n6apHnaIFt+oyYO1kAAzU1AH0AAAAFZAAgAAAAACUc2CtD1MK/UTxtv+8iA9FoHEyTwdl43HKeSwDw2Lp5BXMAIAAAAACCIduIdw65bQMzRYRfjBJj62bc69T4QqH4QoWanwlvowVsACAAAAAAM0TV7S+aPVVzJOQ+cpSNKHTwyQ0mWa8tcHzfk3nR+9IAAzU2AH0AAAAFZAAgAAAAAHSaHWs/dnmI9sc7nB50VB2Bzs0kHapMHCQdyVEYY30TBXMAIAAAAACkV22lhEjWv/9/DubfHBAcwJggKI5mIbSK5L2nyqloqQVsACAAAAAAS19m7DccQxgryOsBJ3GsCs37yfQqNi1G+S6fCXpEhn4AAzU3AH0AAAAFZAAgAAAAAAL8jhNBG0KXXZhmZ0bPXtfgapJCB/AI+BEHB0eZ3C75BXMAIAAAAADHx/fPa639EBmGV5quLi8IQT600ifiKSOhTDOK19DnzwVsACAAAAAAlyLTDVkHxbayklD6Qymh3odIK1JHaOtps4f4HR+PcDgAAzU4AH0AAAAFZAAgAAAAAAxgeclNl09H7HvzD1oLwb2YpFca5eaX90uStYXHilqKBXMAIAAAAACMU5pSxzIzWlQxHyW170Xs9EhD1hURASQk+qkx7K5Y6AVsACAAAAAAJbMMwJfNftA7Xom8Bw/ghuZmSa3x12vTZxBUbV8m888AAzU5AH0AAAAFZAAgAAAAABmO7QD9vxWMmFjIHz13lyOeV6vHT6mYCsWxF7hb/yOjBXMAIAAAAACT9lmgkiqzuWG24afuzYiCeK9gmJqacmxAruIukd0xEAVsACAAAAAAZa0/FI/GkZR7CtX18Xg9Tn9zfxkD0UoaSt+pIO5t1t4AAzYwAH0AAAAFZAAgAAAAAB89SjLtDJkqEghRGyj6aQ/2qvWLNuMROoXmzbYbCMKMBXMAIAAAAAC8sywgND+CjhVTF6HnRQeay8y9/HnVzDI42dEPah28LQVsACAAAAAAoxv7UKh0RqUAWcOsQvU123zO1qZn73Xfib0qncZCB34AAzYxAH0AAAAFZAAgAAAAABN2alGq9Aats1mwERNGwL/fIwZSvVCe9/8XMHTFlpUpBXMAIAAAAACuDPjJgvvbBYhbLpjMiWUCsVppiYrhvR+yMysNPN8cZAVsACAAAAAAKpADjc4bzIZMi9Q/+oe0EMRJHYQt6dlo1x/lRquagqkAAzYyAH0AAAAFZAAgAAAAAL8YB6VAqGBiWD4CBv16IBscg5J7VQCTZu87n6pj+86KBXMAIAAAAAAmxm8e68geeyAdUjSMWBHzUjneVB0pG9TBXIoE6467hAVsACAAAAAAV76JZAlYpgC/Zl8awx2ArCg1uuyy2XVTSkp0wUMi/7UAAzYzAH0AAAAFZAAgAAAAAL4yLkCTV5Dmxa5toBu4JT8ge/cITAaURIOuFuOtFUkeBXMAIAAAAAAXoFNQOMGkAj7qEJP0wQafmFSXgWGeorDVbwyOxWLIsgVsACAAAAAAc4Un6dtIFe+AQ+RSfNWs3q63RTHhmyc+5GKRRdpWRv8AAzY0AH0AAAAFZAAgAAAAAEU8DoUp46YtYjNFS9kNXwdYxQ9IW27vCTb+VcqqfnKNBXMAIAAAAADe7vBOgYReE8X78k5ARuUnv4GmzPZzg6SbConf4L2G3wVsACAAAAAA78YHWVkp6HbZ0zS4UL2z/2pj9vPDcMDt7zTv6NcRsVsAAzY1AH0AAAAFZAAgAAAAAPa4yKTtkUtySuWo1ZQsp2QXtPb5SYqzA5vYDnS1P6c0BXMAIAAAAADKnF58R1sXlHlsHIvCBR3YWW/qk54z9CTDhZydkD1cOQVsACAAAAAAHW3ERalTFWKMzjuXF3nFh0pSrQxM/ojnPbPhc4v5MaQAAzY2AH0AAAAFZAAgAAAAAN5WJnMBmfgpuQPyonmY5X6OdRvuHw4nhsnGRnFAQ95VBXMAIAAAAACwftzu7KVV1rmGKwXtJjs3cJ1gE3apr8+N0SAg1F2cHwVsACAAAAAATDW0reyaCjbJuVLJzbSLx1OBuBoQu+090kgW4RurVacAAzY3AH0AAAAFZAAgAAAAACHvDsaPhoSb6DeGnKQ1QOpGYAgK82qpnqwcmzSeWaJHBXMAIAAAAABRq3C5+dOfnkAHM5Mg5hPB3O4jhwQlBgQWLA7Ph5bhgwVsACAAAAAAqkC8zYASvkVrp0pqmDyFCkPaDmD/ePAJpMuNOCBhni8AAzY4AH0AAAAFZAAgAAAAAOBePJvccPMJmy515KB1AkXF5Pi8NOG4V8psWy0SPRP+BXMAIAAAAAB3dOJG9xIDtEKCRzeNnPS3bFZepMj8UKBobKpSoCPqpgVsACAAAAAAPG3IxQVOdZrr509ggm5FKizWWoZPuVtOgOIGZ3m+pdEAAzY5AH0AAAAFZAAgAAAAABUvRrDQKEXLMdhnzXRdhiL6AGNs2TojPky+YVLXs+JnBXMAIAAAAAD1kYicbEEcPzD4QtuSYQQWDPq8fuUWGddpWayKn3dT9QVsACAAAAAA9+Sf7PbyFcY45hP9oTfjQiOUS3vEIAT8C0vOHymwYSUAAzcwAH0AAAAFZAAgAAAAAOvSnpujeKNen4pqc2HR63C5s5oJ1Vf4CsbKoYQvkwl5BXMAIAAAAACw2+vAMdibzd2YVVNfk81yXkFZP0WLJ82JBxJmXnYE+QVsACAAAAAArQ/E1ACyhK4ZyLqH9mNkCU7WClqRQTGyW9tciSGG/EMAAzcxAH0AAAAFZAAgAAAAAAo0xfGG7tJ3GWhgPVhW5Zn239nTD3PadShCNRc9TwdNBXMAIAAAAADZh243oOhenu0s/P/5KZLBDh9ADqKHtSWcXpO9D2sIjgVsACAAAAAAlgTPaoQKz+saU8rwCT3UiNOdG6hdpjzFx9GBn08ZkBEAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -95,28 +106,15 @@ tests: "limit": 1 } ] + ordered: true encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDouble": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: delete outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml similarity index 79% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Double-FindOneAndUpdate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml index 0b3d39dffa..59556ba71e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. FindOneAndUpdate." @@ -65,7 +68,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -76,7 +83,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedDouble": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -95,25 +106,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDouble": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: findAndModify outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml similarity index 79% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Double-InsertFind.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml index 87f3f05d0b..95b9fdc8f6 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. Insert and Find." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -83,7 +94,7 @@ tests: "encryptedDouble": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -91,7 +102,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml similarity index 69% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Update.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml index 5f8423b8fe..7ab0a0cec2 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Double-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. Update." @@ -67,7 +70,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -78,59 +85,45 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command_name: update command: - { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDouble": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDouble": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" } } - }, - "u": { - "$set": { - "encryptedDouble": { $$type: "binData" } - } } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": *encrypted_fields }, - "deleteTokens": { - "default.default": { - "encryptedDouble": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } + "u": { + "$set": { + "encryptedDouble": { $$type: "binData" } } } - }, - "$db": "default" - } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Aggregate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml index 47ac5d4444..2700de4ad4 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DoublePrecision. Aggregate." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedDoublePrecision": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -97,7 +108,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: aggregate outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml similarity index 96% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Correctness.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml index 291ffa58ab..6508db7d19 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml @@ -1,14 +1,17 @@ # Test correctness results. # Does not include command monitoring expectations or outcome assertions to make tests more readable. -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml similarity index 70% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Delete.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml index dd8b862471..e5a35c8ee4 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DoublePrecision. Delete." @@ -64,7 +67,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -75,7 +82,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -86,7 +97,7 @@ tests: "encryptedDoublePrecision": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -95,28 +106,15 @@ tests: "limit": 1 } ] + ordered: true encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDoublePrecision": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: delete outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-FindOneAndUpdate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml index a13f878e30..e74b6ecb73 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DoublePrecision. FindOneAndUpdate." @@ -65,7 +68,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -76,7 +83,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedDoublePrecision": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -95,25 +106,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedDoublePrecision": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: findAndModify outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-InsertFind.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml index 1cc9969c01..bc15c9b88b 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DoublePrecision. Insert and Find." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -83,7 +94,7 @@ tests: "encryptedDoublePrecision": { "$gt": { "$binary": { - "base64": "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", + "base64": "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", "subType": "06" } } @@ -91,7 +102,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml similarity index 65% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Update.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml index 116b623ac0..3211b5047e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-DoublePrecision-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range DoublePrecision. Update." @@ -67,7 +70,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -78,59 +85,47 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command_name: update command: - { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDoublePrecision": { $$type: "binData" } + + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } } } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": *encrypted_fields }, - "deleteTokens": { - "default.default": { - "encryptedDoublePrecision": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } + "u": { + "$set": { + "encryptedDoublePrecision": { $$type: "binData" } } } - }, - "$db": "default" - } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Aggregate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml index 163ff4f750..cb7765faae 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Int. Aggregate." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedInt": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -97,7 +108,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: aggregate outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml similarity index 96% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Correctness.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml index db2a1e46a8..77206aed6e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml @@ -1,14 +1,17 @@ # Test correctness results. # Does not include command monitoring expectations or outcome assertions to make tests more readable. -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml similarity index 70% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Delete.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml index 51a343493f..59f58650e3 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Int. Delete." @@ -64,7 +67,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -75,7 +82,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -86,7 +97,7 @@ tests: "encryptedInt": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -95,28 +106,15 @@ tests: "limit": 1 } ] + ordered: true encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedInt": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: delete outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Int-FindOneAndUpdate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml index 433be15d31..f49f319e22 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Int. FindOneAndUpdate." @@ -65,7 +68,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -76,7 +83,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedInt": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -95,25 +106,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedInt": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: findAndModify outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml similarity index 75% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Int-InsertFind.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml index dc5215eae6..d848bbdf7e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Int. Insert and Find." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -83,7 +94,7 @@ tests: "encryptedInt": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -91,7 +102,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml similarity index 65% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Update.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml index 8e0bdcb8dc..a18878f55e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Int-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Int. Update." @@ -67,7 +70,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -78,59 +85,47 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command_name: update command: - { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", - "subType": "06" - } + + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", + "subType": "06" } } - }, - "u": { - "$set": { - "encryptedInt": { $$type: "binData" } - } } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": *encrypted_fields }, - "deleteTokens": { - "default.default": { - "encryptedInt": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } + "u": { + "$set": { + "encryptedInt": { $$type: "binData" } } } - }, - "$db": "default" - } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Aggregate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml index 8064fbeddd..9837cf6e41 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Long. Aggregate." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedLong": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -97,7 +108,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: aggregate outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml similarity index 96% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Correctness.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml index 696a09fa10..4df64cb67a 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml @@ -1,14 +1,17 @@ # Test correctness results. # Does not include command monitoring expectations or outcome assertions to make tests more readable. -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml similarity index 70% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Delete.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml index 12080d60ae..1df1150c75 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Long. Delete." @@ -64,7 +67,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -75,7 +82,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -86,7 +97,7 @@ tests: "encryptedLong": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -95,28 +106,15 @@ tests: "limit": 1 } ] + ordered: true encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedLong": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: delete outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml similarity index 77% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Long-FindOneAndUpdate.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml index a90e09c47c..24f5d45c92 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Long. FindOneAndUpdate." @@ -65,7 +68,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -76,7 +83,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -85,7 +96,7 @@ tests: "encryptedLong": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -95,25 +106,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields - "deleteTokens": { - "default.default": { - "encryptedLong": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: findAndModify outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml similarity index 75% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Long-InsertFind.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml index f0c79b21dc..8e2f826f72 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Long. Insert and Find." @@ -63,7 +66,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -74,7 +81,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -83,7 +94,7 @@ tests: "encryptedLong": { "$gt": { "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", "subType": "06" } } @@ -91,7 +102,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: find outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml similarity index 65% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Update.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml index 20783a7cdd..c293a64093 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-Long-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml @@ -1,12 +1,15 @@ -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Long. Update." @@ -67,7 +70,11 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command: @@ -78,59 +85,47 @@ tests: encryptionInformation: type: 1 schema: - default.default: *encrypted_fields + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields command_name: insert - command_started_event: command_name: update command: - { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "CnEFAAADcGF5bG9hZABBBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVjACAAAAAAKs6X8gaa+AJ6PpxVicsk7iCXpSYIkQ5zB0KnXqlfCWgAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWMAIAAAAABYTWOKsDZ7cgPhiKDR2bE5CFGz4MuEwZbzZ0z46eGzBQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFYwAgAAAAABSqgKlxv5LXoxUO3zNmqGztlZhL2S150WiR/eycv7CWAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVjACAAAAAAfsofSP7nQHv8ic8ZW0aNlWxplS46Z+mywPR4rQk+wcgAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWMAIAAAAADu3L42J/oqNLe/XNYpodf2fhmPdcLhqO4J24o0hiveXwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFYwAgAAAAAAUljtCIvjRnLB2UPTEeRIz11Snl5SOBUNaXMXig4rqKAAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVjACAAAAAAD51NYesiO4Fo7w7iWBfqAFxEqkaDVctpvzZ28nT4SE8AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWMAIAAAAABJDtFhJ2tPbowp1UUmOCN/rqSqHRL1dtMu0c47vIlK4wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FYwAgAAAAAE6kvmXPqTnYIH4EJmNhy8OLVJZFOmdiBXLMorhELjKWAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVjACAAAAAA85AiE+bNFAYQTXQAFexgeczdVhf8FUnf16WzJlI/kmsAAAVlACAAAAAA65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+wSY20AAAAAAAAAAAAAEHBheWxvYWRJZAAAAAAAEGZpcnN0T3BlcmF0b3IAAQAAAAA=", - "subType": "06" - } + + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", + "subType": "06" } } - }, - "u": { - "$set": { - "encryptedLong": { $$type: "binData" } - } } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": *encrypted_fields }, - "deleteTokens": { - "default.default": { - "encryptedLong": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } + "u": { + "$set": { + "encryptedLong": { $$type: "binData" } } } - }, - "$db": "default" - } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + outcome: collection: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Range-WrongType.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml similarity index 80% rename from spec/spec_tests/data/client_side_encryption/fle2-Range-WrongType.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml index 9bc2c53dfd..ada65209e5 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Range-WrongType.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml @@ -1,14 +1,17 @@ # Test correctness results. # Does not include command monitoring expectations or outcome assertions to make tests more readable. -# Requires libmongocrypt 1.7.0. + +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.2.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +encrypted_fields: &encrypted_fields { 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Wrong type: Insert Double" diff --git a/spec/spec_tests/data/client_side_encryption/fle2-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Update.yml similarity index 72% rename from spec/spec_tests/data/client_side_encryption/fle2-Update.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-Update.yml index 929f4a3a38..ce5a4ed670 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Update.yml @@ -1,11 +1,14 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Update can query an FLE2 indexed field" @@ -63,7 +66,12 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert - command_started_event: command: @@ -74,7 +82,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -82,27 +90,16 @@ tests: }, "u": { "$set": { "foo": "bar"} } } + ordered: true encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields - deleteTokens: - "default.default": { - "encryptedIndexed": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: update outcome: collection: @@ -168,7 +165,12 @@ tests: encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert - command_started_event: command: @@ -179,7 +181,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -187,28 +189,16 @@ tests: }, "u": { "$set": { "encryptedIndexed": { "$$type": "binData" }} } } + ordered: true encryptionInformation: type: 1 schema: - "default.default": *encrypted_fields - deleteTokens: { - "default.default": { - "encryptedIndexed": { - "e": { - "$binary": { - "base64": "65pz95EthqQpfoHS9nWvdCh05AV+OokP7GUaI+7j8+w=", - "subType": "00" - } - }, - "o": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } - } - } - } - } + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: update - command_started_event: command: diff --git a/spec/spec_tests/data/client_side_encryption/fle2-validatorAndPartialFieldExpression.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml similarity index 65% rename from spec/spec_tests/data/client_side_encryption/fle2-validatorAndPartialFieldExpression.yml rename to spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml index 9463ddecad..fdeb49010c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-validatorAndPartialFieldExpression.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml @@ -1,7 +1,9 @@ -# This test requires libmongocrypt 1.5.0-alpha2. +# Requires libmongocrypt 1.8.0. runOn: # Require server version 6.0.0 to get behavior added in SERVER-64911. - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + serverless: "forbid" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] @@ -15,7 +17,7 @@ tests: kmsProviders: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: - "default.encryptedCollection": {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} + "default.encryptedCollection": {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} operations: # Drop to remove a collection that may exist from previous test runs. - name: dropCollection @@ -39,7 +41,7 @@ tests: kmsProviders: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: - "default.encryptedCollection": {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} + "default.encryptedCollection": {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} operations: # Drop to remove a collection that may exist from previous test runs. - name: dropCollection @@ -60,7 +62,7 @@ tests: kmsProviders: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: - "default.encryptedCollection": {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} + "default.encryptedCollection": {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} operations: # Drop to remove a collection that may exist from previous test runs. - name: dropCollection @@ -84,7 +86,7 @@ tests: kmsProviders: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: - "default.encryptedCollection": {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} + "default.encryptedCollection": {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} operations: # Drop to remove a collection that may exist from previous test runs. - name: dropCollection @@ -110,7 +112,7 @@ tests: kmsProviders: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: - "default.encryptedCollection": {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} + "default.encryptedCollection": {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} operations: # Drop to remove a collection that may exist from previous test runs. - name: dropCollection @@ -143,7 +145,7 @@ tests: kmsProviders: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: - "default.encryptedCollection": {'escCollection': 'enxcol_.default.esc', 'eccCollection': 'enxcol_.default.ecc', 'ecocCollection': 'enxcol_.default.ecoc', 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} + "default.encryptedCollection": {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} operations: # Drop to remove a collection that may exist from previous test runs. - name: dropCollection diff --git a/spec/support/crypt/encrypted_fields/encryptedFields.json b/spec/support/crypt/encrypted_fields/encryptedFields.json index 2364590e4c..15186fdeba 100644 --- a/spec/support/crypt/encrypted_fields/encryptedFields.json +++ b/spec/support/crypt/encrypted_fields/encryptedFields.json @@ -1,6 +1,5 @@ { "escCollection": "enxcol_.default.esc", - "eccCollection": "enxcol_.default.ecc", "ecocCollection": "enxcol_.default.ecoc", "fields": [ { diff --git a/spec/support/utils.rb b/spec/support/utils.rb index fc99bc95dc..7926d1d3f0 100644 --- a/spec/support/utils.rb +++ b/spec/support/utils.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all autoload :Base64, 'base64' autoload :JSON, 'json' @@ -8,13 +7,26 @@ module Net end module Utils + extend self + + # Used by #yamlify_command_events + MAP_REDUCE_COMMANDS = %w[ map reduce ].freeze + + # Used by #yamlify_command_events + AUTHENTICATION_COMMANDS = %w[ saslStart saslContinue authenticate getnonce ].freeze + + # The system command to invoke to represent a false result + BIN_FALSE = File.executable?('/bin/false') ? '/bin/false' : 'false' + + # The system command to invoke to represent a true result + BIN_TRUE = File.executable?('/bin/true') ? '/bin/true' : 'true' + # Converts a 'camelCase' string or symbol to a :under_score symbol. def underscore(str) str = str.to_s str = str[0].downcase + str[1...str.length].gsub(/([A-Z]+)/) { |m| "_#{m.downcase}" } str.to_sym end - module_function :underscore # Creates a copy of a hash where all keys and string values are converted to # snake-case symbols. @@ -30,14 +42,13 @@ def underscore_hash(value) end end end - module_function :underscore_hash # Creates a copy of a hash where all keys and string values are converted to # snake-case symbols. # # For example, { 'fooBar' => { 'baz' => 'bingBing', :x => 1 } } converts to # { :foo_bar => { :baz => :bing_bing, :x => 1 } }. - module_function def shallow_underscore_hash(value) + def shallow_underscore_hash(value) return value unless value.is_a?(Hash) value.reduce({}) do |hash, (k, v)| @@ -54,6 +65,7 @@ def underscore_hash(value) # { :foo_bar => { :baz => :bing_bing, :x => 1 } }. def snakeize_hash(value) return underscore(value) if value.is_a?(String) + case value when Array value.map do |sub| @@ -74,7 +86,6 @@ def snakeize_hash(value) value end end - module_function :snakeize_hash # Like snakeize_hash but does not recurse. def shallow_snakeize_hash(value) @@ -87,7 +98,6 @@ def shallow_snakeize_hash(value) end end end - module_function :shallow_snakeize_hash # Creates a copy of a hash where all keys and symbol values are converted to # camel-case strings. @@ -104,25 +114,18 @@ def camelize_hash(value, upcase_first = false) end end end - module_function :camelize_hash def camelize(str, upcase_first = false) - str = str.gsub(%r,_(\w),) { |m| m[1].upcase } - if upcase_first - str = str[0].upcase + str[1...str.length] - end + str = str.gsub(/_(\w)/) { |m| m[1].upcase } + str = str[0].upcase + str[1...str.length] if upcase_first str end - module_function :camelize - module_function def downcase_keys(hash) - # TODO replace with Hash#transform_keys when we require Ruby 2.5+ - Hash[hash.map do |k, v| - [k.downcase, v] - end] + def downcase_keys(hash) + hash.transform_keys(&:downcase) end - module_function def disable_retries_client_options + def disable_retries_client_options { retry_reads: false, retry_writes: false, @@ -134,12 +137,11 @@ def camelize(str, upcase_first = false) # Converts camel case clientOptions, as used in spec tests, # to Ruby driver underscore options. def convert_client_options(spec_test_options) - uri = Mongo::URI.new('mongodb://localhost') mapper = Mongo::URI::OptionsMapper.new - spec_test_options.reduce({}) do |opts, (name, value)| + spec_test_options.each_with_object({}) do |(name, value), opts| if name == 'autoEncryptOpts' auto_encryption_options = convert_auto_encryption_client_options(value) - opts.merge!(auto_encryption_options: auto_encryption_options) + opts[:auto_encryption_options] = auto_encryption_options else mapper.add_uri_option(name, value.to_s, opts) end @@ -147,97 +149,21 @@ def convert_client_options(spec_test_options) opts end end - module_function :convert_client_options - - private def convert_auto_encryption_client_options(opts) - auto_encrypt_opts = Utils.snakeize_hash(opts) - - if opts['kmsProviders']['aws'] - # The tests require that AWS credentials be filled in by the driver. - auto_encrypt_opts[:kms_providers][:aws] = { - access_key_id: SpecConfig.instance.fle_aws_key, - secret_access_key: SpecConfig.instance.fle_aws_secret, - } - end - - if opts['kmsProviders']['azure'] - # The tests require that Azure credentials be filled in by the driver. - auto_encrypt_opts[:kms_providers][:azure] = { - tenant_id: SpecConfig.instance.fle_azure_tenant_id, - client_id: SpecConfig.instance.fle_azure_client_id, - client_secret: SpecConfig.instance.fle_azure_client_secret, - } - end - - if opts['kmsProviders']['gcp'] - # The tests require that GCP credentials be filled in by the driver. - auto_encrypt_opts[:kms_providers][:gcp] = { - email: SpecConfig.instance.fle_gcp_email, - private_key: SpecConfig.instance.fle_gcp_private_key, - } - end - - - if opts['kmsProviders']['local'] - auto_encrypt_opts[:kms_providers][:local] = { - key: BSON::ExtJSON.parse_obj(opts['kmsProviders']['local']['key']).data - } - end - - if opts['keyVaultNamespace'] - auto_encrypt_opts[:key_vault_namespace] = opts['keyVaultNamespace'] - else - auto_encrypt_opts[:key_vault_namespace] = 'keyvault.datakeys' - end - - if opts['schemaMap'] - auto_encrypt_opts[:schema_map] = BSON::ExtJSON.parse_obj(opts['schemaMap']) - end - - if opts['encryptedFieldsMap'] - auto_encrypt_opts[:encrypted_fields_map] = BSON::ExtJSON.parse_obj(opts['encryptedFieldsMap']) - end - - auto_encrypt_opts.merge!(extra_options: convert_auto_encryption_extra_options(auto_encrypt_opts)) - end - module_function :convert_auto_encryption_client_options - - private def convert_auto_encryption_extra_options(opts) - # Spawn mongocryptd on non-default port for sharded cluster tests - extra_options = { - mongocryptd_spawn_args: [ "--port=#{SpecConfig.instance.mongocryptd_port}" ], - mongocryptd_uri: "mongodb://localhost:#{SpecConfig.instance.mongocryptd_port}" - }.merge(opts[:extra_options] || {}) - - # if bypass_query_analysis has been explicitly specified, then we ignore - # any requirement to use the shared library, as the two are not - # compatible. - if SpecConfig.instance.crypt_shared_lib_required && !opts[:bypass_query_analysis] - extra_options[:crypt_shared_lib_required] = SpecConfig.instance.crypt_shared_lib_required - extra_options[:crypt_shared_lib_path] = SpecConfig.instance.crypt_shared_lib_path - extra_options[:mongocryptd_uri] = "mongodb://localhost:27777" - end - - extra_options - end - module_function :convert_auto_encryption_extra_options def order_hash(hash) - Hash[hash.to_a.sort] + hash.to_a.sort.to_h end - module_function :order_hash # Transforms an array of CommandStarted events to an array of hashes # matching event specification in YAML spec files + # rubocop:disable Metrics, Style/IfUnlessModifier def yamlify_command_events(events) events = events.map do |e| command = e.command.dup # Fake BSON::Code for map/reduce commands - %w(map reduce).each do |key| - if command[key].is_a?(String) - command[key] = BSON::Code.new(command[key]) - end + MAP_REDUCE_COMMANDS.each do |key| + command[key] = BSON::Code.new(command[key]) if command[key].is_a?(String) end if command['readConcern'] @@ -262,14 +188,13 @@ def yamlify_command_events(events) command['getMore'] = command['getMore'].class.new(42) if command['getMore'] # Remove fields if empty - #command.delete('filter') if command['filter'] && command['filter'].empty? command.delete('query') if command['query'] && command['query'].empty? { 'command_started_event' => order_hash( 'command' => order_hash(command), 'command_name' => e.command_name.to_s, - 'database_name' => e.database_name, + 'database_name' => e.database_name ) } end @@ -277,79 +202,74 @@ def yamlify_command_events(events) # Remove any events from authentication commands. events.reject! do |e| command_name = e['command_started_event']['command_name'] - %w(saslStart saslContinue authenticate getnonce).include?(command_name) + AUTHENTICATION_COMMANDS.include?(command_name) end events end - module_function :yamlify_command_events + # rubocop:enable Metrics, Style/IfUnlessModifier + # rubocop:disable Metrics def convert_operation_options(options) if options - Hash[options.map do |k, v| - out_v = case k - when 'readPreference' - out_k = :read - out_v = {} - v.each do |sub_k, sub_v| - if sub_k == 'mode' - out_v[:mode] = Utils.underscore(v['mode']) - else - out_v[sub_k.to_sym] = sub_v + options.map do |k, v| + out_v = + case k + when 'readPreference' + out_k = :read + out_v = {} + v.each do |sub_k, sub_v| + if sub_k == 'mode' + out_v[:mode] = Utils.underscore(v['mode']) + else + out_v[sub_k.to_sym] = sub_v + end end - end - out_v - when 'defaultTransactionOptions' - out_k = Utils.underscore(k).to_sym - convert_operation_options(v) - when 'readConcern' - out_k = Utils.underscore(k).to_sym - Mongo::Options::Mapper.transform_keys_to_symbols(v).tap do |out| - if out[:level] - out[:level] = out[:level].to_sym + out_v + when 'defaultTransactionOptions' + out_k = Utils.underscore(k).to_sym + convert_operation_options(v) + when 'readConcern' + out_k = Utils.underscore(k).to_sym + Mongo::Options::Mapper.transform_keys_to_symbols(v).tap do |out| + out[:level] = out[:level].to_sym if out[:level] end - end - when 'causalConsistency' - out_k = Utils.underscore(k).to_sym - v - when 'writeConcern' - if v == {} + when 'causalConsistency' + out_k = Utils.underscore(k).to_sym + v + when 'writeConcern' # Tests added in SPEC-1352 specify {writeConcern: {}} but what # they mean is for the driver to use the default write concern, # which for Ruby means no write concern is specified at all. # # This nil return requires the compact call below to get rid of # the nils before outgoing options are constructed. - next nil - else + next nil if v == {} + # Write concern option is called :write on the client, but # :write_concern on all levels below the client. out_k = :write_concern # The client expects write concern value to only have symbol keys. - Hash[v.map do |sub_k, sub_v| - [sub_k.to_sym, sub_v] - end] + v.transform_keys(&:to_sym) + else + raise "Unhandled operation option #{k}" end - else - raise "Unhandled operation option #{k}" - end - [out_k, out_v] - end.compact] + [ out_k, out_v ] + end.compact.to_h else {} end end - module_function :convert_operation_options + # rubocop:enable Metrics def int64_value(value) if value.respond_to?(:value) # bson-ruby >= 4.6.0 value.value else - value.instance_variable_get('@integer') + value.instance_variable_get(:@integer) end end - module_function :int64_value URI_OPTION_MAP = { app_name: 'appName', @@ -361,17 +281,14 @@ def int64_value(value) ssl_key: 'tlsCertificateKeyFile', }.freeze - module_function def create_mongodb_uri(address_strs, **opts) - creds = if opts[:username] - "#{opts[:username]}:#{opts[:password]}@" - else - '' - end + # rubocop:disable Metrics + def create_mongodb_uri(address_strs, **opts) + creds = opts[:username] ? "#{opts[:username]}:#{opts[:password]}@" : '' + uri = +"mongodb://#{creds}#{address_strs.join(',')}/" - if opts[:database] - uri << opts[:database] - end - if uri_options = opts[:uri_options] + uri << opts[:database] if opts[:database] + + if (uri_options = opts[:uri_options]) uri << '?' uri_options.each do |k, v| @@ -379,17 +296,14 @@ def int64_value(value) write_k = URI_OPTION_MAP[k] || k - if k == :compressors + case k + when :compressors write_v = v.join(',') - elsif k == :auth_mech - if v - write_v = Mongo::URI::AUTH_MECH_MAP.key(v) - unless write_v - raise "Unhandled auth mech value: #{v}" - end - else - next - end + when :auth_mech + next unless v + + write_v = Mongo::URI::AUTH_MECH_MAP.key(v) + raise "Unhandled auth mech value: #{v}" unless write_v else write_v = v end @@ -400,11 +314,13 @@ def int64_value(value) uri end + # rubocop:enable Metrics # Client-Side encryption tests introduce the $$type syntax for determining # equality in command started events. The $$type key specifies which type of # BSON object is expected in the result. If the $$type key is present, only # check the class of the result. + # rubocop:disable Metrics def match_with_type?(expected, actual) if expected.is_a?(Hash) && expected.key?('$$type') case expected['$$type'] @@ -420,6 +336,11 @@ def match_with_type?(expected, actual) actual.is_a?(expected_class) || actual.key?(expected_key) elsif expected.is_a?(Hash) && actual.is_a?(Hash) + # some specs treat nil values as meaning that the field should be + # absent; we'll just squish nil values out for consistency. + expected = expected.compact + actual = actual.compact + has_all_keys = (expected.keys - actual.keys).empty? same_values = expected.keys.all? do |key| @@ -441,79 +362,71 @@ def match_with_type?(expected, actual) expected == actual end end - module_function :match_with_type? + # rubocop:enable Metrics # Takes a timeout and a block. Waits up to the specified timeout until # the value of the block is true. If timeout is reached, this method # returns normally and does not raise an exception. The block is invoked # every second or so. - module_function def wait_for_condition(timeout) + def wait_for_condition(timeout) deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout loop do - if yield - break - end - if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline - break - end + break if yield || + Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline + sleep 1 end end - module_function def ensure_port_free(port) + def ensure_port_free(port) TCPServer.open(port) do # Nothing end end - module_function def wait_for_port_free(port, timeout) + def wait_for_port_free(port, timeout) wait_for_condition(timeout) do - begin - ensure_port_free(port) - true - rescue Errno::EADDRINUSE - false - end + ensure_port_free(port) + true + rescue Errno::EADDRINUSE + false end end - module_function def get_ec2_metadata_token(ttl: 30, http: nil) + def get_ec2_metadata_token(ttl: 30, http: nil) http ||= Net::HTTP.new('169.254.169.254') + # The TTL is required in order to obtain the metadata token. req = Net::HTTP::Put.new('/latest/api/token', - # The TTL is required in order to obtain the metadata token. - {'x-aws-ec2-metadata-token-ttl-seconds' => ttl.to_s}) + { 'x-aws-ec2-metadata-token-ttl-seconds' => ttl.to_s }) resp = http.request(req) - if resp.code != '200' - raise "Metadata token request failed: #{e.class}: #{e}" - end + raise "Metadata token request failed: #{e.class}: #{e}" if resp.code != '200' + resp.body end - module_function def ec2_instance_id + def ec2_instance_id http = Net::HTTP.new('169.254.169.254') metadata_token = get_ec2_metadata_token(http: http) req = Net::HTTP::Get.new('/latest/dynamic/instance-identity/document', - {'x-aws-ec2-metadata-token' => metadata_token}) + { 'x-aws-ec2-metadata-token' => metadata_token }) resp = http.request(req) payload = JSON.parse(resp.body) payload.fetch('instanceId') end - module_function def ec2_instance_profile + def ec2_instance_profile http = Net::HTTP.new('169.254.169.254') metadata_token = get_ec2_metadata_token(http: http) req = Net::HTTP::Get.new('/latest/meta-data/iam/info', - {'x-aws-ec2-metadata-token' => metadata_token}) + { 'x-aws-ec2-metadata-token' => metadata_token }) resp = http.request(req) - if resp.code == '404' - nil - else - payload = JSON.parse(resp.body) - payload['InstanceProfileArn'] - end + return nil if resp.code == '404' + + payload = JSON.parse(resp.body) + payload['InstanceProfileArn'] end - module_function def wait_for_instance_profile + def wait_for_instance_profile deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 15 loop do begin @@ -522,55 +435,54 @@ def match_with_type?(expected, actual) puts "Instance profile assigned: #{ip}" break end - rescue => e + rescue StandardError => e puts "Problem retrieving instance profile: #{e.class}: #{e}" end + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise 'Instance profile did not get assigned in 15 seconds' end + sleep 3 end end - module_function def wait_for_no_instance_profile + def wait_for_no_instance_profile deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 15 loop do begin ip = ec2_instance_profile if ip.nil? - puts "Instance profile cleared" + puts 'Instance profile cleared' break end - rescue => e + rescue StandardError => e puts "Problem retrieving instance profile: #{e.class}: #{e}" end + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise 'Instance profile did not get cleared in 15 seconds' end + sleep 3 end end - BIN_FALSE = File.executable?("/bin/false") ? "/bin/false" : "false" - BIN_TRUE = File.executable?("/bin/true") ? "/bin/true" : "true" - - module_function def wrap_forked_child - begin - yield - rescue => e - STDERR.puts "Failing process #{Process.pid} due to #{e.class}: #{e}" - exec(BIN_FALSE) - else - # Exec so that we do not close any clients etc. in the child. - exec(BIN_TRUE) - end + def wrap_forked_child + yield + rescue StandardError => e + warn "Failing process #{Process.pid} due to #{e.class}: #{e}" + exec(BIN_FALSE) + else + # Exec so that we do not close any clients etc. in the child. + exec(BIN_TRUE) end - module_function def subscribe_all(client, subscriber) + def subscribe_all(client, subscriber) subscribe_all_sdam_proc(subscriber).call(client) end - module_function def subscribe_all_sdam_proc(subscriber) + def subscribe_all_sdam_proc(subscriber) lambda do |client| client.subscribe(Mongo::Monitoring::TOPOLOGY_OPENING, subscriber) client.subscribe(Mongo::Monitoring::SERVER_OPENING, subscriber) @@ -591,7 +503,7 @@ def match_with_type?(expected, actual) # specified client, invokes the passed block, asserts there is exactly one # command event published, asserts the command event published has the # specified command name, and returns the published event. - module_function def get_command_event(client, command_name, include_auth: false) + def get_command_event(client, command_name, include_auth: false) subscriber = Mrss::EventSubscriber.new client.subscribe(Mongo::Monitoring::COMMAND, subscriber) begin @@ -608,7 +520,7 @@ def match_with_type?(expected, actual) # # @param [ Mongo::Client ] client # @param [ String ] collection_name - module_function def create_collection(client, collection_name) + def create_collection(client, collection_name) client[collection_name].drop client[collection_name].create end @@ -616,50 +528,156 @@ def match_with_type?(expected, actual) # If the deployment is a sharded cluster, creates a direct client # to each of the mongos nodes and yields each in turn to the # provided block. Does nothing in other topologies. - module_function def mongos_each_direct_client - if ClusterConfig.instance.topology == :sharded - client = ClientRegistry.instance.global_client('basic') - client.cluster.next_primary - client.cluster.servers.each do |server| - direct_client = ClientRegistry.instance.new_local_client( - [server.address.to_s], - SpecConfig.instance.test_options.merge( - connect: :sharded - ).merge(SpecConfig.instance.auth_options)) - yield direct_client - direct_client.close - end - end - end - - module_function def load_spec_yaml_file(path) - permitted_classes = [ - BigDecimal, - Date, - Time, - Range, - Regexp, - Symbol, - BSON::Binary, - BSON::Code, - BSON::CodeWithScope, - BSON::DbPointer, - BSON::Decimal128, - BSON::Int32, - BSON::Int64, - BSON::MaxKey, - BSON::MinKey, - BSON::ObjectId, - BSON::Regexp::Raw, - BSON::Symbol::Raw, - BSON::Timestamp, - BSON::Undefined, - ] + # rubocop:disable Metrics + def mongos_each_direct_client + return unless ClusterConfig.instance.topology == :sharded + + client = ClientRegistry.instance.global_client('basic') + client.cluster.next_primary + client.cluster.servers.each do |server| + direct_client = ClientRegistry.instance.new_local_client( + [ server.address.to_s ], + SpecConfig.instance.test_options.merge( + connect: :sharded + ).merge(SpecConfig.instance.auth_options) + ) + yield direct_client + direct_client.close + end + end + # rubocop:enable Metrics + + # rubocop:disable Metrics + def permitted_yaml_classes + @permitted_yaml_classes ||= [ + BigDecimal, + Date, + Time, + Range, + Regexp, + Symbol, + BSON::Binary, + BSON::Code, + BSON::CodeWithScope, + BSON::DbPointer, + BSON::Decimal128, + BSON::Int32, + BSON::Int64, + BSON::MaxKey, + BSON::MinKey, + BSON::ObjectId, + BSON::Regexp::Raw, + BSON::Symbol::Raw, + BSON::Timestamp, + BSON::Undefined, + ].freeze + end + # rubocop:enable Metrics + + def load_spec_yaml_file(path) if RUBY_VERSION < '2.6' - YAML.safe_load(File.read(path), permitted_classes, [], true) + YAML.safe_load(File.read(path), permitted_yaml_classes, [], true) else # Here we have Ruby 2.6+ that supports the new syntax of `safe_load``. - YAML.safe_load(File.read(path), permitted_classes: permitted_classes, aliases: true) + YAML.safe_load(File.read(path), permitted_classes: permitted_yaml_classes, aliases: true) + end + end + + private + + def convert_auto_encryption_client_options(opts) + auto_encrypt_opts = Utils.snakeize_hash(opts) + + _apply_kms_providers(opts, auto_encrypt_opts) + + _apply_key_vault_namespace(opts, auto_encrypt_opts) + _apply_schema_map(opts, auto_encrypt_opts) + _apply_encrypted_fields_map(opts, auto_encrypt_opts) + + auto_encrypt_opts.merge!(extra_options: convert_auto_encryption_extra_options(auto_encrypt_opts)) + end + + def _apply_kms_provider_aws(opts, auto_encrypt_opts) + return unless opts['kmsProviders']['aws'] + + # The tests require that AWS credentials be filled in by the driver. + auto_encrypt_opts[:kms_providers][:aws] = { + access_key_id: SpecConfig.instance.fle_aws_key, + secret_access_key: SpecConfig.instance.fle_aws_secret, + } + end + + def _apply_kms_providers(opts, auto_encrypt_opts) + _apply_kms_provider_aws(opts, auto_encrypt_opts) + _apply_kms_provider_azure(opts, auto_encrypt_opts) + _apply_kms_provider_gcp(opts, auto_encrypt_opts) + _apply_kms_provider_local(opts, auto_encrypt_opts) + end + + def _apply_kms_provider_azure(opts, auto_encrypt_opts) + return unless opts['kmsProviders']['azure'] + + # The tests require that Azure credentials be filled in by the driver. + auto_encrypt_opts[:kms_providers][:azure] = { + tenant_id: SpecConfig.instance.fle_azure_tenant_id, + client_id: SpecConfig.instance.fle_azure_client_id, + client_secret: SpecConfig.instance.fle_azure_client_secret, + } + end + + def _apply_kms_provider_gcp(opts, auto_encrypt_opts) + return unless opts['kmsProviders']['gcp'] + + # The tests require that GCP credentials be filled in by the driver. + auto_encrypt_opts[:kms_providers][:gcp] = { + email: SpecConfig.instance.fle_gcp_email, + private_key: SpecConfig.instance.fle_gcp_private_key, + } + end + + def _apply_kms_provider_local(opts, auto_encrypt_opts) + return unless opts['kmsProviders']['local'] + + auto_encrypt_opts[:kms_providers][:local] = { + key: BSON::ExtJSON.parse_obj(opts['kmsProviders']['local']['key']).data + } + end + + def _apply_key_vault_namespace(opts, auto_encrypt_opts) + auto_encrypt_opts[:key_vault_namespace] = + opts['keyVaultNamespace'] || 'keyvault.datakeys' + end + + def _apply_schema_map(opts, auto_encrypt_opts) + return unless opts['schemaMap'] + + auto_encrypt_opts[:schema_map] = BSON::ExtJSON.parse_obj(opts['schemaMap']) + end + + def _apply_encrypted_fields_map(opts, auto_encrypt_opts) + return unless opts['encryptedFieldsMap'] + + auto_encrypt_opts[:encrypted_fields_map] = BSON::ExtJSON.parse_obj(opts['encryptedFieldsMap']) + end + + # rubocop:disable Metrics + def convert_auto_encryption_extra_options(opts) + # Spawn mongocryptd on non-default port for sharded cluster tests + extra_options = { + mongocryptd_spawn_args: [ "--port=#{SpecConfig.instance.mongocryptd_port}" ], + mongocryptd_uri: "mongodb://localhost:#{SpecConfig.instance.mongocryptd_port}" + }.merge(opts[:extra_options] || {}) + + # if bypass_query_analysis has been explicitly specified, then we ignore + # any requirement to use the shared library, as the two are not + # compatible. + if SpecConfig.instance.crypt_shared_lib_required && !opts[:bypass_query_analysis] + extra_options[:crypt_shared_lib_required] = SpecConfig.instance.crypt_shared_lib_required + extra_options[:crypt_shared_lib_path] = SpecConfig.instance.crypt_shared_lib_path + extra_options[:mongocryptd_uri] = 'mongodb://localhost:27777' end + + extra_options end + # rubocop:enable Metrics end From 1b17ac9bbd2d6db9339093ef2a0a4b10400d8e9f Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 25 May 2023 16:53:46 -0600 Subject: [PATCH 125/198] RUBY-3232 Switch to supported build hosts (#2712) * Bump to latest toolchain and clean up specs to use consistent (and modern) build hosts * fix config validation errors * don't try to run ruby 2.7 on ubuntu2204 * bump submodule to get updated toolchain version * rhel84 -> rhel80 * keep tweaking * add "require_solo" helper * for testing: pointing spec/shared at my own branch * update spec/shared again to fix typo * bump spec/shared version again to prevent apt-get install from prompting * make sure cmake is installed for FLE runs * don't test on 3.2 yet (broken tests) * make sure we check for cmake BEFORE the bundle is installed * use MDB 5.x for testing certain auth mechanisms this is because run-tests.sh uses `mongo` to configure the MDB instance for certain auth mechanisms, which isn't shipped in MDB 6+. * try building the native extension for mongo_kerberos * remove jruby-9.4 for now to see if things pass any better with 9.3 * bump spec/shared to get updated server registry (for aarch64 versions) * update serverless to use rhel80 * move the install_cmake function to spec/shared, and make sure it gets called in run-tests-gcp * don't run zstd tests on jruby (no support) * bump spec/shared to get updated target arch selection * replace jruby 9.4 with 9.3 in sample_rubies list * bump spec/shared again * make sure kerberos stuff doesn't get confused by the BUNDLE_GEMFILE variable * don't test jruby on arm for now * don't assume x86_64 architecture * fix invalid config * jruby-9.3 isn't being built for ubuntu/debian :/ * need to build the bundle for the kerberos gem before compiling it * bump spec/shared to latest from my branch * jruby-9.3 is being built as a recent ruby now * don't include ruby-3.2 in supported rubies (yet) * rebuild config.yml * bump spec/shared * leaving out debian11 for now; we have ubuntu2204, which is closely related * the file is /etc/os-release, not /etc/os_release * remove jruby from FLE tests * comment out the failing tests (and reference the corresponding Jira tickets) this will at least let us get this "build hosts" effort sorted * forgot to comment out 'latest' * cmake is only needed if the helper is being installed Co-authored-by: Dmitry Rybakov * bump spec/shared --------- Co-authored-by: Dmitry Rybakov --- .evergreen/config.yml | 709 ++++++------------- .evergreen/config/axes.yml.erb | 41 +- .evergreen/config/standard.yml.erb | 643 ++++++----------- .evergreen/run-tests-gcp.sh | 1 + .evergreen/run-tests-kerberos-integration.sh | 16 + .evergreen/run-tests-serverless.sh | 10 +- .evergreen/run-tests.sh | 6 + .evergreen/update-evergreen-configs | 30 - .gitmodules | 2 +- spec/lite_spec_helper.rb | 9 + spec/shared | 2 +- spec/solo/clean_exit_spec.rb | 13 +- spec/support/shared/app_metadata.rb | 16 +- 13 files changed, 539 insertions(+), 959 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 919ee71c08..a7562f9210 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -788,6 +788,14 @@ axes: - id: "ruby" display_name: Ruby Version values: + - id: "ruby-3.2" + display_name: ruby-3.2 + variables: + RVM_RUBY: "ruby-3.2" + - id: "ruby-3.1" + display_name: ruby-3.1 + variables: + RVM_RUBY: "ruby-3.1" - id: "ruby-3.1" display_name: ruby-3.1 variables: @@ -820,51 +828,38 @@ axes: display_name: jruby-9.3 variables: RVM_RUBY: "jruby-9.3" - + - id: "jruby-9.4" + display_name: jruby-9.4 + variables: + RVM_RUBY: "jruby-9.4" + - id: "os" display_name: OS values: - id: debian11 display_name: "Debian 11" run_on: debian11-small - - id: ubuntu2004 - display_name: "Ubuntu 20.04" - run_on: ubuntu2004-small - - id: ubuntu1804 - display_name: "Ubuntu 18.04" - run_on: ubuntu1804-small - - id: ubuntu1604 - display_name: "Ubuntu 16.04" - run_on: ubuntu1604-small - - id: rhel80 + - id: ubuntu2204 + display_name: "Ubuntu 22.04" + run_on: ubuntu2204-small + - id: rhel8 display_name: "RHEL 8" run_on: rhel80-small - - id: rhel70 - display_name: "RHEL 7" - run_on: rhel70-small - - id: rhel62 - display_name: "RHEL 6" - run_on: rhel62-small + - id: rhel8-arm + display_name: "RHEL 8 ARM64" + run_on: rhel82-arm64-small - id: docker-distro display_name: Docker Distro values: - - id: debian92 - display_name: debian92 - variables: - DOCKER_DISTRO: debian92 - - id: debian10 - display_name: debian10 + - id: debian11 + display_name: debian11 variables: - DOCKER_DISTRO: debian10 - - id: ubuntu1804 - display_name: ubuntu1804 + DOCKER_DISTRO: debian11 + - id: ubuntu2204 + display_name: ubuntu2204 variables: - DOCKER_DISTRO: ubuntu1804 - - id: ubuntu2004 - display_name: ubuntu2004 - variables: - DOCKER_DISTRO: ubuntu2004 + DOCKER_DISTRO: ubuntu2204 - id: "compressor" display_name: Compressor @@ -959,7 +954,7 @@ axes: values: - id: mmapv1 display_name: MMAPv1 - run_on: ubuntu1604-small + run_on: rhel80-small variables: MMAPV1: 'true' @@ -1060,148 +1055,105 @@ axes: display_name: No buildvariants: - - matrix_name: "docker" - matrix_spec: - ruby: ["ruby-2.7"] - mongodb-version: "4.2" - topology: standalone - os: ubuntu1804 - preload: preload - display_name: "Docker ${os} ${mongodb-version} ${topology} ${ruby} ${preload}" - tasks: - - name: "test-docker" - - - matrix_name: "docker" - matrix_spec: - ruby: ["ruby-2.7"] - mongodb-version: "4.2" - topology: standalone - os: ubuntu1804 - preload: nopreload - display_name: "Docker ${os} ${mongodb-version} ${topology} ${ruby} ${preload}" - tasks: - - name: "test-docker" - - - matrix_name: "mongo-latest" + - matrix_name: "auth/ssl" matrix_spec: auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1"] - mongodb-version: ['latest'] + ruby: "ruby-3.1" + mongodb-version: "6.0" topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + os: rhel8 + display_name: ${auth-and-ssl} ${ruby} ${topology} tasks: - name: "test-mlaunch" - - matrix_name: "mongo-6.0" + # 'latest' is broken until the QE2 changes are merged (RUBY-3211, RUBY-3226) + - matrix_name: "mongo-recent" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1"] + ruby: ["ruby-3.1", "jruby-9.3"] + #mongodb-version: ['latest', '6.0'] mongodb-version: ['6.0'] topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + os: ['rhel8', 'ubuntu2204'] + display_name: "${mongodb-version} ${os} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "mongo-5.3" + # Only JRuby-9.4 is built for arm in the current toolchain, but 9.4 has other + # issues. Either we fix those issues, or we build 9.3 for arm in the toolchain, + # and hope it fixes the issues... Until then, we can only test MRI ruby on arm. + # + # further: 'latest' is broken until the QE2 changes are merged (RUBY-3211, + # RUBY-3226) + - matrix_name: "mongo-recent-arm" matrix_spec: - auth-and-ssl: noauth-and-nossl - ruby: ["ruby-3.1"] - mongodb-version: ['5.3'] + ruby: "ruby-3.1" + #mongodb-version: ['latest', '6.0'] + mongodb-version: ['6.0'] topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + os: 'rhel8-arm' + display_name: "${mongodb-version} ${os} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "mongo-4.2" + - matrix_name: "mongo-5.x" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-2.7", "ruby-2.5"] - mongodb-version: ['4.2'] + ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + mongodb-version: ['5.3', '5.0'] topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "mongo-4.0" + - matrix_name: "mongo-4.x" matrix_spec: - auth-and-ssl: ["noauth-and-ssl", "auth-and-nossl"] - ruby: "ruby-2.6" - mongodb-version: ['4.0'] + ruby: ["ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] + mongodb-version: ['4.4', '4.2', '4.0'] topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "mongo-3.6" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] ruby: "ruby-2.5" mongodb-version: ['3.6'] topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1604 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "mongo-4.4" - matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.0", "ruby-2.7"] - mongodb-version: '4.4' - topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "mongo-5.0" - matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1", "ruby-2.7"] - mongodb-version: '5.0' - topology: '*' - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "single-lb" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.0"] - mongodb-version: '5.0' + ruby: "ruby-3.1" + mongodb-version: "6.0" topology: load-balanced single-mongos: single-mongos - os: ubuntu2004 + os: rhel8 display_name: "${mongodb-version} ${topology} single-lb ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "mongo-5.0-api-version" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1"] + ruby: "ruby-3.1" mongodb-version: '5.0' topology: standalone api-version-required: yes - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} api-version-required ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "single-mongos" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: "ruby-2.6" - mongodb-version: ['4.0', '4.2', '4.4'] + ruby: "ruby-3.1" + mongodb-version: "6.0" topology: "sharded-cluster" single-mongos: single-mongos - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} single-mongos ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" @@ -1209,10 +1161,10 @@ buildvariants: - matrix_name: "no-retry-reads" matrix_spec: retry-reads: no-retry-reads - ruby: "ruby-2.6" - mongodb-version: ["4.0"] + ruby: "ruby-3.1" + mongodb-version: "6.0" topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} ${retry-reads} ${ruby}" tasks: - name: "test-mlaunch" @@ -1220,10 +1172,10 @@ buildvariants: - matrix_name: "no-retry-writes" matrix_spec: retry-writes: no-retry-writes - ruby: ["ruby-2.7"] - mongodb-version: ["4.0"] + ruby: "ruby-3.1" + mongodb-version: "6.0" topology: [replica-set, sharded-cluster] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} ${retry-writes} ${ruby}" tasks: - name: "test-mlaunch" @@ -1234,18 +1186,18 @@ buildvariants: mongodb-version: ['3.6', '4.0'] topology: ["standalone", "replica-set", "sharded-cluster"] storage-engine: mmapv1 - os: ubuntu1604 + os: rhel8 display_name: "${mongodb-version} ${topology} mmapv1 ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "lint-5.0" + - matrix_name: "lint" matrix_spec: lint: on - ruby: ["ruby-3.1", "ruby-2.7"] - mongodb-version: ["5.0"] + ruby: "ruby-3.1" + mongodb-version: "6.0" topology: '*' - os: ubuntu2004 + os: rhel8 display_name: "${mongodb-version} ${topology} ${lint} ${ruby}" tasks: - name: "test-mlaunch" @@ -1253,10 +1205,10 @@ buildvariants: - matrix_name: "fork" matrix_spec: fork: on - ruby: ["ruby-3.0", "ruby-2.7"] - mongodb-version: ["4.4"] + ruby: "ruby-3.1" + mongodb-version: "6.0" topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} fork ${ruby}" tasks: - name: "test-mlaunch" @@ -1264,10 +1216,10 @@ buildvariants: - matrix_name: "solo" matrix_spec: solo: on - ruby: [ruby-3.1, ruby-3.0, ruby-2.7, ruby-2.6, ruby-2.5, jruby-9.2, jruby-9.3] - mongodb-version: ["4.4"] + ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + mongodb-version: "6.0" topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} solo ${ruby}" tasks: - name: "test-mlaunch" @@ -1278,7 +1230,7 @@ buildvariants: ruby: ["ruby-2.7"] mongodb-version: ['4.2', '4.0', '3.6'] topology: replica-set - os: ubuntu1604 + os: rhel8 display_name: "${mongodb-version} ${topology} stress ${ruby}" tasks: - name: "test-mlaunch" @@ -1286,392 +1238,189 @@ buildvariants: - matrix_name: "stress" matrix_spec: stress: on - ruby: ["ruby-2.7"] - mongodb-version: ['5.0', '4.4'] + ruby: "ruby-3.1" + mongodb-version: ["6.0", "5.3"] topology: replica-set - os: ubuntu2004 + os: rhel8 display_name: "${mongodb-version} ${topology} stress ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "x509-tests" - matrix_spec: - auth-and-ssl: "x509" - ruby: "ruby-2.7" - mongodb-version: "4.2" - topology: standalone - os: ubuntu1804 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" + # The X.509 tests are failing with exceptions being expected, but never + # raised. (RUBY-3267) + # + # - matrix_name: "x509-tests" + # matrix_spec: + # auth-and-ssl: "x509" + # ruby: "ruby-3.1" + # # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure + # # the server for certain auth mechanisms. Once run-tests.sh is made smart + # # enough to install mongosh, and then use either mongo or mongosh + # # (depending on server version and what's available), we can bump this to + # # the latest stable db version. + # mongodb-version: "5.3" + # topology: standalone + # os: rhel8 + # display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + # tasks: + # - name: "test-mlaunch" - matrix_name: "jruby-auth" matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: "jruby-9.3" - mongodb-version: "4.2" + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: jruby-9.3 + mongodb-version: "6.0" topology: ["standalone", "replica-set", "sharded-cluster"] - os: rhel80 + os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "jruby-noauth" + - matrix_name: "zlib" matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "jruby-9.2" - mongodb-version: "3.6" - topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zlib-auth" - matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-2.7"] - mongodb-version: "4.4" + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + mongodb-version: "6.0" topology: "replica-set" compressor: 'zlib' - os: ubuntu1804 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zlib-auth-3.0" - matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-3.0"] - mongodb-version: "4.4" - topology: "standalone" - compressor: 'zlib' - os: ubuntu2004 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zlib-noauth" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "ruby-2.5" - mongodb-version: "3.6" - topology: replica-set - compressor: 'zlib' - os: ubuntu1604 + os: rhel8 display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "snappy-auth" + - matrix_name: "snappy" matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-2.7"] - mongodb-version: "4.4" + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + mongodb-version: "6.0" topology: "replica-set" compressor: 'snappy' - os: ubuntu1804 + os: rhel8 display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "snappy-auth-3" - matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-3.1"] - mongodb-version: "4.4" - topology: "standalone" - compressor: 'snappy' - os: ubuntu2004 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "snappy-noauth" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "ruby-2.5" - mongodb-version: ["3.6"] - topology: replica-set - compressor: 'snappy' - os: ubuntu1604 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - -# https://jira.mongodb.org/browse/RUBY-2723 -# Enable or remove when the ticket is resolved. -# - matrix_name: "snappy-jruby" -# matrix_spec: -# ruby: jruby-9.2 -# mongodb-version: "4.4" -# topology: replica-set -# compressor: 'snappy' -# os: ubuntu1804 -# display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" -# tasks: -# - name: "test-mlaunch" - + # the zstd-ruby gem does not support JRuby (explicitly). However, there is + # apparently a zstd-jni gem for JRuby that we could investigate here; if + # this test is ever supported to support jruby, the `sample_mri_rubies` + # reference should be replaced with `sample_rubies`. - matrix_name: "zstd-auth" matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-2.7"] - mongodb-version: "4.4" + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: ["ruby-3.1", "ruby-2.7"] + mongodb-version: "6.0" topology: "replica-set" compressor: 'zstd' - os: ubuntu1804 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zstd-auth-3.0" - matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-3.0"] - mongodb-version: "4.4" - topology: "standalone" - compressor: 'zstd' - os: ubuntu1804 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zstd-noauth" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "ruby-2.5" - mongodb-version: "4.2" - topology: replica-set - compressor: 'zstd' - os: ubuntu1804 + os: rhel8 display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" -# - matrix_name: "zstd-jruby" -# matrix_spec: -# ruby: jruby-9.2 -# mongodb-version: "4.4" -# topology: replica-set -# compressor: 'zstd' -# os: ubuntu1804 -# display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" -# tasks: -# - name: "test-mlaunch" - -# - matrix_name: "ruby-head" -# matrix_spec: -# auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] -# ruby: ["ruby-head"] -# mongodb-version: "4.2" -# topology: "*" -# os: ubuntu1804 -# display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" -# tasks: -# - name: "test-mlaunch" - - matrix_name: "activesupport" matrix_spec: - ruby: ["ruby-3.0", "ruby-2.7"] - mongodb-version: '4.4' + ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + mongodb-version: "6.0" topology: replica-set as: as - os: ubuntu1804 + os: rhel8 display_name: "AS ${mongodb-version} ${topology} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "bson" matrix_spec: - ruby: [ruby-3.1, ruby-2.7, jruby-9.2] - mongodb-version: '4.4' + ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + mongodb-version: "6.0" topology: replica-set bson: "*" - os: rhel80 + os: rhel8 display_name: "AS ${mongodb-version} ${topology} ${ruby} ${bson}" tasks: - name: "test-mlaunch" - - matrix_name: "kerberos-integration-ubuntu" - matrix_spec: - # Other Rubies segfault - ruby: ruby-2.5 - display_name: "Kerberos integration Ubuntu ${ruby}" - run_on: - - ubuntu1804-small - tasks: - - name: "test-kerberos-integration" - - # All skipped because of segfaults - #- matrix_name: "kerberos-integration-rhel" - # matrix_spec: - # # Other Rubies segfault - # ruby: [ruby-2.7] - # os: ubuntu1804 - # display_name: "Kerberos integration RHEL ${ruby} ${os}" - # tasks: - # - name: "test-kerberos-integration" - - # Kerberos integration tests on debian are omitted due to segfaults: - # https://jira.mongodb.org/browse/RUBY-1534 + # kerberos integration tests are broken (RUBY-3266) + # - matrix_name: "kerberos-integration" + # matrix_spec: + # ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + # os: rhel8 + # display_name: "Kerberos integration ${os} ${ruby}" + # tasks: + # - name: "test-kerberos-integration" - matrix_name: "kerberos-unit" matrix_spec: - ruby: "ruby-2.6" - mongodb-version: "4.2" + ruby: "ruby-3.1" + mongodb-version: "6.0" topology: standalone - os: ubuntu1804 + os: rhel8 auth-and-ssl: kerberos display_name: "Kerberos Tests" tasks: - name: "test-kerberos" - - matrix_name: "fle-latest" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1] - topology: [replica-set, sharded-cluster] - mongodb-version: ['latest'] - os: ubuntu2004 - fle: helper - docker-distro: ubuntu2004 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - - matrix_name: "fle-6.0" + # 'latest' is broken until the QE2 changes are merged (RUBY-3211, RUBY-3226) + # + # - matrix_name: "fle-latest" + # matrix_spec: + # auth-and-ssl: "noauth-and-nossl" + # ruby: "ruby-3.1" + # topology: [replica-set, sharded-cluster] + # mongodb-version: [ 'latest' ] + # os: rhel8 + # fle: helper + # display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" + # tasks: + # - name: "test-fle" + + - matrix_name: "fle" matrix_spec: auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1, ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: [replica-set, sharded-cluster] - mongodb-version: ['6.0'] - os: ubuntu2004 - fle: helper - docker-distro: ubuntu2004 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - - matrix_name: "fle-4.4" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1] - topology: standalone - mongodb-version: ['4.4'] - os: ubuntu2004 - fle: helper - docker-distro: ubuntu1804 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - - matrix_name: "fle-4.2" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-2.5] - topology: [standalone, replica-set, sharded-cluster] - mongodb-version: ["4.2"] - os: ubuntu2004 - fle: helper - docker-distro: ubuntu1804 - display_name: "FLE: ${topology} ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - - matrix_name: "fle-4.0" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-2.6] - topology: standalone - mongodb-version: ["4.0"] - os: ubuntu2004 - fle: path - docker-distro: ubuntu1804 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - # https://jira.mongodb.org/browse/RUBY-3217 - - matrix_name: "fle-jruby" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [jruby-9.2, jruby-9.3] - topology: standalone - mongodb-version: ['5.0'] - os: ubuntu2004 + mongodb-version: [ '6.0', '4.4', '4.2', '4.0' ] + os: rhel8 fle: helper - docker-distro: ubuntu2004 - display_name: "FLE: ${mongodb-version} ${ruby}" + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" tasks: - - name: "test-fle-via-docker" + - name: "test-fle" - matrix_name: aws-auth-regular matrix_spec: - auth-and-ssl: aws-regular - ruby: [ruby-2.7] + auth-and-ssl: [ aws-regular, aws-assume-role, aws-ec2, aws-ecs, aws-web-identity ] + ruby: "ruby-3.1" topology: standalone - mongodb-version: '4.4' - os: ubuntu1804 - display_name: "AWS auth regular: ${mongodb-version} ${ruby}" + # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure + # the server for certain auth mechanisms. Once run-tests.sh is made smart + # enough to install mongosh, and then use either mongo or mongosh + # (depending on server version and what's available), we can bump this to + # the latest stable db version. + mongodb-version: "5.3" + os: rhel8 + display_name: "AWS ${auth-and-ssl} ${mongodb-version} ${ruby}" tasks: - name: "test-aws-auth" - - matrix_name: aws-auth-temporary - matrix_spec: - auth-and-ssl: [aws-assume-role, aws-ec2, aws-ecs, aws-web-identity] - ruby: [ruby-3.1] - topology: standalone - mongodb-version: '4.4' - os: ubuntu1804 - display_name: "AWS auth temporary: ${auth-and-ssl} ${mongodb-version} ${ruby}" - tasks: - - name: "test-aws-auth" - - # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 - # TODO Add ruby-3.0: RUBY-2717 - - - matrix_name: ocsp-verifier - ruby-2.5 + - matrix_name: ocsp-verifier matrix_spec: ocsp-verifier: true - ruby: ruby-2.5 + # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 + ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 - docker-distro: debian10 - display_name: 'OCSP verifier: ${mongodb-version} ${ruby}' - tasks: &1 - - name: test-via-docker - - matrix_name: ocsp-verifier - ruby-2.6 - matrix_spec: - ocsp-verifier: true - ruby: ruby-2.6 - topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 - docker-distro: debian10 - display_name: 'OCSP verifier: ${mongodb-version} ${ruby}' - tasks: *1 - - matrix_name: ocsp-verifier - ruby-2.7 - matrix_spec: - ocsp-verifier: true - ruby: ruby-2.7 - topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 - docker-distro: debian10 - display_name: 'OCSP verifier: ${mongodb-version} ${ruby}' - tasks: *1 - + mongodb-version: "6.0" + os: rhel8 + display_name: "OCSP verifier: ${mongodb-version} ${ruby}" + tasks: + - name: test-mlaunch - matrix_name: ocsp-must-staple matrix_spec: ocsp-algorithm: ecdsa ocsp-must-staple: on ocsp-delegate: on - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 auth-and-ssl: noauth-and-ssl display_name: "OCSP integration - must staple: ${mongodb-version} ${ruby}" tasks: @@ -1681,10 +1430,10 @@ buildvariants: matrix_spec: ocsp-algorithm: rsa ocsp-status: unknown - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 auth-and-ssl: noauth-and-ssl display_name: "OCSP integration - unknown: ${mongodb-version} ${ruby}" tasks: @@ -1697,10 +1446,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "none" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1711,10 +1460,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "none" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1725,10 +1474,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: fail extra-uri-options: "none" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1739,10 +1488,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1753,10 +1502,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1767,10 +1516,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1781,10 +1530,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1795,10 +1544,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1809,10 +1558,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.1", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1829,21 +1578,21 @@ buildvariants: ocsp-status: [valid, unknown] ocsp-delegate: '*' ocsp-connectivity: pass - ruby: jruby-9.2 + ruby: jruby-9.3 topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "6.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch - matrix_name: testgcpkms-variant matrix_spec: - ruby: ruby-3.0 + ruby: "ruby-3.1" fle: helper topology: standalone - os: debian11 - mongodb-version: 6.0 + os: rhel8 + mongodb-version: "6.0" display_name: "GCP KMS" tasks: - name: testgcpkms_task_group @@ -1854,7 +1603,7 @@ buildvariants: ruby: ruby-3.0 fle: helper topology: standalone - os: debian11 + os: debian11 # could eventually look at updating this to rhel80 mongodb-version: 6.0 display_name: "AZURE KMS" tasks: @@ -1863,21 +1612,19 @@ buildvariants: - matrix_name: "atlas" matrix_spec: - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + os: rhel8 display_name: "Atlas tests ${ruby}" - run_on: - - ubuntu1804-small tasks: - name: test-atlas - matrix_name: "serverless" matrix_spec: # https://jira.mongodb.org/browse/RUBY-3217 - # ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.3"] - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1"] + # ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] fle: path + os: rhel8 display_name: "Atlas serverless ${ruby} single mongos" - run_on: - - ubuntu2004-small tasks: - name: test-serverless diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index d925a355b8..431da45a63 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -138,6 +138,14 @@ axes: - id: "ruby" display_name: Ruby Version values: + - id: "ruby-3.2" + display_name: ruby-3.2 + variables: + RVM_RUBY: "ruby-3.2" + - id: "ruby-3.1" + display_name: ruby-3.1 + variables: + RVM_RUBY: "ruby-3.1" - id: "ruby-3.1" display_name: ruby-3.1 variables: @@ -170,36 +178,31 @@ axes: display_name: jruby-9.3 variables: RVM_RUBY: "jruby-9.3" - + - id: "jruby-9.4" + display_name: jruby-9.4 + variables: + RVM_RUBY: "jruby-9.4" + - id: "os" display_name: OS values: - id: debian11 display_name: "Debian 11" run_on: debian11-small - - id: ubuntu2004 - display_name: "Ubuntu 20.04" - run_on: ubuntu2004-small - - id: ubuntu1804 - display_name: "Ubuntu 18.04" - run_on: ubuntu1804-small - - id: ubuntu1604 - display_name: "Ubuntu 16.04" - run_on: ubuntu1604-small - - id: rhel80 + - id: ubuntu2204 + display_name: "Ubuntu 22.04" + run_on: ubuntu2204-small + - id: rhel8 display_name: "RHEL 8" run_on: rhel80-small - - id: rhel70 - display_name: "RHEL 7" - run_on: rhel70-small - - id: rhel62 - display_name: "RHEL 6" - run_on: rhel62-small + - id: rhel8-arm + display_name: "RHEL 8 ARM64" + run_on: rhel82-arm64-small - id: docker-distro display_name: Docker Distro values: - <% %w(debian92 debian10 ubuntu1804 ubuntu2004).each do |distro| %> + <% %w(debian11 ubuntu2204).each do |distro| %> - id: <%= distro %> display_name: <%= distro %> variables: @@ -299,7 +302,7 @@ axes: values: - id: mmapv1 display_name: MMAPv1 - run_on: ubuntu1604-small + run_on: rhel80-small variables: MMAPV1: 'true' diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index e2171b1bd8..fe04de6837 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -1,148 +1,150 @@ -<% tt = %w(standalone replica-set sharded-cluster) %> +<% + topologies = %w( standalone replica-set sharded-cluster ) -buildvariants: - - matrix_name: "docker" - matrix_spec: - ruby: ["ruby-2.7"] - mongodb-version: "4.2" - topology: standalone - os: ubuntu1804 - preload: preload - display_name: "Docker ${os} ${mongodb-version} ${topology} ${ruby} ${preload}" - tasks: - - name: "test-docker" + # ruby-3.2 is available in the toolchain, but tests break. It will + # require some investigation. Once available: + # 1. set `latest_ruby` to ruby-3.2 + # 2. add ruby-3.2 to `recent_rubies` and `supported_mri_rubies` + # 3. replace ruby-3.1 with ruby-3.2 in `sample_mri_rubies`. - - matrix_name: "docker" - matrix_spec: - ruby: ["ruby-2.7"] - mongodb-version: "4.2" - topology: standalone - os: ubuntu1804 - preload: nopreload - display_name: "Docker ${os} ${mongodb-version} ${topology} ${ruby} ${preload}" - tasks: - - name: "test-docker" + # jruby-9.4 is available in the toolchain, but tests break. It will + # require some investigation. Once available: + # 1. replace jruby-9.3 with jruby-9.4 in `recent_rubies` and + # `sample_rubies`. + # 2. add jruby-9.4 to the front of `jrubies`. + + # latest_ruby = the most recently released, stable version of Ruby + # (make sure this version is being built by 10gen/mongo-ruby-toolchain) + latest_ruby = "ruby-3.1".inspect # so it gets quoted as a string + + # these are used for testing against a few recent ruby versions + recent_rubies = %w( ruby-3.1 jruby-9.3 ) + + # this is a list of the most most recent 3.x and 2.x MRI ruby versions + sample_mri_rubies = %w( ruby-3.1 ruby-2.7 ) + + # as above, but including the most recent JRuby release + sample_rubies = sample_mri_rubies + %w( jruby-9.3 ) + + # older Ruby versions provided by 10gen/mongo-ruby-toolchain + older_rubies = %w( ruby-3.0 ruby-2.7 ruby-2.6 ruby-2.5 ) + + # all supported JRuby versions provided by 10gen/mongo-ruby-toolchain + jrubies = %w( jruby-9.3 jruby-9.2 ) + + supported_mri_rubies = %w( ruby-3.1 ruby-3.0 + ruby-2.7 ruby-2.6 ruby-2.5 ) + + supported_rubies = supported_mri_rubies + jrubies + + # The latest stable version of MongoDB + latest_stable_mdb = "6.0".inspect # so it gets quoted as a string + + # A few of the most recent MongoDB versions + recent_mdb = %w( 6.0 5.3 ) + latest_5x_mdb = "5.3".inspect # so it gets quoted as a string +%> - - matrix_name: "mongo-latest" +buildvariants: + - matrix_name: "auth/ssl" matrix_spec: auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1"] - mongodb-version: ['latest'] - topology: <%= tt %> - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> + topology: <%= topologies %> + os: rhel8 + display_name: ${auth-and-ssl} ${ruby} ${topology} tasks: - name: "test-mlaunch" - - matrix_name: "mongo-6.0" + # 'latest' is broken until the QE2 changes are merged (RUBY-3211, RUBY-3226) + - matrix_name: "mongo-recent" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1"] + ruby: <%= recent_rubies %> + #mongodb-version: ['latest', '6.0'] mongodb-version: ['6.0'] - topology: <%= tt %> - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + topology: <%= topologies %> + os: ['rhel8', 'ubuntu2204'] + display_name: "${mongodb-version} ${os} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "mongo-5.3" + # Only JRuby-9.4 is built for arm in the current toolchain, but 9.4 has other + # issues. Either we fix those issues, or we build 9.3 for arm in the toolchain, + # and hope it fixes the issues... Until then, we can only test MRI ruby on arm. + # + # further: 'latest' is broken until the QE2 changes are merged (RUBY-3211, + # RUBY-3226) + - matrix_name: "mongo-recent-arm" matrix_spec: - auth-and-ssl: noauth-and-nossl - ruby: ["ruby-3.1"] - mongodb-version: ['5.3'] - topology: <%= tt %> - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + ruby: <%= latest_ruby %> + #mongodb-version: ['latest', '6.0'] + mongodb-version: ['6.0'] + topology: <%= topologies %> + os: 'rhel8-arm' + display_name: "${mongodb-version} ${os} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "mongo-4.2" + - matrix_name: "mongo-5.x" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-2.7", "ruby-2.5"] - mongodb-version: ['4.2'] - topology: <%= tt %> - os: ubuntu1804 + ruby: <%= sample_rubies %> + mongodb-version: ['5.3', '5.0'] + topology: <%= topologies %> + os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "mongo-4.0" + - matrix_name: "mongo-4.x" matrix_spec: - auth-and-ssl: ["noauth-and-ssl", "auth-and-nossl"] - ruby: "ruby-2.6" - mongodb-version: ['4.0'] - topology: <%= tt %> - os: ubuntu1804 + ruby: <%= older_rubies %> + mongodb-version: ['4.4', '4.2', '4.0'] + topology: <%= topologies %> + os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "mongo-3.6" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] ruby: "ruby-2.5" mongodb-version: ['3.6'] - topology: <%= tt %> - os: ubuntu1604 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "mongo-4.4" - matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.0", "ruby-2.7"] - mongodb-version: '4.4' - topology: <%= tt %> - os: ubuntu1804 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "mongo-5.0" - matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1", "ruby-2.7"] - mongodb-version: '5.0' - topology: '*' - os: ubuntu1804 + topology: <%= topologies %> + os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "single-lb" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.0"] - mongodb-version: '5.0' + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> topology: load-balanced single-mongos: single-mongos - os: ubuntu2004 + os: rhel8 display_name: "${mongodb-version} ${topology} single-lb ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "mongo-5.0-api-version" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1"] + ruby: <%= latest_ruby %> mongodb-version: '5.0' topology: standalone api-version-required: yes - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} api-version-required ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "single-mongos" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: "ruby-2.6" - mongodb-version: ['4.0', '4.2', '4.4'] + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> topology: "sharded-cluster" single-mongos: single-mongos - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} single-mongos ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" @@ -150,10 +152,10 @@ buildvariants: - matrix_name: "no-retry-reads" matrix_spec: retry-reads: no-retry-reads - ruby: "ruby-2.6" - mongodb-version: ["4.0"] - topology: <%= tt %> - os: ubuntu1804 + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> + topology: <%= topologies %> + os: rhel8 display_name: "${mongodb-version} ${topology} ${retry-reads} ${ruby}" tasks: - name: "test-mlaunch" @@ -161,10 +163,10 @@ buildvariants: - matrix_name: "no-retry-writes" matrix_spec: retry-writes: no-retry-writes - ruby: ["ruby-2.7"] - mongodb-version: ["4.0"] + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> topology: [replica-set, sharded-cluster] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} ${retry-writes} ${ruby}" tasks: - name: "test-mlaunch" @@ -173,20 +175,20 @@ buildvariants: matrix_spec: ruby: "ruby-2.5" mongodb-version: ['3.6', '4.0'] - topology: <%= tt %> + topology: <%= topologies %> storage-engine: mmapv1 - os: ubuntu1604 + os: rhel8 display_name: "${mongodb-version} ${topology} mmapv1 ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "lint-5.0" + - matrix_name: "lint" matrix_spec: lint: on - ruby: ["ruby-3.1", "ruby-2.7"] - mongodb-version: ["5.0"] + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> topology: '*' - os: ubuntu2004 + os: rhel8 display_name: "${mongodb-version} ${topology} ${lint} ${ruby}" tasks: - name: "test-mlaunch" @@ -194,10 +196,10 @@ buildvariants: - matrix_name: "fork" matrix_spec: fork: on - ruby: ["ruby-3.0", "ruby-2.7"] - mongodb-version: ["4.4"] - topology: <%= tt %> - os: ubuntu1804 + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> + topology: <%= topologies %> + os: rhel8 display_name: "${mongodb-version} ${topology} fork ${ruby}" tasks: - name: "test-mlaunch" @@ -205,10 +207,10 @@ buildvariants: - matrix_name: "solo" matrix_spec: solo: on - ruby: [ruby-3.1, ruby-3.0, ruby-2.7, ruby-2.6, ruby-2.5, jruby-9.2, jruby-9.3] - mongodb-version: ["4.4"] - topology: <%= tt %> - os: ubuntu1804 + ruby: <%= supported_rubies %> + mongodb-version: <%= latest_stable_mdb %> + topology: <%= topologies %> + os: rhel8 display_name: "${mongodb-version} ${topology} solo ${ruby}" tasks: - name: "test-mlaunch" @@ -219,7 +221,7 @@ buildvariants: ruby: ["ruby-2.7"] mongodb-version: ['4.2', '4.0', '3.6'] topology: replica-set - os: ubuntu1604 + os: rhel8 display_name: "${mongodb-version} ${topology} stress ${ruby}" tasks: - name: "test-mlaunch" @@ -227,372 +229,189 @@ buildvariants: - matrix_name: "stress" matrix_spec: stress: on - ruby: ["ruby-2.7"] - mongodb-version: ['5.0', '4.4'] + ruby: <%= latest_ruby %> + mongodb-version: <%= recent_mdb %> topology: replica-set - os: ubuntu2004 + os: rhel8 display_name: "${mongodb-version} ${topology} stress ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "x509-tests" - matrix_spec: - auth-and-ssl: "x509" - ruby: "ruby-2.7" - mongodb-version: "4.2" - topology: standalone - os: ubuntu1804 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" + # The X.509 tests are failing with exceptions being expected, but never + # raised. (RUBY-3267) + # + # - matrix_name: "x509-tests" + # matrix_spec: + # auth-and-ssl: "x509" + # ruby: <%= latest_ruby %> + # # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure + # # the server for certain auth mechanisms. Once run-tests.sh is made smart + # # enough to install mongosh, and then use either mongo or mongosh + # # (depending on server version and what's available), we can bump this to + # # the latest stable db version. + # mongodb-version: <%= latest_5x_mdb %> + # topology: standalone + # os: rhel8 + # display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + # tasks: + # - name: "test-mlaunch" - matrix_name: "jruby-auth" matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: "jruby-9.3" - mongodb-version: "4.2" - topology: <%= tt %> - os: rhel80 + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: <%= jrubies.first %> + mongodb-version: <%= latest_stable_mdb %> + topology: <%= topologies %> + os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "jruby-noauth" + - matrix_name: "zlib" matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "jruby-9.2" - mongodb-version: "3.6" - topology: <%= tt %> - os: ubuntu1804 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zlib-auth" - matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-2.7"] - mongodb-version: "4.4" + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: <%= sample_rubies %> + mongodb-version: <%= latest_stable_mdb %> topology: "replica-set" compressor: 'zlib' - os: ubuntu1804 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zlib-auth-3.0" - matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-3.0"] - mongodb-version: "4.4" - topology: "standalone" - compressor: 'zlib' - os: ubuntu2004 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zlib-noauth" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "ruby-2.5" - mongodb-version: "3.6" - topology: replica-set - compressor: 'zlib' - os: ubuntu1604 + os: rhel8 display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "snappy-auth" + - matrix_name: "snappy" matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-2.7"] - mongodb-version: "4.4" + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: <%= sample_rubies %> + mongodb-version: <%= latest_stable_mdb %> topology: "replica-set" compressor: 'snappy' - os: ubuntu1804 + os: rhel8 display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "snappy-auth-3" - matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-3.1"] - mongodb-version: "4.4" - topology: "standalone" - compressor: 'snappy' - os: ubuntu2004 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "snappy-noauth" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "ruby-2.5" - mongodb-version: ["3.6"] - topology: replica-set - compressor: 'snappy' - os: ubuntu1604 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - -# https://jira.mongodb.org/browse/RUBY-2723 -# Enable or remove when the ticket is resolved. -# - matrix_name: "snappy-jruby" -# matrix_spec: -# ruby: jruby-9.2 -# mongodb-version: "4.4" -# topology: replica-set -# compressor: 'snappy' -# os: ubuntu1804 -# display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" -# tasks: -# - name: "test-mlaunch" - + # the zstd-ruby gem does not support JRuby (explicitly). However, there is + # apparently a zstd-jni gem for JRuby that we could investigate here; if + # this test is ever supported to support jruby, the `sample_mri_rubies` + # reference should be replaced with `sample_rubies`. - matrix_name: "zstd-auth" matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-2.7"] - mongodb-version: "4.4" + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: <%= sample_mri_rubies %> + mongodb-version: <%= latest_stable_mdb %> topology: "replica-set" compressor: 'zstd' - os: ubuntu1804 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "zstd-auth-3.0" - matrix_spec: - auth-and-ssl: "auth-and-ssl" - ruby: ["ruby-3.0"] - mongodb-version: "4.4" - topology: "standalone" - compressor: 'zstd' - os: ubuntu1804 + os: rhel8 display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "zstd-noauth" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "ruby-2.5" - mongodb-version: "4.2" - topology: replica-set - compressor: 'zstd' - os: ubuntu1804 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - -# - matrix_name: "zstd-jruby" -# matrix_spec: -# ruby: jruby-9.2 -# mongodb-version: "4.4" -# topology: replica-set -# compressor: 'zstd' -# os: ubuntu1804 -# display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" -# tasks: -# - name: "test-mlaunch" - -# - matrix_name: "ruby-head" -# matrix_spec: -# auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] -# ruby: ["ruby-head"] -# mongodb-version: "4.2" -# topology: "*" -# os: ubuntu1804 -# display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" -# tasks: -# - name: "test-mlaunch" - - matrix_name: "activesupport" matrix_spec: - ruby: ["ruby-3.0", "ruby-2.7"] - mongodb-version: '4.4' + ruby: <%= sample_rubies %> + mongodb-version: <%= latest_stable_mdb %> topology: replica-set as: as - os: ubuntu1804 + os: rhel8 display_name: "AS ${mongodb-version} ${topology} ${ruby}" tasks: - name: "test-mlaunch" - matrix_name: "bson" matrix_spec: - ruby: [ruby-3.1, ruby-2.7, jruby-9.2] - mongodb-version: '4.4' + ruby: <%= sample_rubies %> + mongodb-version: <%= latest_stable_mdb %> topology: replica-set bson: "*" - os: rhel80 + os: rhel8 display_name: "AS ${mongodb-version} ${topology} ${ruby} ${bson}" tasks: - name: "test-mlaunch" - - matrix_name: "kerberos-integration-ubuntu" - matrix_spec: - # Other Rubies segfault - ruby: ruby-2.5 - display_name: "Kerberos integration Ubuntu ${ruby}" - run_on: - - ubuntu1804-small - tasks: - - name: "test-kerberos-integration" - - # All skipped because of segfaults - #- matrix_name: "kerberos-integration-rhel" - # matrix_spec: - # # Other Rubies segfault - # ruby: [ruby-2.7] - # os: ubuntu1804 - # display_name: "Kerberos integration RHEL ${ruby} ${os}" - # tasks: - # - name: "test-kerberos-integration" - - # Kerberos integration tests on debian are omitted due to segfaults: - # https://jira.mongodb.org/browse/RUBY-1534 + # kerberos integration tests are broken (RUBY-3266) + # - matrix_name: "kerberos-integration" + # matrix_spec: + # ruby: <%= sample_rubies %> + # os: rhel8 + # display_name: "Kerberos integration ${os} ${ruby}" + # tasks: + # - name: "test-kerberos-integration" - matrix_name: "kerberos-unit" matrix_spec: - ruby: "ruby-2.6" - mongodb-version: "4.2" + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> topology: standalone - os: ubuntu1804 + os: rhel8 auth-and-ssl: kerberos display_name: "Kerberos Tests" tasks: - name: "test-kerberos" - - matrix_name: "fle-latest" + # 'latest' is broken until the QE2 changes are merged (RUBY-3211, RUBY-3226) + # + # - matrix_name: "fle-latest" + # matrix_spec: + # auth-and-ssl: "noauth-and-nossl" + # ruby: <%= latest_ruby %> + # topology: [replica-set, sharded-cluster] + # mongodb-version: [ 'latest' ] + # os: rhel8 + # fle: helper + # display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" + # tasks: + # - name: "test-fle" + + - matrix_name: "fle" matrix_spec: auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1] + ruby: <%= sample_mri_rubies %> topology: [replica-set, sharded-cluster] - mongodb-version: ['latest'] - os: ubuntu2004 + mongodb-version: [ '6.0', '4.4', '4.2', '4.0' ] + os: rhel8 fle: helper - docker-distro: ubuntu2004 - display_name: "FLE: ${mongodb-version} ${ruby}" + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" tasks: - - name: "test-fle-via-docker" - - - matrix_name: "fle-6.0" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1, ruby-3.0, ruby-2.7] - topology: [replica-set, sharded-cluster] - mongodb-version: ['6.0'] - os: ubuntu2004 - fle: helper - docker-distro: ubuntu2004 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - - matrix_name: "fle-4.4" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1] - topology: standalone - mongodb-version: ['4.4'] - os: ubuntu2004 - fle: helper - docker-distro: ubuntu1804 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - - matrix_name: "fle-4.2" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-2.5] - topology: [standalone, replica-set, sharded-cluster] - mongodb-version: ["4.2"] - os: ubuntu2004 - fle: helper - docker-distro: ubuntu1804 - display_name: "FLE: ${topology} ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - - matrix_name: "fle-4.0" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-2.6] - topology: standalone - mongodb-version: ["4.0"] - os: ubuntu2004 - fle: path - docker-distro: ubuntu1804 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" - - # https://jira.mongodb.org/browse/RUBY-3217 - - matrix_name: "fle-jruby" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [jruby-9.2, jruby-9.3] - topology: standalone - mongodb-version: ['5.0'] - os: ubuntu2004 - fle: helper - docker-distro: ubuntu2004 - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle-via-docker" + - name: "test-fle" - matrix_name: aws-auth-regular matrix_spec: - auth-and-ssl: aws-regular - ruby: [ruby-2.7] + auth-and-ssl: [ aws-regular, aws-assume-role, aws-ec2, aws-ecs, aws-web-identity ] + ruby: <%= latest_ruby %> topology: standalone - mongodb-version: '4.4' - os: ubuntu1804 - display_name: "AWS auth regular: ${mongodb-version} ${ruby}" + # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure + # the server for certain auth mechanisms. Once run-tests.sh is made smart + # enough to install mongosh, and then use either mongo or mongosh + # (depending on server version and what's available), we can bump this to + # the latest stable db version. + mongodb-version: <%= latest_5x_mdb %> + os: rhel8 + display_name: "AWS ${auth-and-ssl} ${mongodb-version} ${ruby}" tasks: - name: "test-aws-auth" - - matrix_name: aws-auth-temporary - matrix_spec: - auth-and-ssl: [aws-assume-role, aws-ec2, aws-ecs, aws-web-identity] - ruby: [ruby-3.1] - topology: standalone - mongodb-version: '4.4' - os: ubuntu1804 - display_name: "AWS auth temporary: ${auth-and-ssl} ${mongodb-version} ${ruby}" - tasks: - - name: "test-aws-auth" - - # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 - # TODO Add ruby-3.0: RUBY-2717 - <%|= standard_debian_rubies(%w(ruby-2.5 ruby-2.6 ruby-2.7), key: 'docker-distro') do %> - matrix_name: ocsp-verifier + - matrix_name: ocsp-verifier matrix_spec: ocsp-verifier: true # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 - ruby: [ruby-3.0, ruby-2.7, ruby-2.6, ruby-2.5] + ruby: <%= supported_mri_rubies %> topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: <%= latest_stable_mdb %> + os: rhel8 display_name: "OCSP verifier: ${mongodb-version} ${ruby}" tasks: - - name: test-via-docker - <%| end %> + - name: test-mlaunch - matrix_name: ocsp-must-staple matrix_spec: ocsp-algorithm: ecdsa ocsp-must-staple: on ocsp-delegate: on - ruby: [ruby-3.0, ruby-2.7] + ruby: <%= sample_mri_rubies %> topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: <%= latest_stable_mdb %> + os: rhel8 auth-and-ssl: noauth-and-ssl display_name: "OCSP integration - must staple: ${mongodb-version} ${ruby}" tasks: @@ -602,10 +421,10 @@ buildvariants: matrix_spec: ocsp-algorithm: rsa ocsp-status: unknown - ruby: [ruby-3.0, ruby-2.7] + ruby: <%= sample_mri_rubies %> topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: <%= latest_stable_mdb %> + os: rhel8 auth-and-ssl: noauth-and-ssl display_name: "OCSP integration - unknown: ${mongodb-version} ${ruby}" tasks: @@ -632,10 +451,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: <%= outcome %> extra-uri-options: "<%= extra_uri_options %>" - ruby: [ruby-3.0, ruby-2.7] + ruby: <%= sample_mri_rubies %> topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: <%= latest_stable_mdb %> + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -653,21 +472,21 @@ buildvariants: ocsp-status: [valid, unknown] ocsp-delegate: '*' ocsp-connectivity: pass - ruby: jruby-9.2 + ruby: <%= jrubies.first %> topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: <%= latest_stable_mdb %> + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch - matrix_name: testgcpkms-variant matrix_spec: - ruby: ruby-3.0 + ruby: <%= latest_ruby %> fle: helper topology: standalone - os: debian11 - mongodb-version: 6.0 + os: rhel8 + mongodb-version: <%= latest_stable_mdb %> display_name: "GCP KMS" tasks: - name: testgcpkms_task_group @@ -678,7 +497,7 @@ buildvariants: ruby: ruby-3.0 fle: helper topology: standalone - os: debian11 + os: debian11 # could eventually look at updating this to rhel80 mongodb-version: 6.0 display_name: "AZURE KMS" tasks: @@ -687,21 +506,19 @@ buildvariants: - matrix_name: "atlas" matrix_spec: - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.2"] + ruby: <%= supported_rubies %> + os: rhel8 display_name: "Atlas tests ${ruby}" - run_on: - - ubuntu1804-small tasks: - name: test-atlas - matrix_name: "serverless" matrix_spec: # https://jira.mongodb.org/browse/RUBY-3217 - # ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1", "jruby-9.3"] - ruby: ["ruby-2.5", "ruby-2.6", "ruby-2.7", "ruby-3.0", "ruby-3.1"] + # ruby: <%= supported_rubies %> + ruby: <%= supported_mri_rubies %> fle: path + os: rhel8 display_name: "Atlas serverless ${ruby} single mongos" - run_on: - - ubuntu2004-small tasks: - name: test-serverless diff --git a/.evergreen/run-tests-gcp.sh b/.evergreen/run-tests-gcp.sh index 0c84ebc765..baccf6404b 100755 --- a/.evergreen/run-tests-gcp.sh +++ b/.evergreen/run-tests-gcp.sh @@ -4,6 +4,7 @@ set -ex . `dirname "$0"`/../spec/shared/shlib/distro.sh . `dirname "$0"`/../spec/shared/shlib/set_env.sh +. `dirname "$0"`/../spec/shared/shlib/server.sh . `dirname "$0"`/functions.sh set_env_vars diff --git a/.evergreen/run-tests-kerberos-integration.sh b/.evergreen/run-tests-kerberos-integration.sh index 3d9eb49e73..e7002d898e 100755 --- a/.evergreen/run-tests-kerberos-integration.sh +++ b/.evergreen/run-tests-kerberos-integration.sh @@ -61,6 +61,22 @@ configure_kerberos_ip_addr echo "Install dependencies" export BUNDLE_GEMFILE=gemfiles/mongo_kerberos.gemfile bundle_install + +# need to build the native extension, since it doesn't seem to build correctly +# when installed via github. +curdir=`pwd` +cd `bundle info --path mongo_kerberos` + +# unset the BUNDLE_GEMFILE variable so the mongo_kerberos rakefile doesn't +# get confused by it... +saved_gemfile=$BUNDLE_GEMFILE +unset BUNDLE_GEMFILE + +bundle install +rake compile +cd $curdir +export BUNDLE_GEMFILE=$saved_gemfile + bundle list export MONGO_RUBY_DRIVER_KERBEROS=1 diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index faebcf2ca4..91171c1133 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -26,12 +26,18 @@ else exit -1 fi +if ! ( test -f /etc/os-release & grep -q ^ID.*rhel /etc/os-release & grep -q ^VERSION_ID.*8.0 /etc/os-release ); then + echo Serverless tests assume rhel80 + echo If this has changed, update .evergreen/run-tests-serverless.sh as necessary + exit -1 +fi + mkdir libmongocrypt cd libmongocrypt curl --retry 3 -fLo libmongocrypt-all.tar.gz "https://s3.amazonaws.com/mciuploads/libmongocrypt/all/master/latest/libmongocrypt-all.tar.gz" tar xf libmongocrypt-all.tar.gz -# We assume that serverless tests are always use ubuntu2004 -export LIBMONGOCRYPT_PATH=`pwd`/ubuntu2004-64/nocrypto/lib/libmongocrypt.so +# We assume that serverless tests always use rhel80 +export LIBMONGOCRYPT_PATH=`pwd`/rhel-80-64-bit/nocrypto/lib64/libmongocrypt.so cd - cd .evergreen/csfle diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 7209663dd1..a2203eb79a 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -47,6 +47,12 @@ if test "$DOCKER_PRELOAD" != 1; then install_mlaunch_venv fi +# Make sure cmake is installed (in case we need to install the libmongocrypt +# helper) +if [ "$FLE" = "helper" ]; then + install_cmake +fi + # Launching mongod under $MONGO_ORCHESTRATION_HOME # makes its log available through log collecting machinery diff --git a/.evergreen/update-evergreen-configs b/.evergreen/update-evergreen-configs index a57b4e5d16..b1f4296649 100755 --- a/.evergreen/update-evergreen-configs +++ b/.evergreen/update-evergreen-configs @@ -36,36 +36,6 @@ EOT def get_binding binding end - - DEBIAN_FOR_RUBY = { - 'ruby-2.3' => 'debian92', - 'ruby-2.4' => 'debian92', - 'ruby-2.5' => 'debian10', - 'ruby-2.6' => 'debian10', - 'ruby-2.7' => 'debian10', - 'ruby-3.0' => 'debian10', - } - - def standard_debian_rubies(rubies, key: nil, &block) - rubies.flatten! - text = block.call - contents = YAML.load(text) - out = rubies.map do |ruby| - contents.merge( - 'matrix_name' => "#{contents['matrix_name']} - #{ruby}", - 'matrix_spec' => contents['matrix_spec'].merge( - 'ruby' => ruby, - key || 'os' => DEBIAN_FOR_RUBY.fetch(ruby), - ), - ) - end.to_yaml - text =~ /\A\n?(\s+)/ - unless text - raise "Couldn't figure out indentation level" - end - indent = ' ' * ($1.length - 2) - "\n" + out.sub(/\A---.*\n/, indent).gsub("\n", "\n#{indent}") - end end Runner.new.run diff --git a/.gitmodules b/.gitmodules index 366d171c3a..6d428f359d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/mongodb-labs/drivers-evergreen-tools [submodule "spec/shared"] path = spec/shared - url = https://github.com/mongodb-labs/mongo-ruby-spec-shared + url = git@github.com:mongodb-labs/mongo-ruby-spec-shared.git diff --git a/spec/lite_spec_helper.rb b/spec/lite_spec_helper.rb index 22a98d7b8c..0394b97f3b 100644 --- a/spec/lite_spec_helper.rb +++ b/spec/lite_spec_helper.rb @@ -110,6 +110,15 @@ class ExampleTimeout < StandardError; end config.include(MongosMacros) config.extend(Mongo::Macros) + # Used for spec/solo/* + def require_solo + before(:all) do + unless %w(1 true yes).include?(ENV['SOLO']) + skip 'Set SOLO=1 in environment to run solo tests' + end + end + end + if SpecConfig.instance.ci? SdamFormatterIntegration.subscribe config.add_formatter(JsonExtFormatter, File.join(File.dirname(__FILE__), '../tmp/rspec.json')) diff --git a/spec/shared b/spec/shared index faf76703ec..81298ee8ee 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit faf76703ec5aa2fec37a84f3a73b86913c4ff1c4 +Subproject commit 81298ee8ee4bac7f446abcbd13045332396d05fe diff --git a/spec/solo/clean_exit_spec.rb b/spec/solo/clean_exit_spec.rb index 1693b379e9..80e80fd030 100644 --- a/spec/solo/clean_exit_spec.rb +++ b/spec/solo/clean_exit_spec.rb @@ -2,18 +2,11 @@ # rubocop:todo all require 'mongo' +require 'lite_spec_helper' describe 'Clean exit' do - - before(:all) do - unless %w(1 true yes).include?(ENV['SOLO']) - skip 'Set SOLO=1 in environment to run solo tests' - end - - if %w(1 true yes).include?(ENV['EXTERNAL_DISABLED']) - skip "Test requires external connectivity" - end - end + require_external_connectivity + require_solo context 'with SRV URI' do diff --git a/spec/support/shared/app_metadata.rb b/spec/support/shared/app_metadata.rb index 390e847245..8f44ac78c7 100644 --- a/spec/support/shared/app_metadata.rb +++ b/spec/support/shared/app_metadata.rb @@ -1,6 +1,18 @@ # frozen_string_literal: true # rubocop:todo all +def target_arch + @target_arch ||= begin + uname = `uname -a`.strip + case uname + when /aarch/ then "aarch64" + when /x86/ then "x86_64" + when /arm/ then "arm64" + else raise "unrecognized architecture: #{uname.inspect}" + end + end +end + shared_examples 'app metadata document' do let(:app_metadata) do described_class.new({}) @@ -27,7 +39,7 @@ # Ruby 2.7.3 uses linux. %w(linux linux-gnu).should include(document[:client][:os][:name]) end - document[:client][:os][:architecture].should == 'x86_64' + document[:client][:os][:architecture].should == target_arch end end @@ -45,7 +57,7 @@ else document[:client][:os][:name].should =~ /darwin\d+/ end - document[:client][:os][:architecture].should == 'x86_64' + document[:client][:os][:architecture].should == target_arch end end From bafb1297705c6d048db08f30c11029337fd5310e Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 31 May 2023 08:29:31 -0600 Subject: [PATCH 126/198] RUBY-3253 Remove "Public Technical Preview" from Queryable Encryption Equality API (#2719) --- lib/mongo/client_encryption.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/mongo/client_encryption.rb b/lib/mongo/client_encryption.rb index e85e3ccb32..5390048edd 100644 --- a/lib/mongo/client_encryption.rb +++ b/lib/mongo/client_encryption.rb @@ -255,10 +255,6 @@ def rewrap_many_data_key(filter, opts = {}) # created by this method with automatic encryption, the user must create # a new client after calling this function with the :encrypted_fields returned. # - # @note This method is a part of the queryable encryption API and therefore - # is in public technical preview. This method should be considered - # experimental and is subject to change. - # # @param [ Mongo::Database ] database Database to create collection in. # @param [ String ] coll_name Name of collection to create. # @param [ Hash ] coll_opts Options for collection to create. From 6c2c84d984f456fadf2af9dfeb7ab6889a50f835 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 31 May 2023 08:31:35 -0600 Subject: [PATCH 127/198] RUBY-3156 Error if RewrapManyDataKey is called with masterKey and without provider (#2720) * RUBY 3156 enforce that masterKey requires provider * reference the correct variable * rubocop work is being done in a separate branch --- lib/mongo/crypt/explicit_encrypter.rb | 14 ++++++++++++++ .../client_side_encryption/rewrap_prose_spec.rb | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/lib/mongo/crypt/explicit_encrypter.rb b/lib/mongo/crypt/explicit_encrypter.rb index cce570e441..ffe885976b 100644 --- a/lib/mongo/crypt/explicit_encrypter.rb +++ b/lib/mongo/crypt/explicit_encrypter.rb @@ -257,6 +257,8 @@ def remove_key_alt_name(id, key_alt_name) # # @return [ Crypt::RewrapManyDataKeyResult ] Result of the operation. def rewrap_many_data_key(filter, opts = {}) + validate_rewrap_options!(opts) + master_key_document = if opts[:provider] options = opts.dup provider = options.delete(:provider) @@ -291,6 +293,18 @@ def rewrap_many_data_key(filter, opts = {}) @encryption_io.update_data_keys(updates) ) end + + # Ensures the consistency of the options passed to #rewrap_many_data_keys. + # + # @param [Hash] opts the options hash to validate + # + # @raise [ ArgumentError ] if the options are not consistent or + # compatible. + def validate_rewrap_options!(opts) + if opts.key?(:master_key) && !opts.key?(:provider) + raise ArgumentError, 'If :master_key is specified, :provider must also be given' + end + end end end end diff --git a/spec/integration/client_side_encryption/rewrap_prose_spec.rb b/spec/integration/client_side_encryption/rewrap_prose_spec.rb index 9ba2e19bfc..86958929f2 100644 --- a/spec/integration/client_side_encryption/rewrap_prose_spec.rb +++ b/spec/integration/client_side_encryption/rewrap_prose_spec.rb @@ -101,6 +101,13 @@ expect(client_encryption1.decrypt(ciphertext)).to eq('test') expect(client_encryption2.decrypt(ciphertext)).to eq('test') end + + context 'when master_key is present without provider' do + it 'raises an exception' do + expect { client_encryption1.rewrap_many_data_key({}, master_key: {}) } + .to raise_error(ArgumentError, /provider/) + end + end end end end From 4c7933954ee3d82b16c61af37012c84a2c6fa88f Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 1 Jun 2023 08:22:55 -0600 Subject: [PATCH 128/198] RUBY-3156 Linter appeasement for #2720 (#2721) * RUBY 3156 enforce that masterKey requires provider * linter appeasement * reference the correct variable * rubocop work is being done in a separate branch * remove the rubocop:todo for this file * remove merge artifact * merge artifact --- lib/mongo/crypt/explicit_encrypter.rb | 106 ++++++++++++++------------ 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/lib/mongo/crypt/explicit_encrypter.rb b/lib/mongo/crypt/explicit_encrypter.rb index ffe885976b..a38c2705a1 100644 --- a/lib/mongo/crypt/explicit_encrypter.rb +++ b/lib/mongo/crypt/explicit_encrypter.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all # Copyright (C) 2020 MongoDB Inc. # @@ -17,12 +16,13 @@ module Mongo module Crypt - # An ExplicitEncrypter is an object that performs explicit encryption # operations and handles all associated options and instance variables. # # @api private class ExplicitEncrypter + extend Forwardable + # Create a new ExplicitEncrypter object. # # @param [ Mongo::Client ] key_vault_client An instance of Mongo::Client @@ -44,7 +44,7 @@ def initialize(key_vault_client, key_vault_namespace, kms_providers, kms_tls_opt @encryption_io = EncryptionIO.new( key_vault_client: key_vault_client, metadata_client: nil, - key_vault_namespace: key_vault_namespace, + key_vault_namespace: key_vault_namespace ) end @@ -108,7 +108,7 @@ def encrypt(value, options) Crypt::ExplicitEncryptionContext.new( @crypt_handle, @encryption_io, - { 'v': value }, + { v: value }, options ).run_state_machine['v'] end @@ -167,7 +167,7 @@ def encrypt_expression(expression, options) Crypt::ExplicitEncryptionExpressionContext.new( @crypt_handle, @encryption_io, - { 'v': expression }, + { v: expression }, options ).run_state_machine['v'] end @@ -179,10 +179,10 @@ def encrypt_expression(expression, options) # # @return [ Object ] The decrypted value def decrypt(value) - result = Crypt::ExplicitDecryptionContext.new( + Crypt::ExplicitDecryptionContext.new( @crypt_handle, @encryption_io, - { 'v': value }, + { v: value } ).run_state_machine['v'] end @@ -203,9 +203,7 @@ def add_key_alt_name(id, key_alt_name) # # @return [ Operation::Result ] The response from the database for the delete_one # operation that deletes the key. - def delete_key(id) - @encryption_io.delete_key(id) - end + def_delegators :@encryption_io, :delete_key # Finds a single key with the given id. # @@ -213,9 +211,7 @@ def delete_key(id) # # @return [ BSON::Document | nil ] The found key document or nil # if not found. - def get_key(id) - @encryption_io.get_key(id) - end + def_delegators :@encryption_io, :get_key # Returns a key in the key vault collection with the given key_alt_name. # @@ -223,16 +219,12 @@ def get_key(id) # # @return [ BSON::Document | nil ] The found key document or nil # if not found. - def get_key_by_alt_name(key_alt_name) - @encryption_io.get_key_by_alt_name(key_alt_name) - end + def_delegators :@encryption_io, :get_key_by_alt_name # Returns all keys in the key vault collection. # # @return [ Collection::View ] Keys in the key vault collection. - def get_keys - @encryption_io.get_keys - end + def_delegators :@encryption_io, :get_keys # Removes a key_alt_name from a key in the key vault collection with the given id. # @@ -241,9 +233,7 @@ def get_keys # # @return [ BSON::Document | nil ] Document describing the identified key # before removing the key alt name, or nil if no such key. - def remove_key_alt_name(id, key_alt_name) - @encryption_io.remove_key_alt_name(id, key_alt_name) - end + def_delegators :@encryption_io, :remove_key_alt_name # Decrypts multiple data keys and (re-)encrypts them with a new master_key, # or with their current master_key if a new one is not given. @@ -259,11 +249,7 @@ def remove_key_alt_name(id, key_alt_name) def rewrap_many_data_key(filter, opts = {}) validate_rewrap_options!(opts) - master_key_document = if opts[:provider] - options = opts.dup - provider = options.delete(:provider) - KMS::MasterKeyDocument.new(provider, options) - end + master_key_document = master_key_for_provider(opts) rewrap_result = Crypt::RewrapManyDataKeyContext.new( @crypt_handle, @@ -271,11 +257,52 @@ def rewrap_many_data_key(filter, opts = {}) filter, master_key_document ).run_state_machine - if rewrap_result.nil? - return RewrapManyDataKeyResult.new(nil) - end - data_key_documents = rewrap_result.fetch('v') - updates = data_key_documents.map do |doc| + + return RewrapManyDataKeyResult.new(nil) if rewrap_result.nil? + + updates = updates_from_data_key_documents(rewrap_result.fetch('v')) + RewrapManyDataKeyResult.new(@encryption_io.update_data_keys(updates)) + end + + private + + # Ensures the consistency of the options passed to #rewrap_many_data_keys. + # + # @param [ Hash ] opts the options hash to validate + # + # @raise [ ArgumentError ] if the options are not consistent or + # compatible. + def validate_rewrap_options!(opts) + return unless opts.key?(:master_key) && !opts.key?(:provider) + + raise ArgumentError, 'If :master_key is specified, :provider must also be given' + end + + # If a :provider is given, construct a new master key document + # with that provider. + # + # @param [ Hash ] opts the options hash + # + # @option [ String ] :provider KMS provider to encrypt keys. + # + # @return [ KMS::MasterKeyDocument | nil ] the new master key document, + # or nil if no provider was given. + def master_key_for_provider(opts) + return nil unless opts[:provider] + + options = opts.dup + provider = options.delete(:provider) + KMS::MasterKeyDocument.new(provider, options) + end + + # Returns the corresponding update document for each for of the given + # data key documents. + # + # @param [ Array ] documents the data key documents + # + # @return [ Array ] the update documents + def updates_from_data_key_documents(documents) + documents.map do |doc| { update_one: { filter: { _id: doc[:_id] }, @@ -289,21 +316,6 @@ def rewrap_many_data_key(filter, opts = {}) } } end - RewrapManyDataKeyResult.new( - @encryption_io.update_data_keys(updates) - ) - end - - # Ensures the consistency of the options passed to #rewrap_many_data_keys. - # - # @param [Hash] opts the options hash to validate - # - # @raise [ ArgumentError ] if the options are not consistent or - # compatible. - def validate_rewrap_options!(opts) - if opts.key?(:master_key) && !opts.key?(:provider) - raise ArgumentError, 'If :master_key is specified, :provider must also be given' - end end end end From dd3b99b74324ea8687bc091fb82cc0221e82b13a Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 1 Jun 2023 08:27:04 -0600 Subject: [PATCH 129/198] RUBY-3152 Collect FaaS platform info in handshake (#2722) * add environment detection logic * add environment info, and updated truncation logic * rubocop pass for new files * prose tests from handshake spec * updated truncation logic, and AWS detection logic * minor cleanups --- .rubocop.yml | 3 + lib/mongo/server/app_metadata.rb | 71 ++--- lib/mongo/server/app_metadata/environment.rb | 255 ++++++++++++++++++ lib/mongo/server/app_metadata/truncator.rb | 142 ++++++++++ spec/integration/connection/faas_env_spec.rb | 63 +++++ spec/mongo/client_construction_spec.rb | 6 +- spec/mongo/cluster_spec.rb | 4 +- .../server/app_metadata/environment_spec.rb | 193 +++++++++++++ .../server/app_metadata/truncator_spec.rb | 158 +++++++++++ spec/mongo/server/app_metadata_spec.rb | 80 +++--- spec/mongo/socket/ssl_spec.rb | 10 +- 11 files changed, 883 insertions(+), 102 deletions(-) create mode 100644 lib/mongo/server/app_metadata/environment.rb create mode 100644 lib/mongo/server/app_metadata/truncator.rb create mode 100644 spec/integration/connection/faas_env_spec.rb create mode 100644 spec/mongo/server/app_metadata/environment_spec.rb create mode 100644 spec/mongo/server/app_metadata/truncator_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 3f63fb451b..2b7a7f90f7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -64,6 +64,9 @@ RSpec/BeforeAfterAll: RSpec/DescribeClass: Enabled: false +Style/FetchEnvVar: + Enabled: false + RSpec/ImplicitExpect: EnforcedStyle: is_expected diff --git a/lib/mongo/server/app_metadata.rb b/lib/mongo/server/app_metadata.rb index 5d47a1d438..88a75032c2 100644 --- a/lib/mongo/server/app_metadata.rb +++ b/lib/mongo/server/app_metadata.rb @@ -15,6 +15,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'mongo/server/app_metadata/environment' +require 'mongo/server/app_metadata/truncator' + module Mongo class Server # Application metadata that is sent to the server during a handshake, @@ -26,11 +29,6 @@ class Server class AppMetadata extend Forwardable - # The max application metadata document byte size. - # - # @since 2.4.0 - MAX_DOCUMENT_SIZE = 512.freeze - # The max application name byte size. # # @since 2.4.0 @@ -139,6 +137,23 @@ def validated_document document end + # Get BSON::Document to be used as value for `client` key in + # handshake document. + # + # @return [BSON::Document] Document describing client for handshake. + # + # @api private + def client_document + @client_document ||= + BSON::Document.new.tap do |doc| + doc[:application] = { name: @app_name } if @app_name + doc[:driver] = driver_doc + doc[:os] = os_doc + doc[:platform] = platform + env_doc.tap { |env| doc[:env] = env if env } + end + end + private # Check whether it is possible to build a valid app metadata document @@ -152,20 +167,6 @@ def validate! true end - # Get BSON::Document to be used as value for `client` key in - # handshake document. - # - # @return [BSON::Document] Document describing client for handshake. - def full_client_document - BSON::Document.new.tap do |doc| - doc[:application] = { name: @app_name } if @app_name - doc[:driver] = driver_doc - doc[:os] = os_doc - doc[:platform] = platform - end - end - - # Get the metadata as BSON::Document to be sent to # as part of the handshake. The document should # be appended to a suitable handshake command. @@ -173,30 +174,11 @@ def full_client_document # @return [BSON::Document] Document for connection's handshake. def document @document ||= begin - client_document = full_client_document - while client_document.to_bson.to_s.size > MAX_DOCUMENT_SIZE do - if client_document[:os][:name] || client_document[:os][:architecture] - client_document[:os].delete(:name) - client_document[:os].delete(:architecture) - elsif client_document[:platform] - client_document.delete(:platform) - else - client_document = nil - end + client = Truncator.new(client_document).document + BSON::Document.new(compression: @compressors, client: client).tap do |doc| + doc[:saslSupportedMechs] = @request_auth_mech if @request_auth_mech + doc.update(Utils.transform_server_api(@server_api)) if @server_api end - document = BSON::Document.new( - { - compression: @compressors, - client: client_document, - } - ) - document[:saslSupportedMechs] = @request_auth_mech if @request_auth_mech - if @server_api - document.update( - Utils.transform_server_api(@server_api) - ) - end - document end end @@ -223,6 +205,11 @@ def os_doc } end + def env_doc + env = Environment.new + env.faas? ? env.to_h : nil + end + def type (RbConfig::CONFIG && RbConfig::CONFIG['host_os']) ? RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase : 'unknown' diff --git a/lib/mongo/server/app_metadata/environment.rb b/lib/mongo/server/app_metadata/environment.rb new file mode 100644 index 0000000000..5e9c556f7b --- /dev/null +++ b/lib/mongo/server/app_metadata/environment.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +# Copyright (C) 2016-2023 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Server + class AppMetadata + # Implements the logic from the handshake spec, for deducing and + # reporting the current FaaS environment in which the program is + # executing. + # + # @api private + class Environment + # Error class for reporting that too many discriminators were found + # in the environment. (E.g. if the environment reports that it is + # running under both AWS and Azure.) + class TooManyEnvironments < Mongo::Error; end + + # Error class for reporting that a required environment variable is + # missing. + class MissingVariable < Mongo::Error; end + + # Error class for reporting that the wrong type was given for a + # field. + class TypeMismatch < Mongo::Error; end + + # Error class for reporting that the value for a field is too long. + class ValueTooLong < Mongo::Error; end + + # This value is not explicitly specified in the spec, only implied to be + # less than 512. + MAXIMUM_VALUE_LENGTH = 500 + + # The mapping that determines which FaaS environment is active, based + # on which environment variable(s) are present. + DISCRIMINATORS = { + 'AWS_EXECUTION_ENV' => { pattern: /^AWS_Lambda_/, name: 'aws.lambda' }, + 'AWS_LAMBDA_RUNTIME_API' => { name: 'aws.lambda' }, + 'FUNCTIONS_WORKER_RUNTIME' => { name: 'azure.func' }, + 'K_SERVICE' => { name: 'gcp.func' }, + 'FUNCTION_NAME' => { name: 'gcp.func' }, + 'VERCEL' => { name: 'vercel' }, + }.freeze + + # Describes how to coerce values of the specified type. + COERCIONS = { + string: ->(v) { String(v) }, + integer: ->(v) { Integer(v) } + }.freeze + + # Describes which fields are required for each FaaS environment, + # along with their expected types, and how they should be named in + # the handshake document. + FIELDS = { + 'aws.lambda' => { + 'AWS_REGION' => { field: :region, type: :string }, + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => { field: :memory_mb, type: :integer }, + }, + + 'azure.func' => {}, + + 'gcp.func' => { + 'FUNCTION_MEMORY_MB' => { field: :memory_mb, type: :integer }, + 'FUNCTION_TIMEOUT_SEC' => { field: :timeout_sec, type: :integer }, + 'FUNCTION_REGION' => { field: :region, type: :string }, + }, + + 'vercel' => { + 'VERCEL_URL' => { field: :url, type: :string }, + 'VERCEL_REGION' => { field: :region, type: :string }, + }, + }.freeze + + # @return [ String | nil ] the name of the FaaS environment that was + # detected, or nil if no valid FaaS environment was detected. + attr_reader :name + + # @return [ Hash | nil ] the fields describing the detected FaaS + # environment. + attr_reader :fields + + # @return [ String | nil ] the error message explaining why a valid + # FaaS environment was not detected, or nil if no error occurred. + # + # @note These error messagess are not to be propogated to the + # user; they are intended only for troubleshooting and debugging.) + attr_reader :error + + # Create a new AppMetadata::Environment object, initializing it from + # the current ENV variables. If no FaaS environment is detected, or + # if the environment contains invalid or contradictory state, it will + # be initialized with {{name}} set to {{nil}}. + def initialize + @error = nil + @name = detect_environment + populate_fields + rescue TooManyEnvironments => e + self.error = "too many environments detected: #{e.message}" + rescue MissingVariable => e + self.error = "missing environment variable: #{e.message}" + rescue TypeMismatch => e + self.error = e.message + rescue ValueTooLong => e + self.error = "value for #{e.message} is too long" + end + + # Queries whether the current environment is a valid FaaS environment. + # + # @return [ true | false ] whether the environment is a FaaS + # environment or not. + def faas? + @name != nil + end + + # Queries whether the current environment is a valid AWS Lambda + # environment. + # + # @return [ true | false ] whether the environment is a AWS Lambda + # environment or not. + def aws? + @name == 'aws.lambda' + end + + # Queries whether the current environment is a valid Azure + # environment. + # + # @return [ true | false ] whether the environment is a Azure + # environment or not. + def azure? + @name == 'azure.func' + end + + # Queries whether the current environment is a valid GCP + # environment. + # + # @return [ true | false ] whether the environment is a GCP + # environment or not. + def gcp? + @name == 'gcp.func' + end + + # Queries whether the current environment is a valid Vercel + # environment. + # + # @return [ true | false ] whether the environment is a Vercel + # environment or not. + def vercel? + @name == 'vercel' + end + + # Compiles the detected environment information into a Hash. It will + # always include a {{name}} key, but may include other keys as well, + # depending on the detected FaaS environment. (See the handshake + # spec for details.) + # + # @return [ Hash ] the detected environment information. + def to_h + fields.merge(name: name) + end + + private + + # Searches the DESCRIMINATORS list to see which (if any) apply to + # the current environment. + # + # @return [ String | nil ] the name of the detected FaaS provider. + # + # @raise [ TooManyEnvironments ] if the environment contains + # discriminating variables for more than one FaaS provider. + def detect_environment + matches = DISCRIMINATORS.keys.select { |k| discriminator_matches?(k) } + names = matches.map { |m| DISCRIMINATORS[m][:name] }.uniq + + raise TooManyEnvironments, names.join(', ') if names.length > 1 + + names.first + end + + # Determines whether the named environment variable exists, and (if + # a pattern has been declared for that descriminator) whether the + # pattern matches the value of the variable. + # + # @param [ String ] var the name of the environment variable + # + # @return [ true | false ] if the variable describes the current + # environment or not. + def discriminator_matches?(var) + return false unless ENV[var] + + disc = DISCRIMINATORS[var] + return true unless disc[:pattern] + + disc[:pattern].match?(ENV[var]) + end + + # Extracts environment information from the current environment + # variables, based on the detected FaaS environment. Populates the + # {{@fields}} instance variable. + def populate_fields + return unless name + + @fields = FIELDS[name].each_with_object({}) do |(var, defn), fields| + fields[defn[:field]] = extract_field(var, defn) + end + end + + # Extracts the named variable from the environment and validates it + # against its declared definition. + # + # @param [ String ] var The name of the environment variable to look + # for. + # @param [ Hash ] definition The definition of the field that applies + # to the named variable. + # + # @return [ Integer | String ] the validated and coerced value of the + # given environment variable. + # + # @raise [ MissingVariable ] if the environment does not include a + # variable required by the current FaaS provider. + # @raise [ ValueTooLong ] if a required variable is too long. + # @raise [ TypeMismatch ] if a required variable cannot be coerced to + # the expected type. + def extract_field(var, definition) + raise MissingVariable, var unless ENV[var] + raise ValueTooLong, var if ENV[var].length > MAXIMUM_VALUE_LENGTH + + COERCIONS[definition[:type]].call(ENV[var]) + rescue ArgumentError + raise TypeMismatch, + "#{var} must be #{definition[:type]} (got #{ENV[var].inspect})" + end + + # Sets the error message to the given value and sets the name to nil. + # + # @param [ String ] msg The error message to store. + def error=(msg) + @name = nil + @error = msg + end + end + end + end +end diff --git a/lib/mongo/server/app_metadata/truncator.rb b/lib/mongo/server/app_metadata/truncator.rb new file mode 100644 index 0000000000..125b8f1e2e --- /dev/null +++ b/lib/mongo/server/app_metadata/truncator.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +# Copyright (C) 2016-2023 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Server + class AppMetadata + # Implements the metadata truncation logic described in the handshake + # spec. + # + # @api private + class Truncator + # @return [ BSON::Document ] the document being truncated. + attr_reader :document + + # The max application metadata document byte size. + MAX_DOCUMENT_SIZE = 512 + + # Creates a new Truncator instance and tries enforcing the maximum + # document size on the given document. + # + # @param [ BSON::Document] document The document to (potentially) + # truncate. + # + # @note The document is modified in-place; if you wish to keep the + # original unchanged, you must deep-clone it prior to sending it to + # a truncator. + def initialize(document) + @document = document + try_truncate! + end + + # The current size of the document, in bytes, as a serialized BSON + # document. + # + # @return [ Integer ] the size of the document + def size + @document.to_bson.to_s.length + end + + # Whether the document fits within the required maximum document size. + # + # @return [ true | false ] if the document is okay or not. + def ok? + size <= MAX_DOCUMENT_SIZE + end + + private + + # How many extra bytes must be trimmed before the document may be + # considered #ok?. + # + # @return [ Integer ] how many bytes larger the document is than the + # maximum document size. + def excess + size - MAX_DOCUMENT_SIZE + end + + # Attempt to truncate the document using the documented metadata + # priorities (see the handshake specification). + def try_truncate! + %i[ env_fields os_fields env platform ].each do |target| + break if ok? + + send(:"try_truncate_#{target}!") + end + end + + # Attempt to truncate or remove the {{:platform}} key from the + # document. + def try_truncate_platform! + @document.delete(:platform) unless try_truncate_string(@document[:platform]) + end + + # Attempt to truncate the keys in the {{:env}} subdocument. + def try_truncate_env_fields! + try_truncate_hash(@document[:env], reserved: %w[ name ]) + end + + # Attempt to truncate the keys in the {{:os}} subdocument. + def try_truncate_os_fields! + try_truncate_hash(@document[:os], reserved: %w[ type ]) + end + + # Remove the {{:env}} key from the document. + def try_truncate_env! + @document.delete(:env) + end + + # A helper method for truncating a string (in-place) by whatever + # {{#excess}} is required. + # + # @param [ String ] string the string value to truncate. + # + # @note the parameter is modified in-place. + def try_truncate_string(string) + length = string&.length || 0 + + return false if excess > length + + string[(length - excess)..-1] = '' + end + + # A helper method for removing the keys of a Hash (in-place) until + # the document is the necessary size. The keys are considered in order + # (using the Hash's native key ordering), and each will be removed from + # the hash in turn, until the document is the necessary size. + # + # Any keys in the {{reserved}} list will be ignored. + # + # @param [ Hash | nil ] hash the Hash instance to consider. + # @param [ Array ] reserved the list of keys to ignore in the hash. + # + # @note the hash parameter is modified in-place. + def try_truncate_hash(hash, reserved: []) + return false unless hash + + keys = hash.keys - reserved + keys.each do |key| + hash.delete(key) + + return true if ok? + end + + false + end + end + end + end +end diff --git a/spec/integration/connection/faas_env_spec.rb b/spec/integration/connection/faas_env_spec.rb new file mode 100644 index 0000000000..08f2b55867 --- /dev/null +++ b/spec/integration/connection/faas_env_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Test Plan scenarios from the handshake spec +SCENARIOS = { + 'Valid AWS' => { + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_REGION' => 'us-east-2', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024', + }, + + 'Valid Azure' => { + 'FUNCTIONS_WORKER_RUNTIME' => 'ruby', + }, + + 'Valid GCP' => { + 'K_SERVICE' => 'servicename', + 'FUNCTION_MEMORY_MB' => '1024', + 'FUNCTION_TIMEOUT_SEC' => '60', + 'FUNCTION_REGION' => 'us-central1', + }, + + 'Valid Vercel' => { + 'VERCEL' => '1', + 'VERCEL_URL' => '*.vercel.app', + 'VERCEL_REGION' => 'cdg1', + }, + + 'Invalid - multiple providers' => { + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_REGION' => 'us-east-2', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024', + 'FUNCTIONS_WORKER_RUNTIME' => 'ruby', + }, + + 'Invalid - long string' => { + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_REGION' => 'a' * 512, + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024', + }, + + 'Invalid - wrong types' => { + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_REGION' => 'us-east-2', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => 'big', + }, +}.freeze + +describe 'Connect under FaaS Env' do + clean_slate + + SCENARIOS.each do |name, env| + context "when given #{name}" do + local_env(env) + + it 'connects successfully' do + resp = authorized_client.database.command(ping: 1) + expect(resp).to be_a(Mongo::Operation::Result) + end + end + end +end diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index 254cc9001a..ec70621d92 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -955,7 +955,7 @@ end it 'includes the platform info in the app metadata' do - expect(app_metadata.send(:full_client_document)[:platform]).to match(/mongoid-6\.0\.2/) + expect(app_metadata.client_document[:platform]).to match(/mongoid-6\.0\.2/) end end @@ -982,7 +982,7 @@ end it 'does not include the platform info in the app metadata' do - expect(app_metadata.send(:full_client_document)[:platform]).to eq(platform_string) + expect(app_metadata.client_document[:platform]).to eq(platform_string) end end @@ -1001,7 +1001,7 @@ end it 'does not include the platform info in the app metadata' do - expect(app_metadata.send(:full_client_document)[:platform]).to eq(platform_string) + expect(app_metadata.client_document[:platform]).to eq(platform_string) end end end diff --git a/spec/mongo/cluster_spec.rb b/spec/mongo/cluster_spec.rb index f51436c253..6db47aff5a 100644 --- a/spec/mongo/cluster_spec.rb +++ b/spec/mongo/cluster_spec.rb @@ -480,7 +480,7 @@ end it 'constructs an AppMetadata object with the app_name' do - expect(cluster.app_metadata.send(:full_client_document)[:application]).to eq('name' => 'cluster_test') + expect(cluster.app_metadata.client_document[:application]).to eq('name' => 'cluster_test') end end @@ -491,7 +491,7 @@ end it 'constructs an AppMetadata object with no app_name' do - expect(cluster.app_metadata.send(:full_client_document)[:application]).to be_nil + expect(cluster.app_metadata.client_document[:application]).to be_nil end end end diff --git a/spec/mongo/server/app_metadata/environment_spec.rb b/spec/mongo/server/app_metadata/environment_spec.rb new file mode 100644 index 0000000000..3bf88acca9 --- /dev/null +++ b/spec/mongo/server/app_metadata/environment_spec.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mongo::Server::AppMetadata::Environment do + let(:env) { described_class.new } + + shared_examples_for 'running in a FaaS environment' do + it 'reports that a FaaS environment is detected' do + expect(env.faas?).to be true + end + end + + shared_examples_for 'running outside a FaaS environment' do + it 'reports that no FaaS environment is detected' do + expect(env.faas?).to be false + end + end + + context 'when run outside of a FaaS environment' do + it_behaves_like 'running outside a FaaS environment' + end + + context 'when run in a FaaS environment' do + context 'when environment is invalid due to type mismatch' do + local_env( + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_REGION' => 'us-east-2', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => 'big' + ) + + it_behaves_like 'running outside a FaaS environment' + + it 'fails due to type mismatch' do + expect(env.error).to match(/AWS_LAMBDA_FUNCTION_MEMORY_SIZE must be integer/) + end + end + + context 'when environment is invalid due to long string' do + local_env( + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_REGION' => 'a' * 512, + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024' + ) + + it_behaves_like 'running outside a FaaS environment' + + it 'fails due to long string' do + expect(env.error).to match(/too long/) + end + end + + context 'when environment is invalid due to multiple providers' do + local_env( + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_REGION' => 'us-east-2', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024', + 'FUNCTIONS_WORKER_RUNTIME' => 'ruby' + ) + + it_behaves_like 'running outside a FaaS environment' + + it 'fails due to multiple providers' do + expect(env.error).to match(/too many environments/) + end + end + + context 'when environment is invalid due to missing variable' do + local_env( + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024' + ) + + it_behaves_like 'running outside a FaaS environment' + + it 'fails due to missing variable' do + expect(env.error).to match(/missing environment variable/) + end + end + + context 'when FaaS environment is AWS' do + shared_examples_for 'running in an AWS environment' do + context 'when environment is valid' do + local_env( + 'AWS_REGION' => 'us-east-2', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024' + ) + + it_behaves_like 'running in a FaaS environment' + + it 'recognizes AWS' do + expect(env.name).to be == 'aws.lambda' + expect(env.fields[:region]).to be == 'us-east-2' + expect(env.fields[:memory_mb]).to be == 1024 + end + end + end + + # per DRIVERS-2623, AWS_EXECUTION_ENV must be prefixed + # with 'AWS_Lambda_'. + context 'when AWS_EXECUTION_ENV is invalid' do + local_env( + 'AWS_EXECUTION_ENV' => 'EC2', + 'AWS_REGION' => 'us-east-2', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024' + ) + + it_behaves_like 'running outside a FaaS environment' + end + + context 'when AWS_EXECUTION_ENV is detected' do + local_env('AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7') + it_behaves_like 'running in an AWS environment' + end + + context 'when AWS_LAMBDA_RUNTIME_API is detected' do + local_env('AWS_LAMBDA_RUNTIME_API' => 'lambda.aws.amazon.com/api') + it_behaves_like 'running in an AWS environment' + end + end + + context 'when FaaS environment is Azure' do + local_env('FUNCTIONS_WORKER_RUNTIME' => 'ruby') + + it_behaves_like 'running in a FaaS environment' + + it 'recognizes Azure' do + expect(env.name).to be == 'azure.func' + end + end + + context 'when FaaS environment is GCP' do + local_env( + 'FUNCTION_MEMORY_MB' => '1024', + 'FUNCTION_TIMEOUT_SEC' => '60', + 'FUNCTION_REGION' => 'us-central1' + ) + + shared_examples_for 'running in a GCP environment' do + it_behaves_like 'running in a FaaS environment' + + it 'recognizes GCP' do + expect(env.name).to be == 'gcp.func' + expect(env.fields[:region]).to be == 'us-central1' + expect(env.fields[:memory_mb]).to be == 1024 + expect(env.fields[:timeout_sec]).to be == 60 + end + end + + context 'when K_SERVICE is present' do + local_env('K_SERVICE' => 'servicename') + it_behaves_like 'running in a GCP environment' + end + + context 'when FUNCTION_NAME is present' do + local_env('FUNCTION_NAME' => 'functionName') + it_behaves_like 'running in a GCP environment' + end + end + + context 'when FaaS environment is Vercel' do + local_env( + 'VERCEL' => '1', + 'VERCEL_URL' => '*.vercel.app', + 'VERCEL_REGION' => 'cdg1' + ) + + it_behaves_like 'running in a FaaS environment' + + it 'recognizes Vercel' do + expect(env.name).to be == 'vercel' + expect(env.fields[:url]).to be == '*.vercel.app' + expect(env.fields[:region]).to be == 'cdg1' + end + end + + context 'when converting environment to a hash' do + local_env( + 'K_SERVICE' => 'servicename', + 'FUNCTION_MEMORY_MB' => '1024', + 'FUNCTION_TIMEOUT_SEC' => '60', + 'FUNCTION_REGION' => 'us-central1' + ) + + it 'includes name and all fields' do + expect(env.to_h).to be == { + name: 'gcp.func', memory_mb: 1024, + timeout_sec: 60, region: 'us-central1', + } + end + end + end +end diff --git a/spec/mongo/server/app_metadata/truncator_spec.rb b/spec/mongo/server/app_metadata/truncator_spec.rb new file mode 100644 index 0000000000..52fcad3240 --- /dev/null +++ b/spec/mongo/server/app_metadata/truncator_spec.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Quoted from specifications/source/mongodb-handshake/handshake.rst: +# +# Implementors SHOULD cumulatively update fields in the following order +# until the document is under the size limit: +# +# 1. Omit fields from env except env.name. +# 2. Omit fields from os except os.type. +# 3. Omit the env document entirely. +# 4. Truncate platform. + +describe Mongo::Server::AppMetadata::Truncator do + let(:truncator) { described_class.new(Marshal.load(Marshal.dump(metadata))) } + + let(:app_name) { 'application' } + let(:driver) { { name: 'driver', version: '1.2.3' } } + let(:os) { { type: 'Darwin', name: 'macOS', architecture: 'arm64', version: '13.4' } } + let(:platform) { { platform: 'platform' } } + let(:env) { { name: 'aws.lambda', region: 'region', memory_mb: 1024 } } + + let(:metadata) do + BSON::Document.new.tap do |doc| + doc[:application] = { name: app_name } + doc[:driver] = driver + doc[:os] = os + doc[:platform] = platform + doc[:env] = env + end + end + + let(:untruncated_length) { metadata.to_bson.to_s.length } + let(:truncated_length) { truncator.document.to_bson.to_s.length } + + shared_examples_for 'a truncated document' do + it 'is shorter' do + expect(truncated_length).to be < untruncated_length + end + + it 'is not be longer than the maximum document size' do + expect(truncated_length).to be <= described_class::MAX_DOCUMENT_SIZE + end + end + + describe 'MAX_DOCUMENT_SIZE' do + it 'is 512 bytes' do + # This test is an additional check that MAX_DOCUMENT_SIZE + # has not been accidentially changed. + expect(described_class::MAX_DOCUMENT_SIZE).to be == 512 + end + end + + context 'when document does not need truncating' do + it 'does not truncate anything' do + expect(truncated_length).to be == untruncated_length + end + end + + context 'when modifying env is sufficient' do + context 'when a single value is too long' do + let(:env) { { name: 'name', a: 'a' * 1000, b: 'b' } } + + it 'preserves name' do + expect(truncator.document[:env][:name]).to be == 'name' + end + + it 'removes the too-long entry and keeps name' do + expect(truncator.document[:env].keys).to be == %w[ name b ] + end + + it_behaves_like 'a truncated document' + end + + context 'when multiple values are too long' do + let(:env) { { name: 'name', a: 'a' * 1000, b: 'b', c: 'c' * 1000, d: 'd' } } + + it 'preserves name' do + expect(truncator.document[:env][:name]).to be == 'name' + end + + it 'removes all other entries until size is satisifed' do + expect(truncator.document[:env].keys).to be == %w[ name d ] + end + + it_behaves_like 'a truncated document' + end + end + + context 'when modifying os is sufficient' do + context 'when a single value is too long' do + let(:os) { { type: 'type', a: 'a' * 1000, b: 'b' } } + + it 'truncates env' do + expect(truncator.document[:env].keys).to be == %w[ name ] + end + + it 'preserves type' do + expect(truncator.document[:os][:type]).to be == 'type' + end + + it 'removes the too-long entry and keeps type' do + expect(truncator.document[:os].keys).to be == %w[ type b ] + end + + it_behaves_like 'a truncated document' + end + + context 'when multiple values are too long' do + let(:os) { { type: 'type', a: 'a' * 1000, b: 'b', c: 'c' * 1000, d: 'd' } } + + it 'truncates env' do + expect(truncator.document[:env].keys).to be == %w[ name ] + end + + it 'preserves type' do + expect(truncator.document[:os][:type]).to be == 'type' + end + + it 'removes all other entries until size is satisifed' do + expect(truncator.document[:os].keys).to be == %w[ type d ] + end + + it_behaves_like 'a truncated document' + end + end + + context 'when truncating os is insufficient' do + let(:env) { { name: 'n' * 1000 } } + + it 'truncates os' do + expect(truncator.document[:os].keys).to be == %w[ type ] + end + + it 'removes env' do + expect(truncator.document.key?(:env)).to be false + end + + it_behaves_like 'a truncated document' + end + + context 'when platform is too long' do + let(:platform) { 'n' * 1000 } + + it 'truncates os' do + expect(truncator.document[:os].keys).to be == %w[ type ] + end + + it 'removes env' do + expect(truncator.document.key?(:env)).to be false + end + + it 'truncates platform' do + expect(truncator.document[:platform].length).to be < 1000 + end + end +end diff --git a/spec/mongo/server/app_metadata_spec.rb b/spec/mongo/server/app_metadata_spec.rb index 80b9c6cdca..c8819c67ce 100644 --- a/spec/mongo/server/app_metadata_spec.rb +++ b/spec/mongo/server/app_metadata_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe Mongo::Server::AppMetadata do + let(:max_size) { described_class::Truncator::MAX_DOCUMENT_SIZE } let(:app_metadata) do described_class.new(cluster.options) @@ -13,14 +14,6 @@ authorized_client.cluster end - describe 'MAX_DOCUMENT_SIZE' do - it 'should be 512 bytes' do - # This test is an additional check that MAX_DOCUMENT_SIZE - # has not been accidentially changed. - expect(described_class::MAX_DOCUMENT_SIZE).to eq(512) - end - end - describe '#initialize' do context 'when the cluster has an app name option set' do @@ -34,7 +27,7 @@ end it 'sets the app name' do - expect(app_metadata.send(:full_client_document)[:application][:name]).to eq('app_metadata_test') + expect(app_metadata.client_document[:application][:name]).to eq('app_metadata_test') end context 'when the app name exceeds the max length of 128' do @@ -58,77 +51,70 @@ context 'when the cluster does not have an app name option set' do it 'does not set the app name' do - expect(app_metadata.send(:full_client_document)[:application]).to be(nil) + expect(app_metadata.client_document[:application]).to be(nil) end end context 'when the client document exceeds the max of 512 bytes' do - # Server api parameters change metadata length - require_no_required_api_version - - context 'when the os.type length is too long' do - - before do - allow(app_metadata).to receive(:type).and_return('x'*500) + shared_examples_for 'a truncated document' do + it 'is too long before validation' do + expect(app_metadata.client_document.to_bson.to_s.size).to be > max_size end - it 'truncates the document' do - expect( - app_metadata.validated_document.to_bson.to_s.size - ).to be < described_class::MAX_DOCUMENT_SIZE + it 'is acceptable after validation' do + app_metadata.validated_document # force validation + expect(app_metadata.client_document.to_bson.to_s.size).to be <= max_size end end context 'when the os.name length is too long' do - before do allow(app_metadata).to receive(:name).and_return('x'*500) end - it 'truncates the document' do - expect( - app_metadata.validated_document.to_bson.to_s.size - ).to be < described_class::MAX_DOCUMENT_SIZE - end + it_behaves_like 'a truncated document' end context 'when the os.architecture length is too long' do - before do allow(app_metadata).to receive(:architecture).and_return('x'*500) end - it 'truncates the document' do - expect( - app_metadata.validated_document.to_bson.to_s.size - ).to be < described_class::MAX_DOCUMENT_SIZE - end + it_behaves_like 'a truncated document' end context 'when the platform length is too long' do - before do allow(app_metadata).to receive(:platform).and_return('x'*500) end - it 'truncates the document' do - expect( - app_metadata.validated_document.to_bson.to_s.size - ).to be < described_class::MAX_DOCUMENT_SIZE - end + it_behaves_like 'a truncated document' end + end - context 'when the driver info is too long' do - require_no_compression + context 'when run outside of a FaaS environment' do + it 'should exclude the :env key from the client document' do + expect(app_metadata.client_document.key?(:env)).to be false + end + end - before do - allow(app_metadata).to receive(:driver_doc).and_return('x'*500) + context 'when run inside of a FaaS environment' do + context 'when the environment is invalid' do + # invalid, because it is missing the other required fields + local_env('AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7') + + it 'should exclude the :env key from the client document' do + expect(app_metadata.client_document.key?(:env)).to be false end + end + + context 'when the environment is valid' do + # valid, because Azure requires only the one field + local_env('FUNCTIONS_WORKER_RUNTIME' => 'ruby') - it 'truncates the document' do - expect( - app_metadata.validated_document.to_bson.to_s.size - ).to be < described_class::MAX_DOCUMENT_SIZE + it 'should include the :env key in the client document' do + expect(app_metadata.client_document.key?(:env)).to be true + expect(app_metadata.client_document[:env][:name]).to be == "azure.func" end end end diff --git a/spec/mongo/socket/ssl_spec.rb b/spec/mongo/socket/ssl_spec.rb index d3d7b5fa9d..ecdb74e06a 100644 --- a/spec/mongo/socket/ssl_spec.rb +++ b/spec/mongo/socket/ssl_spec.rb @@ -592,14 +592,8 @@ ) end - around do |example| - saved = ENV['SSL_CERT_FILE'] - ENV['SSL_CERT_FILE'] = SpecConfig.instance.local_ca_cert_path - begin - example.run - ensure - ENV['SSL_CERT_FILE'] = saved - end + local_env do + { 'SSL_CERT_FILE' => SpecConfig.instance.local_ca_cert_path } end it 'uses the default cert store' do From 3c5cc83d1b24e654c7f89371bd9b99f47bdf4d25 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 1 Jun 2023 11:34:07 -0600 Subject: [PATCH 130/198] RUBY-3152 FaaS environment detection spec changes (#2724) * VERCEL_URL field was removed in https://github.com/mongodb/specifications/commit/27f9a691008538b939467581e56eaac66191e665 * if vercel & lambda are both present, prefer vercel --- lib/mongo/server/app_metadata/environment.rb | 6 +++++- spec/integration/connection/faas_env_spec.rb | 1 - .../server/app_metadata/environment_spec.rb | 20 +++++++++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/mongo/server/app_metadata/environment.rb b/lib/mongo/server/app_metadata/environment.rb index 5e9c556f7b..fafb1c73e7 100644 --- a/lib/mongo/server/app_metadata/environment.rb +++ b/lib/mongo/server/app_metadata/environment.rb @@ -78,7 +78,6 @@ class ValueTooLong < Mongo::Error; end }, 'vercel' => { - 'VERCEL_URL' => { field: :url, type: :string }, 'VERCEL_REGION' => { field: :region, type: :string }, }, }.freeze @@ -183,6 +182,11 @@ def detect_environment matches = DISCRIMINATORS.keys.select { |k| discriminator_matches?(k) } names = matches.map { |m| DISCRIMINATORS[m][:name] }.uniq + # From the spec: + # When variables for multiple ``client.env.name`` values are present, + # ``vercel`` takes precedence over ``aws.lambda``; any other + # combination MUST cause ``client.env`` to be entirely omitted. + return 'vercel' if names.sort == %w[ aws.lambda vercel ] raise TooManyEnvironments, names.join(', ') if names.length > 1 names.first diff --git a/spec/integration/connection/faas_env_spec.rb b/spec/integration/connection/faas_env_spec.rb index 08f2b55867..a9e613de7f 100644 --- a/spec/integration/connection/faas_env_spec.rb +++ b/spec/integration/connection/faas_env_spec.rb @@ -23,7 +23,6 @@ 'Valid Vercel' => { 'VERCEL' => '1', - 'VERCEL_URL' => '*.vercel.app', 'VERCEL_REGION' => 'cdg1', }, diff --git a/spec/mongo/server/app_metadata/environment_spec.rb b/spec/mongo/server/app_metadata/environment_spec.rb index 3bf88acca9..b63e174e16 100644 --- a/spec/mongo/server/app_metadata/environment_spec.rb +++ b/spec/mongo/server/app_metadata/environment_spec.rb @@ -65,6 +65,24 @@ end end + context 'when VERCEL and AWS are both given' do + local_env( + 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', + 'AWS_REGION' => 'us-east-2', + 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE' => '1024', + 'VERCEL' => '1', + 'VERCEL_REGION' => 'cdg1' + ) + + it_behaves_like 'running in a FaaS environment' + + it 'prefers vercel' do + expect(env.aws?).to be false + expect(env.vercel?).to be true + expect(env.fields[:region]).to be == 'cdg1' + end + end + context 'when environment is invalid due to missing variable' do local_env( 'AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7', @@ -161,7 +179,6 @@ context 'when FaaS environment is Vercel' do local_env( 'VERCEL' => '1', - 'VERCEL_URL' => '*.vercel.app', 'VERCEL_REGION' => 'cdg1' ) @@ -169,7 +186,6 @@ it 'recognizes Vercel' do expect(env.name).to be == 'vercel' - expect(env.fields[:url]).to be == '*.vercel.app' expect(env.fields[:region]).to be == 'cdg1' end end From e9d8dc17b260147c593b40fdb5d98e6db8b007ce Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 1 Jun 2023 13:09:46 -0600 Subject: [PATCH 131/198] RUBY-3152 Linter appeasement for #2722 (#2723) * add environment detection logic * add environment info, and updated truncation logic * rubocop pass for new files * prose tests from handshake spec * updated truncation logic, and AWS detection logic * minor cleanups * linter fixes for AppMetadata * fix the low-hanging linter fruit * linting updates * linting updates * fix missed linter complaint * fix broken tests caused by refactoring for linter --- lib/mongo/server/app_metadata.rb | 118 +- lib/mongo/server/app_metadata/platform.rb | 114 ++ spec/mongo/client_construction_spec.rb | 1217 +++++++++------------ spec/mongo/cluster_spec.rb | 298 ++--- spec/mongo/server/app_metadata_spec.rb | 40 +- 5 files changed, 852 insertions(+), 935 deletions(-) create mode 100644 lib/mongo/server/app_metadata/platform.rb diff --git a/lib/mongo/server/app_metadata.rb b/lib/mongo/server/app_metadata.rb index 88a75032c2..2d982c4f4b 100644 --- a/lib/mongo/server/app_metadata.rb +++ b/lib/mongo/server/app_metadata.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all # Copyright (C) 2016-2020 MongoDB Inc. # @@ -16,6 +15,7 @@ # limitations under the License. require 'mongo/server/app_metadata/environment' +require 'mongo/server/app_metadata/platform' require 'mongo/server/app_metadata/truncator' module Mongo @@ -24,35 +24,23 @@ class Server # when a new connection is established. # # @api private - # - # @since 2.4.0 class AppMetadata extend Forwardable # The max application name byte size. - # - # @since 2.4.0 - MAX_APP_NAME_SIZE = 128.freeze + MAX_APP_NAME_SIZE = 128 # The driver name. - # - # @since 2.4.0 DRIVER_NAME = 'mongo-ruby-driver' # Option keys that affect auth mechanism negotiation. - # - # @api private - AUTH_OPTION_KEYS = [:user, :auth_source, :auth_mech].freeze + AUTH_OPTION_KEYS = %i[ user auth_source auth_mech].freeze # Possible connection purposes. - # - # @api private - PURPOSES = %i(application monitor push_monitor).freeze + PURPOSES = %i[ application monitor push_monitor ].freeze # Instantiate the new AppMetadata object. # - # @api private - # # @example Instantiate the app metadata. # Mongo::Server::AppMetadata.new(options) # @@ -86,35 +74,33 @@ class AppMetadata def initialize(options = {}) @app_name = options[:app_name].to_s if options[:app_name] @platform = options[:platform] - if @purpose = options[:purpose] - unless PURPOSES.include?(@purpose) - raise ArgumentError, "Invalid purpose: #{@purpose}" - end - end + + @purpose = check_purpose!(options[:purpose]) + @compressors = options[:compressors] || [] @wrapping_libraries = options[:wrapping_libraries] @server_api = options[:server_api] - if options[:user] && !options[:auth_mech] - auth_db = options[:auth_source] || 'admin' - @request_auth_mech = "#{auth_db}.#{options[:user]}" - end + return unless options[:user] && !options[:auth_mech] + + auth_db = options[:auth_source] || 'admin' + @request_auth_mech = "#{auth_db}.#{options[:user]}" end # @return [ Symbol ] The purpose of the connection for which this # app metadata is created. - # - # @api private attr_reader :purpose + # @return [ String ] The platform information given when the object was + # instantiated. + attr_reader :platform + # @return [ Hash | nil ] The requested server API version. # # Thes hash can have the following items: # - *:version* -- string # - *:strict* -- boolean # - *:deprecation_errors* -- boolean - # - # @api private attr_reader :server_api # @return [ Array | nil ] Information about libraries wrapping @@ -130,8 +116,6 @@ def initialize(options = {}) # @return [BSON::Document] Valid document for connection's handshake. # # @raise [ Error::InvalidApplicationName ] When the metadata are invalid. - # - # @api private def validated_document validate! document @@ -141,15 +125,13 @@ def validated_document # handshake document. # # @return [BSON::Document] Document describing client for handshake. - # - # @api private def client_document @client_document ||= BSON::Document.new.tap do |doc| doc[:application] = { name: @app_name } if @app_name doc[:driver] = driver_doc doc[:os] = os_doc - doc[:platform] = platform + doc[:platform] = platform_string env_doc.tap { |env| doc[:env] = env if env } end end @@ -164,6 +146,7 @@ def validate! if @app_name && @app_name.bytesize > MAX_APP_NAME_SIZE raise Error::InvalidApplicationName.new(@app_name, MAX_APP_NAME_SIZE) end + true end @@ -183,14 +166,13 @@ def document end def driver_doc - names = [DRIVER_NAME] - versions = [Mongo::VERSION] - if wrapping_libraries - wrapping_libraries.each do |library| - names << library[:name] || '' - versions << library[:version] || '' - end + names = [ DRIVER_NAME ] + versions = [ Mongo::VERSION ] + wrapping_libraries&.each do |library| + names << (library[:name] || '') + versions << (library[:version] || '') end + { name: names.join('|'), version: versions.join('|'), @@ -201,18 +183,25 @@ def os_doc { type: type, name: name, - architecture: architecture + architecture: architecture, } end + # Returns the environment doc describing the current FaaS environment. + # + # @return [ Hash | nil ] the environment doc (or nil if not in a FaaS + # environment). def env_doc env = Environment.new env.faas? ? env.to_h : nil end def type - (RbConfig::CONFIG && RbConfig::CONFIG['host_os']) ? - RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase : 'unknown' + if RbConfig::CONFIG && RbConfig::CONFIG['host_os'] + RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase + else + 'unknown' + end end def name @@ -223,31 +212,22 @@ def architecture RbConfig::CONFIG['target_cpu'] end - def platform - if BSON::Environment.jruby? - ruby_versions = ["JRuby #{JRUBY_VERSION}", "like Ruby #{RUBY_VERSION}"] - platforms = [RUBY_PLATFORM, "JVM #{java.lang.System.get_property('java.version')}"] - else - ruby_versions = ["Ruby #{RUBY_VERSION}"] - platforms = [RUBY_PLATFORM] - end - platforms = [ - @platform, - *ruby_versions, - *platforms, - RbConfig::CONFIG['build'], - ] - if @purpose - platforms << @purpose.to_s[0].upcase - end - platform = platforms.compact.join(', ') - platforms = [platform] - if wrapping_libraries - wrapping_libraries.each do |library| - platforms << library[:platform] || '' - end - end - platforms.join('|') + def platform_string + Platform.new(self).to_s + end + + # Verifies that the given purpose is either nil, or is one of the + # allowed purposes. + # + # @param [ String | nil ] purpose The purpose to validate + # + # @return [ String | nil ] the {{purpose}} argument + # + # @raise [ ArgumentError ] if the purpose is invalid + def check_purpose!(purpose) + return purpose unless purpose && !PURPOSES.include?(purpose) + + raise ArgumentError, "Invalid purpose: #{purpose}" end end end diff --git a/lib/mongo/server/app_metadata/platform.rb b/lib/mongo/server/app_metadata/platform.rb new file mode 100644 index 0000000000..0df1b85459 --- /dev/null +++ b/lib/mongo/server/app_metadata/platform.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +# Copyright (C) 2016-2023 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Server + class AppMetadata + # Implements the logic for building the platform string for the + # handshake. + # + # @api private + class Platform + # @return [ Mongo::Server::AppMetadata ] the metadata object to + # reference when building the platform string. + attr_reader :metadata + + # Create a new Platform object, referencing the given metadata object. + # + # @param [ Mongo::Server::AppMetadata ] metadata the metadata object + # the reference when building the platform string. + def initialize(metadata) + @metadata = metadata + end + + # Queries whether the current runtime is JRuby or not. + # + # @return [ true | false ] whether the runtime is JRuby or not. + def jruby? + BSON::Environment.jruby? + end + + # Returns the list of Ruby versions that identify this runtime. + # + # @return [ Array ] the list of ruby versions + def ruby_versions + if jruby? + [ "JRuby #{JRUBY_VERSION}", "like Ruby #{RUBY_VERSION}" ] + else + [ "Ruby #{RUBY_VERSION}" ] + end + end + + # Returns the list of platform identifiers that identify this runtime. + # + # @return [ Array ] the list of platform identifiers. + def platforms + [ RUBY_PLATFORM ].tap do |list| + list.push "JVM #{java_version}" if jruby? + end + end + + # Returns the version of the current Java environment, or nil if not + # invoked with JRuby. + # + # @return [ String | nil ] the current Java version + def java_version + return nil unless jruby? + + java.lang.System.get_property('java.version') + end + + # Builds and returns the default platform list, for use when building + # the platform string. + # + # @return [ Array ] the list of platform identifiers + def default_platform_list + [ + metadata.platform, + *ruby_versions, + *platforms, + RbConfig::CONFIG['build'] + ] + end + + # Returns a single letter representing the purpose reported to the + # metadata, or nil if no purpose was specified. + # + # @return [ String | nil ] the code representing the purpose + def purpose + return nil unless metadata.purpose + + metadata.purpose.to_s[0].upcase + end + + # Builds and returns the platform string by concatenating relevant + # values together. + # + # @return [ String ] the platform string + def to_s + primary = [ *default_platform_list, purpose ].compact.join(', ') + list = [ primary ] + + metadata.wrapping_libraries&.each do |library| + list << (library[:platform] || '') + end + + list.join('|') + end + end + end + end +end diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index ec70621d92..0ea5715e8c 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -1,8 +1,19 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' +SINGLE_CLIENT = [ '127.0.0.1:27017' ].freeze + +# let these existing styles stand, rather than going in for a deep refactoring +# of these specs. +# +# possible future work: re-enable these one at a time and do the hard work of +# making them right. +# +# rubocop:disable RSpec/ExpectInHook, RSpec/MessageSpies, RSpec/ExampleLength +# rubocop:disable RSpec/ContextWording, RSpec/RepeatedExampleGroupDescription +# rubocop:disable RSpec/ExampleWording, Style/BlockComments, RSpec/AnyInstance +# rubocop:disable RSpec/VerifiedDoubles describe Mongo::Client do clean_slate @@ -18,7 +29,7 @@ # return should be instant c = Timeout.timeout(1) do - ClientRegistry.instance.new_local_client(['1.1.1.1'], scan: false) + ClientRegistry.instance.new_local_client([ '1.1.1.1' ], scan: false) end expect(c.cluster.servers).to be_empty c.close @@ -26,23 +37,21 @@ end context 'with default scan: true' do - shared_examples 'does not wait for server selection timeout' do - let(:logger) do - Logger.new(STDOUT, level: Logger::DEBUG) + Logger.new($stdout, level: Logger::DEBUG) end let(:subscriber) do Mongo::Monitoring::UnifiedSdamLogSubscriber.new( logger: logger, - log_prefix: 'CCS-SDAM', + log_prefix: 'CCS-SDAM' ) end let(:client) do ClientRegistry.instance.new_local_client( - [address], + [ address ], # Specify server selection timeout here because test suite sets # one by default and it's fairly low SpecConfig.instance.test_options.merge( @@ -51,10 +60,9 @@ server_selection_timeout: 8, logger: logger, log_prefix: 'CCS-CLIENT', - sdam_proc: lambda do |client| - subscriber.subscribe(client) - end - )) + sdam_proc: ->(client) { subscriber.subscribe(client) } + ) + ) end it 'does not wait for server selection timeout' do @@ -75,11 +83,11 @@ actual_class = client.cluster.topology.class expect([ - Mongo::Cluster::Topology::ReplicaSetWithPrimary, - Mongo::Cluster::Topology::Single, - Mongo::Cluster::Topology::Sharded, - Mongo::Cluster::Topology::LoadBalanced, - ]).to include(actual_class) + Mongo::Cluster::Topology::ReplicaSetWithPrimary, + Mongo::Cluster::Topology::Single, + Mongo::Cluster::Topology::Sharded, + Mongo::Cluster::Topology::LoadBalanced, + ]).to include(actual_class) expect(time_taken).to be < 5 # run a command to ensure the client is a working one @@ -90,17 +98,17 @@ context 'when cluster is monitored' do require_topology :single, :replica_set, :sharded - # TODO this test requires there being no outstanding background + # TODO: this test requires there being no outstanding background # monitoring threads running, as otherwise the scan! expectation # can be executed on a thread that belongs to one of the global # clients for instance it 'performs one round of sdam' do # Does not work due to # https://github.com/rspec/rspec-mocks/issues/1242. - #expect_any_instance_of(Mongo::Server::Monitor).to receive(:scan!). + # + # expect_any_instance_of(Mongo::Server::Monitor).to receive(:scan!). # exactly(SpecConfig.instance.addresses.length).times.and_call_original - c = new_local_client( - SpecConfig.instance.addresses, SpecConfig.instance.test_options) + c = new_local_client(SpecConfig.instance.addresses, SpecConfig.instance.test_options) expect(c.cluster.servers).not_to be_empty end @@ -128,7 +136,7 @@ context 'with monitoring_io: false' do let(:client) do - new_local_client(['127.0.0.1:27017'], monitoring_io: false) + new_local_client(SINGLE_CLIENT, monitoring_io: false) end it 'passes monitoring_io: false to cluster' do @@ -180,7 +188,7 @@ let(:mongocryptd_uri) { 'mongodb://localhost:27021' } let(:mongocryptd_bypass_spawn) { true } let(:mongocryptd_spawn_path) { '/spawn/path' } - let(:mongocryptd_spawn_args) { ['--idleShutdownTimeoutSecs=100'] } + let(:mongocryptd_spawn_args) { [ '--idleShutdownTimeoutSecs=100' ] } shared_examples 'a functioning auto encryption client' do let(:encryption_options) { client.encrypter.options } @@ -205,7 +213,10 @@ let(:key_vault_namespace) { 'not.good.formatting' } it 'raises an exception' do - expect { client }.to raise_error(ArgumentError, /key_vault_namespace option must be in the format database.collection/) + expect { client }.to raise_error( + ArgumentError, + /key_vault_namespace option must be in the format database.collection/ + ) end end @@ -221,7 +232,10 @@ let(:kms_providers) { { random_key: 'hello' } } it 'raises an exception' do - expect { client }.to raise_error(ArgumentError, /KMS providers options must have one of the following keys: :aws, :azure, :gcp, :kmip, :local/) + expect { client }.to raise_error( + ArgumentError, + /KMS providers options must have one of the following keys: :aws, :azure, :gcp, :kmip, :local/ + ) end end @@ -229,15 +243,25 @@ let(:kms_providers) { { local: { wrong_key: 'hello' } } } it 'raises an exception' do - expect { client }.to raise_error(ArgumentError, /Local KMS provider options must be in the format: { key: 'MASTER-KEY' }/) + expect { client }.to raise_error( + ArgumentError, + /Local KMS provider options must be in the format: { key: 'MASTER-KEY' }/ + ) end end context 'when aws kms_provider is incorrectly formatted' do let(:kms_providers) { { aws: { wrong_key: 'hello' } } } + let(:expected_options_format) do + "{ access_key_id: 'YOUR-ACCESS-KEY-ID', secret_access_key: 'SECRET-ACCESS-KEY' }" + end + it 'raises an exception' do - expect { client }.to raise_error(ArgumentError, / AWS KMS provider options must be in the format: { access_key_id: 'YOUR-ACCESS-KEY-ID', secret_access_key: 'SECRET-ACCESS-KEY' }/) + expect { client }.to raise_error( + ArgumentError, + / AWS KMS provider options must be in the format: #{expected_options_format}/ + ) end end @@ -267,7 +291,7 @@ expect(encryption_options[:key_vault_namespace]).to eq(key_vault_namespace) # Don't explicitly expect kms_providers to avoid accidentally exposing # sensitive data in evergreen logs - expect(encryption_options[:kms_providers]).to be_a_kind_of(Hash) + expect(encryption_options[:kms_providers]).to be_a(Hash) expect(encryption_options[:schema_map]).to eq(schema_map) expect(encryption_options[:bypass_auto_encryption]).to eq(bypass_auto_encryption) expect(encryption_options[:extra_options][:mongocryptd_uri]).to eq(mongocryptd_uri) @@ -300,20 +324,22 @@ expect(encryption_options[:extra_options][:mongocryptd_uri]).to eq('mongodb://localhost:27020') expect(encryption_options[:extra_options][:mongocryptd_bypass_spawn]).to be false expect(encryption_options[:extra_options][:mongocryptd_spawn_path]).to eq('mongocryptd') - expect(encryption_options[:extra_options][:mongocryptd_spawn_args]).to eq(['--idleShutdownTimeoutSecs=60']) + expect(encryption_options[:extra_options][:mongocryptd_spawn_args]) + .to eq([ '--idleShutdownTimeoutSecs=60' ]) end end context 'with mongocryptd_spawn_args that don\'t include idleShutdownTimeoutSecs' do - let(:mongocryptd_spawn_args) { ['--otherArgument=true'] } + let(:mongocryptd_spawn_args) { [ '--otherArgument=true' ] } it 'adds a default value to mongocryptd_spawn_args' do - expect(encryption_options[:extra_options][:mongocryptd_spawn_args]).to eq(mongocryptd_spawn_args + ['--idleShutdownTimeoutSecs=60']) + expect(encryption_options[:extra_options][:mongocryptd_spawn_args]) + .to eq(mongocryptd_spawn_args + [ '--idleShutdownTimeoutSecs=60' ]) end end context 'with mongocryptd_spawn_args that has idleShutdownTimeoutSecs as two arguments' do - let(:mongocryptd_spawn_args) { ['--idleShutdownTimeoutSecs', 100] } + let(:mongocryptd_spawn_args) { [ '--idleShutdownTimeoutSecs', 100 ] } it 'does not modify mongocryptd_spawn_args' do expect(encryption_options[:extra_options][:mongocryptd_spawn_args]).to eq(mongocryptd_spawn_args) @@ -326,7 +352,7 @@ it 'creates a key_vault_client' do key_vault_client = encryption_options[:key_vault_client] - expect(key_vault_client).to be_a_kind_of(Mongo::Client) + expect(key_vault_client).to be_a(described_class) end end end @@ -347,54 +373,47 @@ context 'timeout options' do let(:client) do - new_local_client(SpecConfig.instance.addresses, - SpecConfig.instance.authorized_test_options.merge(options)) + new_local_client( + SpecConfig.instance.addresses, + SpecConfig.instance.authorized_test_options.merge(options) + ) end context 'when network timeouts are zero' do - let(:options) do - { socket_timeout: 0, connect_timeout: 0 } - end + let(:options) { { socket_timeout: 0, connect_timeout: 0 } } it 'sets options to zeros' do - client.options[:socket_timeout].should == 0 - client.options[:connect_timeout].should == 0 + expect(client.options[:socket_timeout]).to be == 0 + expect(client.options[:connect_timeout]).to be == 0 end it 'connects and performs operations successfully' do - lambda do - client.database.command(ping: 1) - end.should_not raise_error + expect { client.database.command(ping: 1) } + .not_to raise_error end end - %i(socket_timeout connect_timeout).each do |option| + %i[ socket_timeout connect_timeout ].each do |option| context "when #{option} is negative" do - let(:options) do - { option => -1 } - end + let(:options) { { option => -1 } } it 'fails client creation' do - lambda do - client - end.should raise_error(ArgumentError, /#{option} must be a non-negative number/) + expect { client } + .to raise_error(ArgumentError, /#{option} must be a non-negative number/) end end context "when #{option} is of the wrong type" do - let(:options) do - { option => '42' } - end + let(:options) { { option => '42' } } it 'fails client creation' do - lambda do - client - end.should raise_error(ArgumentError, /#{option} must be a non-negative number/) + expect { client } + .to raise_error(ArgumentError, /#{option} must be a non-negative number/) end end end - context "when :connect_timeout is very small" do + context 'when :connect_timeout is very small' do # The driver reads first and checks the deadline second. # This means the read (in a monitor) can technically take more than # the connect timeout. Restrict to TLS configurations to make @@ -406,18 +425,15 @@ end it 'allows client creation' do - lambda do - client - end.should_not raise_error + expect { client }.not_to raise_error end context 'non-lb' do require_topology :single, :replica_set, :sharded it 'fails server selection due to very small timeout' do - lambda do - client.database.command(ping: 1) - end.should raise_error(Mongo::Error::NoServerAvailable) + expect { client.database.command(ping: 1) } + .to raise_error(Mongo::Error::NoServerAvailable) end end @@ -425,14 +441,13 @@ require_topology :load_balanced it 'fails the operation after successful server selection' do - lambda do - client.database.command(ping: 1) - end.should raise_error(Mongo::Error::SocketTimeoutError, /socket took over.*to connect/) + expect { client.database.command(ping: 1) } + .to raise_error(Mongo::Error::SocketTimeoutError, /socket took over.*to connect/) end end end - context "when :socket_timeout is very small" do + context 'when :socket_timeout is very small' do # The driver reads first and checks the deadline second. # This means the read (in a monitor) can technically take more than # the connect timeout. Restrict to TLS configurations to make @@ -444,16 +459,13 @@ end it 'allows client creation' do - lambda do - client - end.should_not raise_error + expect { client }.not_to raise_error end retry_test it 'fails operations due to very small timeout' do - lambda do - client.database.command(ping: 1) - end.should raise_error(Mongo::Error::SocketTimeoutError) + expect { client.database.command(ping: 1) } + .to raise_error(Mongo::Error::SocketTimeoutError) end end end @@ -464,7 +476,6 @@ end context 'when retry_writes is true' do - let(:options) do { retry_writes: true } end @@ -475,7 +486,6 @@ end context 'when retry_writes is false' do - let(:options) do { retry_writes: false } end @@ -486,10 +496,7 @@ end context 'when retry_writes is not given' do - - let(:options) do - { } - end + let(:options) { {} } it 'sets retry_writes to true' do expect(client.options['retry_writes']).to be true @@ -498,17 +505,18 @@ end context 'when compressors are provided' do - let(:client) do - new_local_client(SpecConfig.instance.addresses, - SpecConfig.instance.all_test_options.merge(options)) + new_local_client( + SpecConfig.instance.addresses, + SpecConfig.instance.all_test_options.merge(options) + ) end context 'when the compressor is not supported by the driver' do require_warning_clean let(:options) do - { compressors: ['snoopy'] } + { compressors: %w[ snoopy ] } end it 'does not set the compressor and warns' do @@ -525,16 +533,16 @@ min_server_fcv '3.6' let(:options) do - { compressors: ['zlib', 'snoopy'] } + { compressors: %w[ zlib snoopy ] } end it 'does not set the unsupported compressor and warns' do expect(Mongo::Logger.logger).to receive(:warn).at_least(:once) - expect(client.options['compressors']).to eq(['zlib']) + expect(client.options['compressors']).to eq(%w[ zlib ]) end it 'sets the compression key of the handshake document to the list of supported compressors' do - expect(client.cluster.app_metadata.send(:document)[:compression]).to eq(['zlib']) + expect(client.cluster.app_metadata.send(:document)[:compression]).to eq(%w[ zlib ]) end end end @@ -543,7 +551,7 @@ max_server_version '3.4' let(:options) do - { compressors: ['zlib'] } + { compressors: %w[ zlib ] } end it 'does not set the compressor and warns' do @@ -556,7 +564,7 @@ require_zlib_compression let(:options) do - { compressors: ['zlib'] } + { compressors: %w[ zlib ] } end it 'sets the compressor' do @@ -578,9 +586,7 @@ it 'does not use compression for authentication messages' do expect(Mongo::Protocol::Compressed).not_to receive(:new) - client.cluster.next_primary.send(:with_connection) do |conn| - conn.connect! - end + client.cluster.next_primary.send(:with_connection, &:connect!) end end @@ -588,14 +594,14 @@ min_server_version '3.6' let(:options) do - { compressors: ['snappy'] } + { compressors: %w[ snappy ] } end context 'when snappy gem is installed' do require_snappy_compression it 'creates the client' do - expect(client.options['compressors']).to eq(['snappy']) + expect(client.options['compressors']).to eq(%w[ snappy ]) end end @@ -614,14 +620,14 @@ min_server_version '4.2' let(:options) do - { compressors: ['zstd'] } + { compressors: %w[ zstd ] } end context 'when zstd gem is installed' do require_zstd_compression it 'creates the client' do - expect(client.options['compressors']).to eq(['zstd']) + expect(client.options['compressors']).to eq(%w[ zstd ]) end end @@ -663,7 +669,10 @@ min_server_fcv '3.6' let(:client) do - new_local_client_nmio(SpecConfig.instance.addresses, SpecConfig.instance.test_options.merge(zlib_compression_level: 1)) + new_local_client_nmio( + SpecConfig.instance.addresses, + SpecConfig.instance.test_options.merge(zlib_compression_level: 1) + ) end it 'sets the option on the client' do @@ -672,26 +681,25 @@ end context 'when ssl options are provided' do - let(:options) do { - :ssl => true, - :ssl_ca_cert => SpecConfig.instance.ca_cert_path, - :ssl_ca_cert_string => 'ca cert string', - :ssl_ca_cert_object => 'ca cert object', - :ssl_cert => SpecConfig.instance.client_cert_path, - :ssl_cert_string => 'cert string', - :ssl_cert_object => 'cert object', - :ssl_key => SpecConfig.instance.client_key_path, - :ssl_key_string => 'key string', - :ssl_key_object => 'key object', - :ssl_key_pass_phrase => 'passphrase', - :ssl_verify => true + ssl: true, + ssl_ca_cert: SpecConfig.instance.ca_cert_path, + ssl_ca_cert_string: 'ca cert string', + ssl_ca_cert_object: 'ca cert object', + ssl_cert: SpecConfig.instance.client_cert_path, + ssl_cert_string: 'cert string', + ssl_cert_object: 'cert object', + ssl_key: SpecConfig.instance.client_key_path, + ssl_key_string: 'key string', + ssl_key_object: 'key object', + ssl_key_pass_phrase: 'passphrase', + ssl_verify: true, } end let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], options) + new_local_client_nmio(SINGLE_CLIENT, options) end it 'sets the ssl option' do @@ -744,9 +752,8 @@ end context 'when no database is provided' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :read => { :mode => :secondary }) + new_local_client_nmio(SINGLE_CLIENT, read: { mode: :secondary }) end it 'defaults the database to admin' do @@ -755,9 +762,8 @@ end context 'when a database is provided' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :database => :testdb) + new_local_client_nmio(SINGLE_CLIENT, database: :testdb) end it 'sets the current database' do @@ -766,9 +772,8 @@ end context 'when providing a custom logger' do - let(:logger) do - Logger.new(STDOUT).tap do |l| + Logger.new($stdout).tap do |l| l.level = Logger::FATAL end end @@ -778,14 +783,13 @@ end it 'does not use the global logger' do - expect(client.cluster.logger).to_not eq(Mongo::Logger.logger) + expect(client.cluster.logger).not_to eq(Mongo::Logger.logger) end end context 'when providing a heartbeat_frequency' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :heartbeat_frequency => 2) + new_local_client_nmio(SINGLE_CLIENT, heartbeat_frequency: 2) end it 'sets the heartbeat frequency' do @@ -794,37 +798,20 @@ end context 'when min_pool_size is provided' do - - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], options) - end + let(:client) { new_local_client_nmio(SINGLE_CLIENT, options) } context 'when max_pool_size is provided' do - context 'when the min_pool_size is greater than the max_pool_size' do - - let(:options) do - { - :min_pool_size => 20, - :max_pool_size => 10 - } - end + let(:options) { { min_pool_size: 20, max_pool_size: 10 } } it 'raises an Exception' do - expect { - client - }.to raise_exception(Mongo::Error::InvalidMinPoolSize) + expect { client } + .to raise_exception(Mongo::Error::InvalidMinPoolSize) end end context 'when the min_pool_size is less than the max_pool_size' do - - let(:options) do - { - :min_pool_size => 10, - :max_pool_size => 20 - } - end + let(:options) { { min_pool_size: 10, max_pool_size: 20 } } it 'sets the option' do expect(client.options[:min_pool_size]).to eq(options[:min_pool_size]) @@ -833,13 +820,7 @@ end context 'when the min_pool_size is equal to the max_pool_size' do - - let(:options) do - { - :min_pool_size => 10, - :max_pool_size => 10 - } - end + let(:options) { { min_pool_size: 10, max_pool_size: 10 } } it 'sets the option' do expect(client.options[:min_pool_size]).to eq(options[:min_pool_size]) @@ -848,12 +829,7 @@ end context 'when max_pool_size is zero (unlimited)' do - let(:options) do - { - :min_pool_size => 10, - :max_pool_size => 0 - } - end + let(:options) { { min_pool_size: 10, max_pool_size: 0 } } it 'sets the option' do expect(client.options[:min_pool_size]).to eq(options[:min_pool_size]) @@ -863,29 +839,17 @@ end context 'when max_pool_size is not provided' do - context 'when the min_pool_size is greater than the default max_pool_size' do - - let(:options) do - { - :min_pool_size => 30 - } - end + let(:options) { { min_pool_size: 30 } } it 'raises an Exception' do - expect { - client - }.to raise_exception(Mongo::Error::InvalidMinPoolSize) + expect { client } + .to raise_exception(Mongo::Error::InvalidMinPoolSize) end end context 'when the min_pool_size is less than the default max_pool_size' do - - let(:options) do - { - :min_pool_size => 3 - } - end + let(:options) { { min_pool_size: 3 } } it 'sets the option' do expect(client.options[:min_pool_size]).to eq(options[:min_pool_size]) @@ -893,10 +857,9 @@ end context 'when the min_pool_size is equal to the max_pool_size' do - let(:options) do { - :min_pool_size => Mongo::Server::ConnectionPool::DEFAULT_MAX_SIZE + min_pool_size: Mongo::Server::ConnectionPool::DEFAULT_MAX_SIZE } end @@ -907,36 +870,18 @@ end end - context 'when max_pool_size is provided' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], options) - end - - context 'when max_pool_size is 0 (unlimited)' do - let(:options) do - { - :max_pool_size => 0 - } - end + context 'when max_pool_size is 0 (unlimited)' do + let(:client) { new_local_client_nmio(SINGLE_CLIENT, options) } + let(:options) { { max_pool_size: 0 } } - it 'sets the option' do - expect(client.options[:max_pool_size]).to eq(options[:max_pool_size]) - end + it 'sets the option' do + expect(client.options[:max_pool_size]).to eq(options[:max_pool_size]) end end context 'when max_pool_size and min_pool_size are both nil' do - - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], options) - end - - let(:options) do - { - :min_pool_size => nil, - :max_pool_size => nil - } - end + let(:options) { { min_pool_size: nil, max_pool_size: nil } } + let(:client) { new_local_client_nmio(SINGLE_CLIENT, options) } it 'does not set either option' do expect(client.options[:max_pool_size]).to be_nil @@ -945,13 +890,12 @@ end context 'when platform details are specified' do - let(:app_metadata) do client.cluster.app_metadata end let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :platform => 'mongoid-6.0.2') + new_local_client_nmio(SINGLE_CLIENT, platform: 'mongoid-6.0.2') end it 'includes the platform info in the app metadata' do @@ -960,13 +904,12 @@ end context 'when platform details are not specified' do - let(:app_metadata) do client.cluster.app_metadata end let(:client) do - new_local_client_nmio(['127.0.0.1:27017']) + new_local_client_nmio(SINGLE_CLIENT) end context 'mri' do @@ -1008,17 +951,11 @@ end context 'when providing a connection string' do - context 'when the string uses the SRV Protocol' do require_external_connectivity - let!(:uri) do - 'mongodb+srv://test5.test.build.10gen.cc/testdb' - end - - let(:client) do - new_local_client_nmio(uri) - end + let(:uri) { 'mongodb+srv://test5.test.build.10gen.cc/testdb' } + let(:client) { new_local_client_nmio(uri) } it 'sets the database' do expect(client.options[:database]).to eq('testdb') @@ -1026,29 +963,17 @@ end context 'when a database is provided' do - - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb' - end - - let(:client) do - new_local_client_nmio(uri) - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb' } + let(:client) { new_local_client_nmio(uri) } it 'sets the database' do - expect { client[:users] }.to_not raise_error + expect { client[:users] }.not_to raise_error end end context 'when a database is not provided' do - - let!(:uri) do - 'mongodb://127.0.0.1:27017' - end - - let(:client) do - new_local_client_nmio(uri) - end + let(:uri) { 'mongodb://127.0.0.1:27017' } + let(:client) { new_local_client_nmio(uri) } it 'defaults the database to admin' do expect(client.database.name).to eq('admin') @@ -1056,19 +981,17 @@ end context 'when URI options are provided' do - - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb?w=3' - end - - let(:client) do - new_local_client_nmio(uri) - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb?w=3' } + let(:client) { new_local_client_nmio(uri) } let(:expected_options) do - Mongo::Options::Redacted.new(write_concern: { :w => 3 }, - monitoring_io: false, :database => 'testdb', retry_writes: true, - retry_reads: true) + Mongo::Options::Redacted.new( + write_concern: { w: 3 }, + monitoring_io: false, + database: 'testdb', + retry_writes: true, + retry_reads: true + ) end it 'sets the options' do @@ -1076,24 +999,19 @@ end context 'when min_pool_size is provided' do - context 'when max_pool_size is provided' do - context 'when the min_pool_size is greater than the max_pool_size' do - let(:uri) do 'mongodb://127.0.0.1:27017/?minPoolSize=20&maxPoolSize=10' end it 'raises an Exception' do - expect { - client - }.to raise_exception(Mongo::Error::InvalidMinPoolSize) + expect { client } + .to raise_exception(Mongo::Error::InvalidMinPoolSize) end end context 'when the min_pool_size is less than the max_pool_size' do - let(:uri) do 'mongodb://127.0.0.1:27017/?minPoolSize=10&maxPoolSize=20' end @@ -1105,7 +1023,6 @@ end context 'when the min_pool_size is equal to the max_pool_size' do - let(:uri) do 'mongodb://127.0.0.1:27017/?minPoolSize=10&maxPoolSize=10' end @@ -1129,25 +1046,17 @@ end context 'when max_pool_size is not provided' do - context 'when the min_pool_size is greater than the default max_pool_size' do - - let(:uri) do - 'mongodb://127.0.0.1:27017/?minPoolSize=30' - end + let(:uri) { 'mongodb://127.0.0.1:27017/?minPoolSize=30' } it 'raises an Exception' do - expect { - client - }.to raise_exception(Mongo::Error::InvalidMinPoolSize) + expect { client } + .to raise_exception(Mongo::Error::InvalidMinPoolSize) end end context 'when the min_pool_size is less than the default max_pool_size' do - - let(:uri) do - 'mongodb://127.0.0.1:27017/?minPoolSize=3' - end + let(:uri) { 'mongodb://127.0.0.1:27017/?minPoolSize=3' } it 'sets the option' do expect(client.options[:min_pool_size]).to eq(3) @@ -1155,10 +1064,7 @@ end context 'when the min_pool_size is equal to the max_pool_size' do - - let(:uri) do - 'mongodb://127.0.0.1:27017/?minPoolSize=5' - end + let(:uri) { 'mongodb://127.0.0.1:27017/?minPoolSize=5' } it 'sets the option' do expect(client.options[:min_pool_size]).to eq(5) @@ -1168,11 +1074,8 @@ end context 'when retryReads URI option is given' do - context 'it is false' do - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb?retryReads=false' - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb?retryReads=false' } it 'sets the option on the client' do expect(client.options[:retry_reads]).to be false @@ -1180,9 +1083,7 @@ end context 'it is true' do - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb?retryReads=true' - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb?retryReads=true' } it 'sets the option on the client' do expect(client.options[:retry_reads]).to be true @@ -1191,11 +1092,8 @@ end context 'when retryWrites URI option is given' do - context 'it is false' do - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb?retryWrites=false' - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb?retryWrites=false' } it 'sets the option on the client' do expect(client.options[:retry_writes]).to be false @@ -1203,9 +1101,7 @@ end context 'it is true' do - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb?retryWrites=true' - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb?retryWrites=true' } it 'sets the option on the client' do expect(client.options[:retry_writes]).to be true @@ -1215,19 +1111,20 @@ end context 'when options are provided not in the string' do - - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb' - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb' } let(:client) do - new_local_client_nmio(uri, :write => { :w => 3 }) + new_local_client_nmio(uri, write: { w: 3 }) end let(:expected_options) do - Mongo::Options::Redacted.new(:write => { :w => 3 }, - monitoring_io: false, :database => 'testdb', retry_writes: true, - retry_reads: true) + Mongo::Options::Redacted.new( + write: { w: 3 }, + monitoring_io: false, + database: 'testdb', + retry_writes: true, + retry_reads: true + ) end it 'sets the options' do @@ -1236,19 +1133,20 @@ end context 'when options are provided in the URI and as Ruby options' do - - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb?w=3' - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb?w=3' } let(:client) do - new_local_client_nmio(uri, option_name => { :w => 4 }) + new_local_client_nmio(uri, option_name => { w: 4 }) end let(:expected_options) do - Mongo::Options::Redacted.new(option_name => { :w => 4 }, - monitoring_io: false, :database => 'testdb', retry_writes: true, - retry_reads: true) + Mongo::Options::Redacted.new( + option_name => { w: 4 }, + monitoring_io: false, + database: 'testdb', + retry_writes: true, + retry_reads: true + ) end shared_examples_for 'allows explicit options to take preference' do @@ -1271,14 +1169,8 @@ end context 'when a replica set name is provided' do - - let!(:uri) do - 'mongodb://127.0.0.1:27017/testdb?replicaSet=testing' - end - - let(:client) do - new_local_client_nmio(uri) - end + let(:uri) { 'mongodb://127.0.0.1:27017/testdb?replicaSet=testing' } + let(:client) { new_local_client_nmio(uri) } it 'sets the correct cluster topology' do expect(client.cluster.topology).to be_a(Mongo::Cluster::Topology::ReplicaSetNoPrimary) @@ -1288,295 +1180,262 @@ context 'when Ruby options are provided' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], options) + new_local_client_nmio(SINGLE_CLIENT, options) end describe 'connection option conflicts' do context 'direct_connection: true and multiple seeds' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017', '127.0.0.2:27017'], - direct_connection: true) + new_local_client_nmio([ '127.0.0.1:27017', '127.0.0.2:27017' ], direct_connection: true) end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /direct_connection=true cannot be used with multiple seeds/) + expect { client } + .to raise_error(ArgumentError, /direct_connection=true cannot be used with multiple seeds/) end end context 'direct_connection: true and connect: :direct' do let(:options) do - {direct_connection: true, connect: :direct} + { direct_connection: true, connect: :direct } end it 'is accepted' do - client.options[:direct_connection].should be true - client.options[:connect].should be :direct + expect(client.options[:direct_connection]).to be true + expect(client.options[:connect]).to be :direct end end context 'direct_connection: true and connect: :replica_set' do let(:options) do - {direct_connection: true, connect: :replica_set} + { direct_connection: true, connect: :replica_set } end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /Conflicting client options: direct_connection=true and connect=replica_set/) + expect { client } + .to raise_error( + ArgumentError, + /Conflicting client options: direct_connection=true and connect=replica_set/ + ) end end context 'direct_connection: true and connect: :sharded' do let(:options) do - {direct_connection: true, connect: :sharded} + { direct_connection: true, connect: :sharded } end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /Conflicting client options: direct_connection=true and connect=sharded/) + expect { client } + .to raise_error(ArgumentError, /Conflicting client options: direct_connection=true and connect=sharded/) end end context 'direct_connection: false and connect: :direct' do let(:options) do - {direct_connection: false, connect: :direct} + { direct_connection: false, connect: :direct } end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /Conflicting client options: direct_connection=false and connect=direct/) + expect { client } + .to raise_error(ArgumentError, /Conflicting client options: direct_connection=false and connect=direct/) end end context 'direct_connection: false and connect: :replica_set' do let(:options) do - {direct_connection: false, connect: :replica_set, replica_set: 'foo'} + { direct_connection: false, connect: :replica_set, replica_set: 'foo' } end it 'is accepted' do - client.options[:direct_connection].should be false - client.options[:connect].should be :replica_set + expect(client.options[:direct_connection]).to be false + expect(client.options[:connect]).to be :replica_set end end context 'direct_connection: false and connect: :sharded' do let(:options) do - {direct_connection: false, connect: :sharded} + { direct_connection: false, connect: :sharded } end it 'is accepted' do - client.options[:direct_connection].should be false - client.options[:connect].should be :sharded + expect(client.options[:direct_connection]).to be false + expect(client.options[:connect]).to be :sharded end end context 'load_balanced: true and multiple seeds' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017', '127.0.0.2:27017'], - load_balanced: true) + new_local_client_nmio([ '127.0.0.1:27017', '127.0.0.2:27017' ], load_balanced: true) end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /load_balanced=true cannot be used with multiple seeds/) + expect { client } + .to raise_error(ArgumentError, /load_balanced=true cannot be used with multiple seeds/) end end context 'load_balanced: false and multiple seeds' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017', '127.0.0.2:27017'], - load_balanced: false) + new_local_client_nmio([ '127.0.0.1:27017', '127.0.0.2:27017' ], load_balanced: false) end it 'is accepted' do - lambda do - client - end.should_not raise_error - client.options[:load_balanced].should be false + expect { client }.not_to raise_error + expect(client.options[:load_balanced]).to be false end end context 'load_balanced: true and direct_connection: true' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - load_balanced: true, direct_connection: true) + new_local_client_nmio(SINGLE_CLIENT, load_balanced: true, direct_connection: true) end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /direct_connection=true cannot be used with load_balanced=true/) + expect { client } + .to raise_error(ArgumentError, /direct_connection=true cannot be used with load_balanced=true/) end end context 'load_balanced: true and direct_connection: false' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - load_balanced: true, direct_connection: false) + new_local_client_nmio(SINGLE_CLIENT, load_balanced: true, direct_connection: false) end it 'is accepted' do - lambda do - client - end.should_not raise_error - client.options[:load_balanced].should be true - client.options[:direct_connection].should be false + expect { client }.not_to raise_error + expect(client.options[:load_balanced]).to be true + expect(client.options[:direct_connection]).to be false end end context 'load_balanced: false and direct_connection: true' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - load_balanced: false, direct_connection: true) + new_local_client_nmio(SINGLE_CLIENT, load_balanced: false, direct_connection: true) end it 'is accepted' do - lambda do - client - end.should_not raise_error - client.options[:load_balanced].should be false - client.options[:direct_connection].should be true + expect { client }.not_to raise_error + expect(client.options[:load_balanced]).to be false + expect(client.options[:direct_connection]).to be true end end - [:direct, 'direct', :sharded, 'sharded'].each do |v| + [ :direct, 'direct', :sharded, 'sharded' ].each do |v| context "load_balanced: true and connect: #{v.inspect}" do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - load_balanced: true, connect: v) + new_local_client_nmio(SINGLE_CLIENT, load_balanced: true, connect: v) end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /connect=#{v} cannot be used with load_balanced=true/) + expect { client } + .to raise_error(ArgumentError, /connect=#{v} cannot be used with load_balanced=true/) end end end - [nil].each do |v| + [ nil ].each do |v| context "load_balanced: true and connect: #{v.inspect}" do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - load_balanced: true, connect: v) + new_local_client_nmio(SINGLE_CLIENT, load_balanced: true, connect: v) end it 'is accepted' do - lambda do - client - end.should_not raise_error - client.options[:load_balanced].should be true - client.options[:connect].should eq v + expect { client }.not_to raise_error + expect(client.options[:load_balanced]).to be true + expect(client.options[:connect]).to eq v end end end - [:load_balanced, 'load_balanced'].each do |v| + [ :load_balanced, 'load_balanced' ].each do |v| context "load_balanced: true and connect: #{v.inspect}" do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - load_balanced: true, connect: v) + new_local_client_nmio(SINGLE_CLIENT, load_balanced: true, connect: v) end it 'is accepted' do - lambda do - client - end.should_not raise_error - client.options[:load_balanced].should be true - client.options[:connect].should eq v + expect { client }.not_to raise_error + expect(client.options[:load_balanced]).to be true + expect(client.options[:connect]).to eq v end end context "replica_set and connect: #{v.inspect}" do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - replica_set: 'foo', connect: v) + new_local_client_nmio(SINGLE_CLIENT, replica_set: 'foo', connect: v) end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /connect=load_balanced cannot be used with replica_set option/) + expect { client } + .to raise_error(ArgumentError, /connect=load_balanced cannot be used with replica_set option/) end end context "direct_connection=true and connect: #{v.inspect}" do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - direct_connection: true, connect: v) + new_local_client_nmio(SINGLE_CLIENT, direct_connection: true, connect: v) end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /Conflicting client options: direct_connection=true and connect=load_balanced/) + expect { client } + .to raise_error( + ArgumentError, + /Conflicting client options: direct_connection=true and connect=load_balanced/ + ) end end context "multiple seed addresses and connect: #{v.inspect}" do let(:client) do - new_local_client_nmio(['127.0.0.1:27017', '127.0.0.1:1234'], - connect: v) + new_local_client_nmio([ '127.0.0.1:27017', '127.0.0.1:1234' ], connect: v) end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /connect=load_balanced cannot be used with multiple seeds/) + expect { client } + .to raise_error(ArgumentError, /connect=load_balanced cannot be used with multiple seeds/) end end end - [:replica_set, 'replica_set'].each do |v| + [ :replica_set, 'replica_set' ].each do |v| context "load_balanced: true and connect: #{v.inspect}" do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - load_balanced: true, connect: v, replica_set: 'x') + new_local_client_nmio(SINGLE_CLIENT, load_balanced: true, connect: v, replica_set: 'x') end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /connect=replica_set cannot be used with load_balanced=true/) + expect { client } + .to raise_error(ArgumentError, /connect=replica_set cannot be used with load_balanced=true/) end end context "load_balanced: true and #{v.inspect} option" do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - load_balanced: true, v => 'rs') + new_local_client_nmio(SINGLE_CLIENT, load_balanced: true, v => 'rs') end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /load_balanced=true cannot be used with replica_set option/) + expect { client } + .to raise_error(ArgumentError, /load_balanced=true cannot be used with replica_set option/) end end end context 'srv_max_hosts > 0 and load_balanced: true' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - srv_max_hosts: 1, load_balanced: true) + new_local_client_nmio(SINGLE_CLIENT, srv_max_hosts: 1, load_balanced: true) end it 'it is rejected' do - expect do - client - end.to raise_error(ArgumentError, /:srv_max_hosts > 0 cannot be used with :load_balanced=true/) + expect { client } + .to raise_error(ArgumentError, /:srv_max_hosts > 0 cannot be used with :load_balanced=true/) end end context 'srv_max_hosts > 0 and replica_set' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - srv_max_hosts: 1, replica_set: 'rs') + new_local_client_nmio(SINGLE_CLIENT, srv_max_hosts: 1, replica_set: 'rs') end it 'it is rejected' do @@ -1588,55 +1447,45 @@ context 'srv_max_hosts < 0' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - srv_max_hosts: -1) + new_local_client_nmio(SINGLE_CLIENT, srv_max_hosts: -1) end it 'is accepted and does not add the srv_max_hosts to uri_options' do - lambda do - client - end.should_not raise_error - expect(client.options).to_not have_key(:srv_max_hosts) + expect { client }.not_to raise_error + expect(client.options).not_to have_key(:srv_max_hosts) end end context 'srv_max_hosts invalid type' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - srv_max_hosts: 'foo') + new_local_client_nmio(SINGLE_CLIENT, srv_max_hosts: 'foo') end it 'is accepted and does not add the srv_max_hosts to uri_options' do - lambda do - client - end.should_not raise_error - expect(client.options).to_not have_key(:srv_max_hosts) + expect { client }.not_to raise_error + expect(client.options).not_to have_key(:srv_max_hosts) end end context 'srv_max_hosts with non-SRV URI' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - srv_max_hosts: 1) + new_local_client_nmio(SINGLE_CLIENT, srv_max_hosts: 1) end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /:srv_max_hosts cannot be used on non-SRV URI/) + expect { client } + .to raise_error(ArgumentError, /:srv_max_hosts cannot be used on non-SRV URI/) end end context 'srv_service_name with non-SRV URI' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - srv_service_name: "customname") + new_local_client_nmio(SINGLE_CLIENT, srv_service_name: 'customname') end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /:srv_service_name cannot be used on non-SRV URI/) + expect { client } + .to raise_error(ArgumentError, /:srv_service_name cannot be used on non-SRV URI/) end end end @@ -1646,7 +1495,8 @@ double('srv result').tap do |result| allow(result).to receive(:empty?).and_return(false) allow(result).to receive(:address_strs).and_return( - [ClusterConfig.instance.primary_address_str]) + [ ClusterConfig.instance.primary_address_str ] + ) end end @@ -1657,79 +1507,77 @@ new_local_client_nmio('mongodb+srv://foo.a.b', options) end - context "when setting srv_max_hosts" do + context 'when setting srv_max_hosts' do let(:srv_max_hosts) { 1 } let(:options) { { srv_max_hosts: srv_max_hosts } } it 'is accepted and sets srv_max_hosts' do - lambda do - client - end.should_not raise_error + expect { client }.not_to raise_error expect(client.options[:srv_max_hosts]).to eq(srv_max_hosts) end end - context "when setting srv_max_hosts to 0" do + context 'when setting srv_max_hosts to 0' do let(:srv_max_hosts) { 0 } let(:options) { { srv_max_hosts: srv_max_hosts } } it 'is accepted sets srv_max_hosts' do - lambda do - client - end.should_not raise_error + expect { client }.not_to raise_error expect(client.options[:srv_max_hosts]).to eq(srv_max_hosts) end end - context "when setting srv_service_name" do + context 'when setting srv_service_name' do let(:srv_service_name) { 'customname' } let(:options) { { srv_service_name: srv_service_name } } it 'is accepted and sets srv_service_name' do - lambda do - client - end.should_not raise_error + expect { client }.not_to raise_error expect(client.options[:srv_service_name]).to eq(srv_service_name) end end end context ':bg_error_backtrace option' do - [true, false, nil, 42].each do |valid_value| + [ true, false, nil, 42 ].each do |valid_value| context "valid value: #{valid_value.inspect}" do let(:options) do - {bg_error_backtrace: valid_value} + { bg_error_backtrace: valid_value } end it 'is accepted' do - client.options[:bg_error_backtrace].should == valid_value + expect(client.options[:bg_error_backtrace]).to be == valid_value end end end context 'invalid value type' do let(:options) do - {bg_error_backtrace: 'yes'} + { bg_error_backtrace: 'yes' } end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /:bg_error_backtrace option value must be true, false, nil or a positive integer/) + expect { client } + .to raise_error( + ArgumentError, + /:bg_error_backtrace option value must be true, false, nil or a positive integer/ + ) end end context 'invalid value' do - [0, -1, 42.0].each do |invalid_value| + [ 0, -1, 42.0 ].each do |invalid_value| context "invalid value: #{invalid_value.inspect}" do let(:options) do - {bg_error_backtrace: invalid_value} + { bg_error_backtrace: invalid_value } end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /:bg_error_backtrace option value must be true, false, nil or a positive integer/) + expect { client } + .to raise_error( + ArgumentError, + /:bg_error_backtrace option value must be true, false, nil or a positive integer/ + ) end end end @@ -1737,32 +1585,30 @@ end describe ':read option' do - [ - :primary, :primary_preferred, :secondary, :secondary_preferred, :nearest - ].each do |sym| - describe "#{sym}" do + %i[ primary primary_preferred secondary secondary_preferred nearest ].each do |sym| + describe sym.to_s do context 'when given as symbol' do let(:options) do - {read: {mode: sym}} + { read: { mode: sym } } end - it "is accepted" do + it 'is accepted' do # the key got converted to a string here - expect(client.read_preference).to eq({'mode' => sym}) + expect(client.read_preference).to eq({ 'mode' => sym }) end end context 'when given as string' do let(:options) do - {read: {mode: sym.to_s}} + { read: { mode: sym.to_s } } end # string keys are not documented as being allowed # but the code accepts them - it "is accepted" do + it 'is accepted' do # the key got converted to a string here # the value remains a string - expect(client.read_preference).to eq({'mode' => sym.to_s}) + expect(client.read_preference).to eq({ 'mode' => sym.to_s }) end end end @@ -1773,30 +1619,41 @@ it 'rejects bogus read preference as symbol' do expect do - client = new_local_client_nmio(['127.0.0.1:27017'], - :read => {:mode => :bogus}) - end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read preference value: {"mode"=>:bogus}: mode bogus is not one of recognized modes') + new_local_client_nmio(SINGLE_CLIENT, read: { mode: :bogus }) + end.to raise_error( + Mongo::Error::InvalidReadOption, + 'Invalid read preference value: {"mode"=>:bogus}: ' \ + 'mode bogus is not one of recognized modes' + ) end it 'rejects bogus read preference as string' do expect do - client = new_local_client_nmio(['127.0.0.1:27017'], - :read => {:mode => 'bogus'}) - end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read preference value: {"mode"=>"bogus"}: mode bogus is not one of recognized modes') + new_local_client_nmio(SINGLE_CLIENT, read: { mode: 'bogus' }) + end.to raise_error( + Mongo::Error::InvalidReadOption, + 'Invalid read preference value: {"mode"=>"bogus"}: mode bogus is not one of recognized modes' + ) end it 'rejects read option specified as a string' do expect do - client = new_local_client_nmio(['127.0.0.1:27017'], - :read => 'primary') - end.to raise_error(Mongo::Error::InvalidReadOption, 'Invalid read preference value: "primary": the read preference must be specified as a hash: { mode: "primary" }') + new_local_client_nmio(SINGLE_CLIENT, read: 'primary') + end.to raise_error( + Mongo::Error::InvalidReadOption, + 'Invalid read preference value: "primary": ' \ + 'the read preference must be specified as a hash: { mode: "primary" }' + ) end it 'rejects read option specified as a symbol' do expect do - client = new_local_client_nmio(['127.0.0.1:27017'], - :read => :primary) - end.to raise_error(Mongo::Error::InvalidReadOption, "Invalid read preference value: :primary: the read preference must be specified as a hash: { mode: :primary }") + new_local_client_nmio(SINGLE_CLIENT, read: :primary) + end.to raise_error( + Mongo::Error::InvalidReadOption, + 'Invalid read preference value: :primary: ' \ + 'the read preference must be specified as a hash: { mode: :primary }' + ) end end end @@ -1810,7 +1667,7 @@ end it 'does not warn' do - expect(Mongo::Logger.logger).to_not receive(:warn) + expect(Mongo::Logger.logger).not_to receive(:warn) new_local_client_nmio(SpecConfig.instance.addresses, options) end end @@ -1836,15 +1693,17 @@ it 'raises an exception' do expect do new_local_client_nmio(SpecConfig.instance.addresses, options) - end.to raise_error(Mongo::Error::InvalidReadConcern, 'The after_cluster_time read_concern option cannot be specified by the user') + end.to raise_error( + Mongo::Error::InvalidReadConcern, + 'The after_cluster_time read_concern option cannot be specified by the user' + ) end end end context 'when an invalid option is provided' do - let(:options) do - {ssl: false, invalid: :test} + { ssl: false, invalid: :test } end it 'does not set the option' do @@ -1863,9 +1722,8 @@ =begin WriteConcern object support context 'when write concern is provided via a WriteConcern object' do - let(:options) do - {write_concern: wc} + { write_concern: wc } end let(:wc) { Mongo::WriteConcern.get(w: 2) } @@ -1883,52 +1741,52 @@ context ':wrapping_libraries option' do let(:options) do - {wrapping_libraries: wrapping_libraries} + { wrapping_libraries: wrapping_libraries } end context 'valid input' do context 'symbol keys' do let(:wrapping_libraries) do - [name: 'Mongoid', version: '7.1.2'].freeze + [ { name: 'Mongoid', version: '7.1.2' } ].freeze end it 'works' do - client.options[:wrapping_libraries].should == ['name' => 'Mongoid', 'version' => '7.1.2'] + expect(client.options[:wrapping_libraries]).to be == [ { 'name' => 'Mongoid', 'version' => '7.1.2' } ] end end context 'string keys' do let(:wrapping_libraries) do - ['name' => 'Mongoid', 'version' => '7.1.2'].freeze + [ { 'name' => 'Mongoid', 'version' => '7.1.2' } ].freeze end it 'works' do - client.options[:wrapping_libraries].should == ['name' => 'Mongoid', 'version' => '7.1.2'] + expect(client.options[:wrapping_libraries]).to be == [ { 'name' => 'Mongoid', 'version' => '7.1.2' } ] end end context 'Redacted keys' do let(:wrapping_libraries) do - [Mongo::Options::Redacted.new(name: 'Mongoid', version: '7.1.2')].freeze + [ Mongo::Options::Redacted.new(name: 'Mongoid', version: '7.1.2') ].freeze end it 'works' do - client.options[:wrapping_libraries].should == ['name' => 'Mongoid', 'version' => '7.1.2'] + expect(client.options[:wrapping_libraries]).to be == [ { 'name' => 'Mongoid', 'version' => '7.1.2' } ] end end context 'two libraries' do let(:wrapping_libraries) do [ - {name: 'Mongoid', version: '7.1.2'}, - {name: 'Rails', version: '4.0', platform: 'Foobar'}, + { name: 'Mongoid', version: '7.1.2' }, + { name: 'Rails', version: '4.0', platform: 'Foobar' }, ].freeze end it 'works' do - client.options[:wrapping_libraries].should == [ - {'name' => 'Mongoid', 'version' => '7.1.2'}, - {'name' => 'Rails', 'version' => '4.0', 'platform' => 'Foobar'}, + expect(client.options[:wrapping_libraries]).to be == [ + { 'name' => 'Mongoid', 'version' => '7.1.2' }, + { 'name' => 'Rails', 'version' => '4.0', 'platform' => 'Foobar' }, ] end end @@ -1939,7 +1797,7 @@ end it 'works' do - client.options[:wrapping_libraries].should == [] + expect(client.options[:wrapping_libraries]).to be == [] end end @@ -1949,7 +1807,7 @@ end it 'works' do - client.options[:wrapping_libraries].should be nil + expect(client.options[:wrapping_libraries]).to be_nil end end end @@ -1957,37 +1815,34 @@ context 'valid input' do context 'hash given instead of an array' do let(:wrapping_libraries) do - {name: 'Mongoid', version: '7.1.2'}.freeze + { name: 'Mongoid', version: '7.1.2' }.freeze end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /:wrapping_libraries must be an array of hashes/) + expect { client } + .to raise_error(ArgumentError, /:wrapping_libraries must be an array of hashes/) end end context 'invalid keys' do let(:wrapping_libraries) do - [name: 'Mongoid', invalid: '7.1.2'].freeze + [ { name: 'Mongoid', invalid: '7.1.2' } ].freeze end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /:wrapping_libraries element has invalid keys/) + expect { client } + .to raise_error(ArgumentError, /:wrapping_libraries element has invalid keys/) end end context 'value includes |' do let(:wrapping_libraries) do - [name: 'Mongoid|on|Rails', version: '7.1.2'].freeze + [ { name: 'Mongoid|on|Rails', version: '7.1.2' } ].freeze end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, /:wrapping_libraries element value cannot include '|'/) + expect { client } + .to raise_error(ArgumentError, /:wrapping_libraries element value cannot include '|'/) end end end @@ -1995,12 +1850,10 @@ context ':auth_mech_properties option' do context 'is nil' do - let(:options) do - {auth_mech_properties: nil} - end + let(:options) { { auth_mech_properties: nil } } it 'creates the client without the option' do - client.options.should_not have_key(:auth_mech_properties) + expect(client.options).not_to have_key(:auth_mech_properties) end end end @@ -2009,15 +1862,17 @@ context 'is a hash with symbol keys' do context 'using known keys' do let(:options) do - {server_api: { - version: '1', - strict: true, - deprecation_errors: false, - }} + { + server_api: { + version: '1', + strict: true, + deprecation_errors: false, + } + } end it 'is accepted' do - client.options[:server_api].should == { + expect(client.options[:server_api]).to be == { 'version' => '1', 'strict' => true, 'deprecation_errors' => false, @@ -2027,41 +1882,34 @@ context 'using an unknown version' do let(:options) do - {server_api: { - version: '42', - }} + { server_api: { version: '42' } } end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, 'Unknown server API version: 42') + expect { client } + .to raise_error(ArgumentError, 'Unknown server API version: 42') end end context 'using an unknown option' do let(:options) do - {server_api: { - vversion: '1', - }} + { server_api: { vversion: '1' } } end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, 'Unknown keys under :server_api: "vversion"') + expect { client } + .to raise_error(ArgumentError, 'Unknown keys under :server_api: "vversion"') end end context 'using a value which is not a hash' do let(:options) do - {server_api: 42} + { server_api: 42 } end it 'is rejected' do - lambda do - client - end.should raise_error(ArgumentError, ':server_api value must be a hash: 42') + expect { client } + .to raise_error(ArgumentError, ':server_api value must be a hash: 42') end end end @@ -2070,16 +1918,18 @@ max_server_version '3.4' let(:options) do - {server_api: {version: 1}} + { server_api: { version: 1 } } end let(:client) do - new_local_client(SpecConfig.instance.addresses, - SpecConfig.instance.all_test_options.merge(options)) + new_local_client( + SpecConfig.instance.addresses, + SpecConfig.instance.all_test_options.merge(options) + ) end it 'constructs the client' do - client.should be_a(Mongo::Client) + expect(client).to be_a(described_class) end it 'does not discover servers' do @@ -2089,9 +1939,8 @@ end it 'fails operations' do - lambda do - client.command(ping: 1) - end.should raise_error(Mongo::Error::NoServerAvailable) + expect { client.command(ping: 1) } + .to raise_error(Mongo::Error::NoServerAvailable) end end end @@ -2101,9 +1950,9 @@ context 'when the block doesn\'t raise an error' do let(:block_client) do c = nil - Mongo::Client.new( + described_class.new( SpecConfig.instance.addresses, - SpecConfig.instance.test_options.merge(database: SpecConfig.instance.test_db), + SpecConfig.instance.test_options.merge(database: SpecConfig.instance.test_db) ) do |client| c = client end @@ -2111,7 +1960,7 @@ end it 'is closed after block' do - expect(block_client.cluster.connected?).to eq(false) + expect(block_client.cluster.connected?).to be false end context 'with auto encryption options' do @@ -2137,12 +1986,12 @@ let(:block_client) do c = nil - Mongo::Client.new( + described_class.new( SpecConfig.instance.addresses, SpecConfig.instance.test_options.merge( auto_encryption_options: auto_encryption_options, database: SpecConfig.instance.test_db - ), + ) ) do |client| c = client end @@ -2150,63 +1999,73 @@ end it 'closes all clients after block' do - expect(block_client.cluster.connected?).to eq(false) + expect(block_client.cluster.connected?).to be false [ block_client.encrypter.mongocryptd_client, block_client.encrypter.key_vault_client, block_client.encrypter.metadata_client ].each do |crypt_client| - expect(crypt_client.cluster.connected?).to eq(false) + expect(crypt_client.cluster.connected?).to be false end end end end context 'when the block raises an error' do - it 'it is closed after the block' do + it 'is closed after the block' do block_client_raise = nil expect do - Mongo::Client.new( + described_class.new( SpecConfig.instance.addresses, - SpecConfig.instance.test_options.merge(database: SpecConfig.instance.test_db), + SpecConfig.instance.test_options.merge(database: SpecConfig.instance.test_db) ) do |client| block_client_raise = client - raise "This is an error!" + raise 'This is an error!' end - end.to raise_error(StandardError, "This is an error!") - expect(block_client_raise.cluster.connected?).to eq(false) + end.to raise_error(StandardError, 'This is an error!') + expect(block_client_raise.cluster.connected?).to be false end end context 'when the hosts given include the protocol' do it 'raises an error on mongodb://' do expect do - Mongo::Client.new(['mongodb://127.0.0.1:27017/test']) - end.to raise_error(ArgumentError, "Host 'mongodb://127.0.0.1:27017/test' should not contain protocol. Did you mean to not use an array?") + described_class.new([ 'mongodb://127.0.0.1:27017/test' ]) + end.to raise_error(ArgumentError, + "Host 'mongodb://127.0.0.1:27017/test' should not contain protocol. " \ + 'Did you mean to not use an array?') end it 'raises an error on mongodb+srv://' do expect do - Mongo::Client.new(['mongodb+srv://127.0.0.1:27017/test']) - end.to raise_error(ArgumentError, "Host 'mongodb+srv://127.0.0.1:27017/test' should not contain protocol. Did you mean to not use an array?") + described_class.new([ 'mongodb+srv://127.0.0.1:27017/test' ]) + end.to raise_error(ArgumentError, + "Host 'mongodb+srv://127.0.0.1:27017/test' should not contain protocol. " \ + 'Did you mean to not use an array?') end it 'raises an error on multiple items' do expect do - Mongo::Client.new(['127.0.0.1:27017', 'mongodb+srv://127.0.0.1:27017/test']) - end.to raise_error(ArgumentError, "Host 'mongodb+srv://127.0.0.1:27017/test' should not contain protocol. Did you mean to not use an array?") + described_class.new([ '127.0.0.1:27017', 'mongodb+srv://127.0.0.1:27017/test' ]) + end.to raise_error(ArgumentError, + "Host 'mongodb+srv://127.0.0.1:27017/test' should not contain protocol. " \ + 'Did you mean to not use an array?') end it 'raises an error only at beginning of string' do expect do - Mongo::Client.new(['somethingmongodb://127.0.0.1:27017/test', 'mongodb+srv://127.0.0.1:27017/test']) - end.to raise_error(ArgumentError, "Host 'mongodb+srv://127.0.0.1:27017/test' should not contain protocol. Did you mean to not use an array?") + described_class + .new([ 'somethingmongodb://127.0.0.1:27017/test', 'mongodb+srv://127.0.0.1:27017/test' ]) + end.to raise_error(ArgumentError, + "Host 'mongodb+srv://127.0.0.1:27017/test' should not contain protocol. " \ + 'Did you mean to not use an array?') end it 'raises an error with different case' do - expect do - Mongo::Client.new(['MongOdB://127.0.0.1:27017/test']) - end.to raise_error(ArgumentError, "Host 'MongOdB://127.0.0.1:27017/test' should not contain protocol. Did you mean to not use an array?") + expect { described_class.new([ 'MongOdB://127.0.0.1:27017/test' ]) } + .to raise_error(ArgumentError, + "Host 'MongOdB://127.0.0.1:27017/test' should not contain protocol. " \ + 'Did you mean to not use an array?') end end end @@ -2296,13 +2155,11 @@ end describe '#use' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :database => SpecConfig.instance.test_db) + new_local_client_nmio(SINGLE_CLIENT, database: SpecConfig.instance.test_db) end shared_examples_for 'a database switching object' do - it 'returns the new client' do expect(client.send(:database).name).to eq('ruby-driver') end @@ -2313,7 +2170,6 @@ end context 'when provided a string' do - let(:database) do client.use('testdb') end @@ -2322,7 +2178,6 @@ end context 'when provided a symbol' do - let(:database) do client.use(:testdb) end @@ -2331,45 +2186,29 @@ end context 'when providing nil' do - it 'raises an exception' do - expect { - client.use(nil) - }.to raise_error(Mongo::Error::InvalidDatabaseName) + expect { client.use(nil) } + .to raise_error(Mongo::Error::InvalidDatabaseName) end end end describe '#with' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :database => SpecConfig.instance.test_db) + new_local_client_nmio(SINGLE_CLIENT, database: SpecConfig.instance.test_db) end context 'when providing nil' do - it 'returns the cloned client' do expect(client.with(nil)).to eq(client) end end context 'when the app_name is changed' do - - let(:client) do - authorized_client - end - - let!(:original_options) do - client.options - end - - let(:new_options) do - { app_name: 'client_test' } - end - - let!(:new_client) do - authorized_client.with(new_options) - end + let(:client) { authorized_client } + let(:original_options) { client.options } + let(:new_options) { { app_name: 'client_test' } } + let(:new_client) { authorized_client.with(new_options) } it 'returns a new client' do expect(new_client).not_to equal(client) @@ -2395,27 +2234,25 @@ new_local_client(SpecConfig.instance.addresses, options) end - before do - client.options[:direct_connection].should be nil - end - let(:new_client) do client.with(new_options) end - context 'direct_connection set to false' do + before do + expect(client.options[:direct_connection]).to be_nil + end + context 'direct_connection set to false' do let(:new_options) do { direct_connection: false } end it 'is accepted' do - new_client.options[:direct_connection].should be false + expect(new_client.options[:direct_connection]).to be false end end context 'direct_connection set to true' do - let(:new_options) do { direct_connection: true } end @@ -2423,10 +2260,9 @@ context 'in single topology' do require_topology :single - it 'is accepted' do - new_client.options[:direct_connection].should be true - new_client.cluster.topology.should be_a(Mongo::Cluster::Topology::Single) + expect(new_client.options[:direct_connection]).to be true + expect(new_client.cluster.topology).to be_a(Mongo::Cluster::Topology::Single) end end @@ -2434,21 +2270,19 @@ require_topology :replica_set, :sharded it 'is rejected' do - lambda do - new_client - end.should raise_error(ArgumentError, /direct_connection=true cannot be used with topologies other than Single/) + expect { new_client } + .to raise_error(ArgumentError, /direct_connection=true cannot be used with topologies other than Single/) end context 'when a new cluster is created' do - let(:new_options) do { direct_connection: true, app_name: 'new-client' } end it 'is rejected' do - lambda do - new_client - end.should raise_error(ArgumentError, /direct_connection=true cannot be used with topologies other than Single/) + expect { new_client } + .to raise_error(ArgumentError, + /direct_connection=true cannot be used with topologies other than Single/) end end end @@ -2456,28 +2290,37 @@ end context 'when the write concern is not changed' do - let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :secondary }, :write => { :w => 1 }, :database => SpecConfig.instance.test_db + SINGLE_CLIENT, + read: { mode: :secondary }, + write: { w: 1 }, + database: SpecConfig.instance.test_db ) end - let!(:new_client) do - client.with(:read => { :mode => :primary }) - end + let(:new_client) { client.with(read: { mode: :primary }) } let(:new_options) do - Mongo::Options::Redacted.new(:read => { :mode => :primary }, - :write => { :w => 1 }, monitoring_io: false, - :database => SpecConfig.instance.test_db, retry_writes: true, retry_reads: true) + Mongo::Options::Redacted.new( + read: { mode: :primary }, + write: { w: 1 }, + monitoring_io: false, + database: SpecConfig.instance.test_db, + retry_writes: true, + retry_reads: true + ) end let(:original_options) do - Mongo::Options::Redacted.new(:read => { :mode => :secondary }, - :write => { :w => 1 }, monitoring_io: false, - :database => SpecConfig.instance.test_db, retry_writes: true, retry_reads: true) + Mongo::Options::Redacted.new( + read: { mode: :secondary }, + write: { w: 1 }, + monitoring_io: false, + database: SpecConfig.instance.test_db, + retry_writes: true, + retry_reads: true + ) end it 'returns a new client' do @@ -2498,21 +2341,19 @@ end context 'when the write concern is changed' do - let(:client) do - new_local_client(['127.0.0.1:27017'], - {monitoring_io: false}.merge(client_options)) + new_local_client( + SINGLE_CLIENT, + { monitoring_io: false }.merge(client_options) + ) end let(:client_options) do - { :write => { :w => 1 } } + { write: { w: 1 } } end context 'when the write concern has not been accessed' do - - let!(:new_client) do - client.with(:write => { :w => 0 }) - end + let(:new_client) { client.with(write: { w: 0 }) } let(:get_last_error) do new_client.write_concern.get_last_error @@ -2524,10 +2365,9 @@ end context 'when the write concern has been accessed' do - - let!(:new_client) do + let(:new_client) do client.write_concern - client.with(:write => { :w => 0 }) + client.with(write: { w: 0 }) end let(:get_last_error) do @@ -2540,9 +2380,8 @@ end context 'when write concern is given as :write' do - let(:client_options) do - { :write => { :w => 1 } } + { write: { w: 1 } } end it 'sets :write option' do @@ -2550,7 +2389,7 @@ end it 'does not set :write_concern option' do - expect(client.options[:write_concern]).to be nil + expect(client.options[:write_concern]).to be_nil end it 'returns correct write concern' do @@ -2560,9 +2399,8 @@ end context 'when write concern is given as :write_concern' do - let(:client_options) do - { :write_concern => { :w => 1 } } + { write_concern: { w: 1 } } end it 'sets :write_concern option' do @@ -2570,20 +2408,19 @@ end it 'does not set :write option' do - expect(client.options[:write]).to be nil + expect(client.options[:write]).to be_nil end it 'returns correct write concern' do - expect(client.write_concern).to be_a(Mongo::WriteConcern::Acknowledged) - expect(client.write_concern.options).to eq(w: 1) + expect(client.write_concern).to be_a(Mongo::WriteConcern::Acknowledged) + expect(client.write_concern.options).to eq(w: 1) end end context 'when write concern is given as both :write and :write_concern' do context 'with identical values' do - let(:client_options) do - { write: {w: 1}, write_concern: { w: 1 } } + { write: { w: 1 }, write_concern: { w: 1 } } end it 'sets :write_concern option' do @@ -2601,9 +2438,8 @@ end context 'with different values' do - let(:client_options) do - { write: {w: 1}, write_concern: { w: 2 } } + { write: { w: 1 }, write_concern: { w: 2 } } end it 'raises an exception' do @@ -2615,43 +2451,35 @@ end context 'when #with uses a different write concern option name' do - context 'from :write_concern to :write' do - let(:client_options) do - { :write_concern => { :w => 1 } } + { write_concern: { w: 1 } } end - let!(:new_client) do - client.with(:write => { :w => 2 }) - end + let(:new_client) { client.with(write: { w: 2 }) } it 'uses the new option' do expect(new_client.options[:write]).to eq(Mongo::Options::Redacted.new(w: 2)) - expect(new_client.options[:write_concern]).to be nil + expect(new_client.options[:write_concern]).to be_nil end end context 'from :write to :write_concern' do - let(:client_options) do - { :write => { :w => 1 } } + { write: { w: 1 } } end - let!(:new_client) do - client.with(:write_concern => { :w => 2 }) - end + let(:new_client) { client.with(write_concern: { w: 2 }) } it 'uses the new option' do expect(new_client.options[:write_concern]).to eq(Mongo::Options::Redacted.new(w: 2)) - expect(new_client.options[:write]).to be nil + expect(new_client.options[:write]).to be_nil end end end end context 'when an invalid option is provided' do - let(:new_client) do client.with(invalid: :option, ssl: false) end @@ -2672,24 +2500,18 @@ context 'when client is created with ipv6 address' do let(:client) do - new_local_client_nmio(['[::1]:27017'], :database => SpecConfig.instance.test_db) + new_local_client_nmio([ '[::1]:27017' ], database: SpecConfig.instance.test_db) end context 'when providing nil' do - it 'returns the cloned client' do expect(client.with(nil)).to eq(client) end end context 'when changing options' do - let(:new_options) do - { app_name: 'client_test' } - end - - let!(:new_client) do - client.with(new_options) - end + let(:new_options) { { app_name: 'client_test' } } + let(:new_client) { client.with(new_options) } it 'returns a new client' do expect(new_client).not_to equal(client) @@ -2699,11 +2521,14 @@ context 'when new client has a new cluster' do let(:client) do - new_local_client(['127.0.0.1:27017'], + new_local_client( + SINGLE_CLIENT, database: SpecConfig.instance.test_db, server_selection_timeout: 0.5, - socket_timeout: 0.1, connect_timeout: 0.1, populator_io: false) + socket_timeout: 0.1, connect_timeout: 0.1, populator_io: false + ) end + let(:new_client) do client.with(app_name: 'client_construction_spec').tap do |new_client| expect(new_client.cluster).not_to eql(client.cluster) @@ -2727,26 +2552,28 @@ # in #with, the consistent behavior is to never transfer sdam_proc to # the new client. context 'when sdam_proc is given on original client' do - let(:sdam_proc) do - Proc.new do |client| + proc do |client| client.subscribe(Mongo::Monitoring::SERVER_HEARTBEAT, subscriber) end end let(:client) do - new_local_client(SpecConfig.instance.addresses, + new_local_client( + SpecConfig.instance.addresses, SpecConfig.instance.test_options.merge( sdam_proc: sdam_proc, connect_timeout: 3.08, socket_timeout: 3.09, server_selection_timeout: 2.92, heartbeat_frequency: 100, - database: SpecConfig.instance.test_db)) + database: SpecConfig.instance.test_db + ) + ) end let(:new_client) do client.with(app_name: 'foo').tap do |new_client| - new_client.cluster.should_not == client.cluster + expect(new_client.cluster).not_to be == client.cluster end end @@ -2755,14 +2582,14 @@ events = subscriber.select_started_events(Mongo::Monitoring::Event::ServerHeartbeatStarted) if ClusterConfig.instance.topology == :load_balanced # No server monitoring in LB topology - events.length.should == 0 + expect(events.length).to be == 0 else - events.length.should > 0 + expect(events.length).to be > 0 end end it 'does not copy sdam_proc option to new client' do - expect(new_client.options[:sdam_proc]).to be nil + expect(new_client.options[:sdam_proc]).to be_nil end it 'does not notify subscribers set up by sdam_proc' do @@ -2784,34 +2611,34 @@ new_client.cluster.next_primary # Diagnostics + # rubocop:disable Style/IfUnlessModifier, Lint/Debugger unless subscriber.started_events.empty? p subscriber.started_events end + # rubocop:enable Style/IfUnlessModifier, Lint/Debugger expect(subscriber.started_events.length).to eq 0 - new_client.cluster.topology.class.should_not be Mongo::Cluster::Topology::Unknown + expect(new_client.cluster.topology.class).not_to be Mongo::Cluster::Topology::Unknown end end context 'when :server_api is changed' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017']) + new_local_client_nmio(SINGLE_CLIENT) end let(:new_client) do - client.with(server_api: {version: '1'}) + client.with(server_api: { version: '1' }) end it 'changes :server_api' do - new_client.options[:server_api].should == {'version' => '1'} + expect(new_client.options[:server_api]).to be == { 'version' => '1' } end end context 'when :server_api is cleared' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], server_api: {version: '1'}) + new_local_client_nmio(SINGLE_CLIENT, server_api: { version: '1' }) end let(:new_client) do @@ -2819,19 +2646,17 @@ end it 'clears :server_api' do - new_client.options[:server_api].should be nil + expect(new_client.options[:server_api]).to be_nil end end - end describe '#dup' do - let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + SINGLE_CLIENT, + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end @@ -2844,3 +2669,7 @@ it_behaves_like 'duplicated client with reused monitoring' end end +# rubocop:enable RSpec/ExpectInHook, RSpec/MessageSpies, RSpec/ExampleLength +# rubocop:enable RSpec/ContextWording, RSpec/RepeatedExampleGroupDescription +# rubocop:enable RSpec/ExampleWording, Style/BlockComments, RSpec/AnyInstance +# rubocop:enable RSpec/VerifiedDoubles diff --git a/spec/mongo/cluster_spec.rb b/spec/mongo/cluster_spec.rb index 6db47aff5a..b93f529a0e 100644 --- a/spec/mongo/cluster_spec.rb +++ b/spec/mongo/cluster_spec.rb @@ -1,39 +1,54 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' +# let these existing styles stand, rather than going in for a deep refactoring +# of these specs. +# +# possible future work: re-enable these one at a time and do the hard work of +# making them right. +# +# rubocop:disable RSpec/ContextWording, RSpec/VerifiedDoubles, RSpec/MessageSpies +# rubocop:disable RSpec/ExpectInHook, RSpec/ExampleLength describe Mongo::Cluster do - let(:monitoring) do Mongo::Monitoring.new(monitoring: false) end let(:cluster_with_semaphore) do register_cluster( - described_class.new(SpecConfig.instance.addresses, monitoring, + described_class.new( + SpecConfig.instance.addresses, + monitoring, SpecConfig.instance.test_options.merge( - server_selection_semaphore: Mongo::Semaphore.new))) + server_selection_semaphore: Mongo::Semaphore.new + ) + ) + ) end let(:cluster_without_io) do register_cluster( - described_class.new(SpecConfig.instance.addresses, monitoring, - SpecConfig.instance.test_options.merge(monitoring_io: false))) + described_class.new( + SpecConfig.instance.addresses, + monitoring, + SpecConfig.instance.test_options.merge(monitoring_io: false) + ) + ) end let(:cluster) { cluster_without_io } describe 'initialize' do - context 'when there are duplicate addresses' do - let(:addresses) do SpecConfig.instance.addresses + SpecConfig.instance.addresses end + let(:cluster_with_dup_addresses) do register_cluster( - described_class.new(addresses, monitoring, SpecConfig.instance.test_options)) + described_class.new(addresses, monitoring, SpecConfig.instance.test_options) + ) end it 'does not raise an exception' do @@ -52,36 +67,34 @@ SpecConfig.instance.addresses, monitoring, SpecConfig.instance.test_options - ) ) + ) - expect(monitoring).to have_received(:succeeded).with( - Mongo::Monitoring::TOPOLOGY_OPENING, any_args - ) - expect(monitoring).to have_received(:succeeded).with( - Mongo::Monitoring::TOPOLOGY_CHANGED, any_args - ).twice - expect(monitoring).to have_received(:succeeded).with( - Mongo::Monitoring::SERVER_OPENING, any_args - ) - expect(monitoring).to have_received(:succeeded).with( - Mongo::Monitoring::SERVER_DESCRIPTION_CHANGED, any_args - ) + expect(monitoring).to have_received(:succeeded).with( + Mongo::Monitoring::TOPOLOGY_OPENING, any_args + ) + expect(monitoring).to have_received(:succeeded).with( + Mongo::Monitoring::TOPOLOGY_CHANGED, any_args + ).twice + expect(monitoring).to have_received(:succeeded).with( + Mongo::Monitoring::SERVER_OPENING, any_args + ) + expect(monitoring).to have_received(:succeeded).with( + Mongo::Monitoring::SERVER_DESCRIPTION_CHANGED, any_args + ) end end end describe '#==' do - context 'when the other is a cluster' do - context 'when the addresses are the same' do - context 'when the options are the same' do - let(:other) do - described_class.new(SpecConfig.instance.addresses, monitoring, - SpecConfig.instance.test_options.merge(monitoring_io: false)) + described_class.new( + SpecConfig.instance.addresses, monitoring, + SpecConfig.instance.test_options.merge(monitoring_io: false) + ) end it 'returns true' do @@ -90,59 +103,61 @@ end context 'when the options are not the same' do - let(:other) do - described_class.new([ '127.0.0.1:27017' ], monitoring, - SpecConfig.instance.test_options.merge(replica_set: 'test', monitoring_io: false)) + described_class.new( + [ '127.0.0.1:27017' ], + monitoring, + SpecConfig.instance.test_options.merge(replica_set: 'test', monitoring_io: false) + ) end it 'returns false' do - expect(cluster_without_io).to_not eq(other) + expect(cluster_without_io).not_to eq(other) end end end context 'when the addresses are not the same' do - let(:other) do - described_class.new([ '127.0.0.1:27999' ], monitoring, - SpecConfig.instance.test_options.merge(monitoring_io: false)) + described_class.new( + [ '127.0.0.1:27999' ], + monitoring, + SpecConfig.instance.test_options.merge(monitoring_io: false) + ) end it 'returns false' do - expect(cluster_without_io).to_not eq(other) + expect(cluster_without_io).not_to eq(other) end end end context 'when the other is not a cluster' do - it 'returns false' do - expect(cluster_without_io).to_not eq('test') + expect(cluster_without_io).not_to eq('test') end end end describe '#has_readable_server?' do - let(:selector) do Mongo::ServerSelector.primary end it 'delegates to the topology' do - expect(cluster_without_io.has_readable_server?).to eq(cluster_without_io.topology.has_readable_server?(cluster_without_io)) + expect(cluster_without_io.has_readable_server?) + .to eq(cluster_without_io.topology.has_readable_server?(cluster_without_io)) end end describe '#has_writable_server?' do - it 'delegates to the topology' do - expect(cluster_without_io.has_writable_server?).to eq(cluster_without_io.topology.has_writable_server?(cluster_without_io)) + expect(cluster_without_io.has_writable_server?) + .to eq(cluster_without_io.topology.has_writable_server?(cluster_without_io)) end end describe '#inspect' do - let(:preference) do Mongo::ServerSelector.primary end @@ -154,18 +169,16 @@ end describe '#replica_set_name' do - let(:preference) do Mongo::ServerSelector.primary end context 'when the option is provided' do - let(:cluster) do described_class.new( [ '127.0.0.1:27017' ], monitoring, - {monitoring_io: false, connect: :replica_set, replica_set: 'testing'}, + { monitoring_io: false, connect: :replica_set, replica_set: 'testing' } ) end @@ -175,12 +188,11 @@ end context 'when the option is not provided' do - let(:cluster) do described_class.new( [ '127.0.0.1:27017' ], monitoring, - {monitoring_io: false, connect: :direct}, + { monitoring_io: false, connect: :direct } ) end @@ -191,7 +203,6 @@ end describe '#scan!' do - let(:preference) do Mongo::ServerSelector.primary end @@ -224,9 +235,7 @@ context 'when topology is single' do before do - unless ClusterConfig.instance.single_server? - skip 'Topology is not a single server' - end + skip 'Topology is not a single server' unless ClusterConfig.instance.single_server? end context 'when the server is a mongos' do @@ -247,7 +256,6 @@ end context 'when the cluster has no servers' do - let(:servers) do [] end @@ -258,7 +266,6 @@ end context 'when topology is Single' do - let(:topology) do Mongo::Cluster::Topology::Single.new({}, monitoring, cluster_without_io) end @@ -269,9 +276,8 @@ end context 'when topology is ReplicaSetNoPrimary' do - let(:topology) do - Mongo::Cluster::Topology::ReplicaSetNoPrimary.new({replica_set_name: 'foo'}, monitoring, cluster_without_io) + Mongo::Cluster::Topology::ReplicaSetNoPrimary.new({ replica_set_name: 'foo' }, monitoring, cluster_without_io) end it 'returns an empty array' do @@ -280,7 +286,6 @@ end context 'when topology is Sharded' do - let(:topology) do Mongo::Cluster::Topology::Sharded.new({}, monitoring, cluster_without_io) end @@ -291,7 +296,6 @@ end context 'when topology is Unknown' do - let(:topology) do Mongo::Cluster::Topology::Unknown.new({}, monitoring, cluster_without_io) end @@ -304,7 +308,6 @@ end describe '#add' do - context 'topology is Sharded' do require_topology :sharded @@ -317,14 +320,14 @@ end it 'creates server with nil last_scan' do - server = cluster.servers_list.detect do |server| - server.address.seed == 'a' + server = cluster.servers_list.detect do |srv| + srv.address.seed == 'a' end - expect(server).not_to be nil + expect(server).not_to be_nil - expect(server.last_scan).to be nil - expect(server.last_scan_monotime).to be nil + expect(server.last_scan).to be_nil + expect(server.last_scan_monotime).to be_nil end end end @@ -342,22 +345,18 @@ describe 'closing' do before do - known_servers.each do |server| - expect(server).to receive(:close).and_call_original - end + expect(known_servers).to all receive(:close).and_call_original expect(periodic_executor).to receive(:stop!).and_call_original end it 'disconnects each server and the cursor reaper and returns nil' do - expect(cluster.close).to be nil + expect(cluster.close).to be_nil end end describe 'repeated closing' do before do - known_servers.each do |server| - expect(server).to receive(:close).and_call_original - end + expect(known_servers).to all receive(:close).and_call_original expect(periodic_executor).to receive(:stop!).and_call_original end @@ -366,17 +365,16 @@ it 'publishes server closed event once' do monitoring.subscribe(Mongo::Monitoring::SERVER_CLOSED, subscriber) - expect(cluster.close).to be nil - expect(subscriber.first_event('server_closed_event')).not_to be nil + expect(cluster.close).to be_nil + expect(subscriber.first_event('server_closed_event')).not_to be_nil subscriber.succeeded_events.clear - expect(cluster.close).to be nil - expect(subscriber.first_event('server_closed_event')).to be nil + expect(cluster.close).to be_nil + expect(subscriber.first_event('server_closed_event')).to be_nil end end end describe '#reconnect!' do - let(:cluster) { cluster_with_semaphore } let(:periodic_executor) do @@ -385,9 +383,7 @@ before do cluster.next_primary - cluster.servers.each do |server| - expect(server).to receive(:reconnect!).and_call_original - end + expect(cluster.servers).to all receive(:reconnect!).and_call_original expect(periodic_executor).to receive(:restart!).and_call_original end @@ -397,29 +393,32 @@ end describe '#remove' do - - let(:address_a) do - Mongo::Address.new('127.0.0.1:25555') - end - - let(:address_b) do - Mongo::Address.new('127.0.0.1:25556') - end - - let(:monitoring) do - Mongo::Monitoring.new(monitoring: false) - end + let(:address_a) { Mongo::Address.new('127.0.0.1:25555') } + let(:address_b) { Mongo::Address.new('127.0.0.1:25556') } + let(:monitoring) { Mongo::Monitoring.new(monitoring: false) } let(:server_a) do register_server( - Mongo::Server.new(address_a, cluster, monitoring, Mongo::Event::Listeners.new, - monitor: false)) + Mongo::Server.new( + address_a, + cluster, + monitoring, + Mongo::Event::Listeners.new, + monitor: false + ) + ) end let(:server_b) do register_server( - Mongo::Server.new(address_b, cluster, monitoring, Mongo::Event::Listeners.new, - monitor: false)) + Mongo::Server.new( + address_b, + cluster, + monitoring, + Mongo::Event::Listeners.new, + monitor: false + ) + ) end let(:servers) do @@ -436,16 +435,15 @@ end it 'removes the host from the list of servers' do - expect(cluster.instance_variable_get(:@servers)).to eq([server_b]) + expect(cluster.instance_variable_get(:@servers)).to eq([ server_b ]) end it 'removes the host from the list of addresses' do - expect(cluster.addresses).to eq([address_b]) + expect(cluster.addresses).to eq([ address_b ]) end end describe '#next_primary' do - let(:cluster) do # We use next_primary to wait for server selection, and this is # also the method we are testing. @@ -453,12 +451,10 @@ end let(:primary_candidates) do - if cluster.single? || cluster.load_balanced? - cluster.servers - elsif cluster.sharded? + if cluster.single? || cluster.load_balanced? || cluster.sharded? cluster.servers else - cluster.servers.select { |s| s.primary? } + cluster.servers.select(&:primary?) end end @@ -468,13 +464,11 @@ end describe '#app_metadata' do - it 'returns an AppMetadata object' do expect(cluster_without_io.app_metadata).to be_a(Mongo::Server::AppMetadata) end context 'when the client has an app_name set' do - let(:cluster) do authorized_client.with(app_name: 'cluster_test', monitoring_io: false).cluster end @@ -485,7 +479,6 @@ end context 'when the client does not have an app_name set' do - let(:cluster) do authorized_client.cluster end @@ -497,7 +490,6 @@ end describe '#cluster_time' do - let(:operation) do client.command(ping: 1) end @@ -514,10 +506,14 @@ end describe '#update_cluster_time' do - let(:cluster) do - described_class.new(SpecConfig.instance.addresses, monitoring, - SpecConfig.instance.test_options.merge(heartbeat_frequency: 1000, monitoring_io: false)) + described_class.new( + SpecConfig.instance.addresses, monitoring, + SpecConfig.instance.test_options.merge( + heartbeat_frequency: 1000, + monitoring_io: false + ) + ) end let(:result) do @@ -525,14 +521,12 @@ end context 'when the cluster_time variable is nil' do - before do cluster.instance_variable_set(:@cluster_time, nil) cluster.update_cluster_time(result) end context 'when the cluster time received is nil' do - let(:cluster_time_doc) do nil end @@ -543,7 +537,6 @@ end context 'when the cluster time received is not nil' do - let(:cluster_time_doc) do BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)) end @@ -555,29 +548,32 @@ end context 'when the cluster_time variable has a value' do - before do - cluster.instance_variable_set(:@cluster_time, Mongo::ClusterTime.new( - Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1))) + cluster.instance_variable_set( + :@cluster_time, + Mongo::ClusterTime.new( + Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1) + ) + ) cluster.update_cluster_time(result) end context 'when the cluster time received is nil' do - let(:cluster_time_doc) do nil end it 'does not update the cluster_time variable' do - expect(cluster.cluster_time).to eq(BSON::Document.new( - Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1))) + expect(cluster.cluster_time).to eq( + BSON::Document.new( + Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1) + ) + ) end end context 'when the cluster time received is not nil' do - context 'when the cluster time received is greater than the cluster_time variable' do - let(:cluster_time_doc) do BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 2)) end @@ -588,26 +584,30 @@ end context 'when the cluster time received is less than the cluster_time variable' do - let(:cluster_time_doc) do BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(0, 1)) end it 'does not set the cluster_time variable to the cluster time' do - expect(cluster.cluster_time).to eq(BSON::Document.new( - Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1))) + expect(cluster.cluster_time).to eq( + BSON::Document.new( + Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1) + ) + ) end end context 'when the cluster time received is equal to the cluster_time variable' do - let(:cluster_time_doc) do BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)) end it 'does not change the cluster_time variable' do - expect(cluster.cluster_time).to eq(BSON::Document.new( - Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1))) + expect(cluster.cluster_time).to eq( + BSON::Document.new( + Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1) + ) + ) end end end @@ -617,17 +617,15 @@ describe '#validate_session_support!' do shared_examples 'supports sessions' do it 'supports sessions' do - lambda do - cluster.validate_session_support! - end.should_not raise_error + expect { cluster.validate_session_support! } + .not_to raise_error end end shared_examples 'does not support sessions' do it 'does not support sessions' do - lambda do - cluster.validate_session_support! - end.should raise_error(Mongo::Error::SessionsNotSupported) + expect { cluster.validate_session_support! } + .to raise_error(Mongo::Error::SessionsNotSupported) end end @@ -648,9 +646,13 @@ require_topology :replica_set let(:client) do - new_local_client([SpecConfig.instance.addresses.first], + new_local_client( + [ SpecConfig.instance.addresses.first ], SpecConfig.instance.test_options.merge( - connect: :direct, replica_set: ClusterConfig.instance.replica_set_name)) + connect: :direct, + replica_set: ClusterConfig.instance.replica_set_name + ) + ) end it_behaves_like 'does not support sessions' @@ -693,9 +695,13 @@ require_topology :replica_set let(:client) do - new_local_client([SpecConfig.instance.addresses.first], + new_local_client( + [ SpecConfig.instance.addresses.first ], SpecConfig.instance.test_options.merge( - connect: :direct, replica_set: ClusterConfig.instance.replica_set_name)) + connect: :direct, + replica_set: ClusterConfig.instance.replica_set_name + ) + ) end it_behaves_like 'supports sessions' @@ -719,15 +725,12 @@ end end - [ - [:max_read_retries, 1], - [:read_retry_interval, 5], - ].each do |opt, default| + { max_read_retries: 1, read_retry_interval: 5 }.each do |opt, default| describe "##{opt}" do let(:client_options) { {} } let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], client_options) + new_local_client_nmio([ '127.0.0.1:27017' ], client_options) end let(:cluster) do @@ -735,13 +738,13 @@ end it "defaults to #{default}" do - expect(default).not_to be nil - expect(client.options[opt]).to be nil + expect(default).not_to be_nil + expect(client.options[opt]).to be_nil expect(cluster.send(opt)).to eq(default) end context 'specified on client' do - let(:client_options) { {opt => 2} } + let(:client_options) { { opt => 2 } } it 'inherits from client' do expect(client.options[opt]).to eq(2) @@ -759,10 +762,7 @@ require_topology :single, :replica_set, :sharded it 'includes unknown servers' do - cluster.servers_list.each do |server| - expect(server).to be_unknown - end - + expect(cluster.servers_list).to all be_unknown expect(cluster.summary).to match(/Server address=#{default_address}/) end end @@ -785,3 +785,5 @@ end end end +# rubocop:enable RSpec/ContextWording, RSpec/VerifiedDoubles, RSpec/MessageSpies +# rubocop:enable RSpec/ExpectInHook, RSpec/ExampleLength diff --git a/spec/mongo/server/app_metadata_spec.rb b/spec/mongo/server/app_metadata_spec.rb index c8819c67ce..47da5fa8bb 100644 --- a/spec/mongo/server/app_metadata_spec.rb +++ b/spec/mongo/server/app_metadata_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all require 'spec_helper' @@ -15,9 +14,7 @@ end describe '#initialize' do - context 'when the cluster has an app name option set' do - let(:client) do authorized_client.with(app_name: :app_metadata_test) end @@ -31,9 +28,8 @@ end context 'when the app name exceeds the max length of 128' do - let(:client) do - authorized_client.with(app_name: "\u3042"*43) + authorized_client.with(app_name: "\u3042" * 43) end let(:cluster) do @@ -41,17 +37,15 @@ end it 'raises an error' do - expect { - app_metadata.send(:validate!) - }.to raise_exception(Mongo::Error::InvalidApplicationName) + expect { app_metadata.validated_document } + .to raise_exception(Mongo::Error::InvalidApplicationName) end end end context 'when the cluster does not have an app name option set' do - it 'does not set the app name' do - expect(app_metadata.client_document[:application]).to be(nil) + expect(app_metadata.client_document[:application]).to be_nil end end @@ -69,7 +63,7 @@ context 'when the os.name length is too long' do before do - allow(app_metadata).to receive(:name).and_return('x'*500) + allow(app_metadata).to receive(:name).and_return('x' * 500) end it_behaves_like 'a truncated document' @@ -77,7 +71,7 @@ context 'when the os.architecture length is too long' do before do - allow(app_metadata).to receive(:architecture).and_return('x'*500) + allow(app_metadata).to receive(:architecture).and_return('x' * 500) end it_behaves_like 'a truncated document' @@ -85,7 +79,7 @@ context 'when the platform length is too long' do before do - allow(app_metadata).to receive(:platform).and_return('x'*500) + allow(app_metadata).to receive(:platform).and_return('x' * 500) end it_behaves_like 'a truncated document' @@ -93,7 +87,7 @@ end context 'when run outside of a FaaS environment' do - it 'should exclude the :env key from the client document' do + it 'excludes the :env key from the client document' do expect(app_metadata.client_document.key?(:env)).to be false end end @@ -103,7 +97,7 @@ # invalid, because it is missing the other required fields local_env('AWS_EXECUTION_ENV' => 'AWS_Lambda_ruby2.7') - it 'should exclude the :env key from the client document' do + it 'excludes the :env key from the client document' do expect(app_metadata.client_document.key?(:env)).to be false end end @@ -112,9 +106,9 @@ # valid, because Azure requires only the one field local_env('FUNCTIONS_WORKER_RUNTIME' => 'ruby') - it 'should include the :env key in the client document' do + it 'includes the :env key in the client document' do expect(app_metadata.client_document.key?(:env)).to be true - expect(app_metadata.client_document[:env][:name]).to be == "azure.func" + expect(app_metadata.client_document[:env][:name]).to be == 'azure.func' end end end @@ -140,17 +134,15 @@ describe '#validated_document' do it 'raises with too long app name' do - app_name = 'app'*500 - expect { - described_class.new(app_name: app_name).validated_document - }.to raise_error(Mongo::Error::InvalidApplicationName) + app_name = 'app' * 500 + expect { described_class.new(app_name: app_name).validated_document } + .to raise_error(Mongo::Error::InvalidApplicationName) end it 'does not raise with correct app name' do app_name = 'app' - expect { - described_class.new(app_name: app_name).validated_document - }.not_to raise_error + expect { described_class.new(app_name: app_name).validated_document } + .not_to raise_error end end end From 32543f3767f74f15d40e8372f0ec1415b207ac78 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 2 Jun 2023 16:23:16 +0200 Subject: [PATCH 132/198] RUBY-3246 Test against 7.0 (#2725) * Add 7.0 to text setup * Try to fix query cache spec * Try to fix snapshot query example * Disable latest for now * Get latest back * Do not test latest on ubuntu2204 * Fix failed specs --- .evergreen/config.yml | 38 ++++++++++--------- .evergreen/config/axes.yml.erb | 4 ++ .evergreen/config/standard.yml.erb | 35 +++++++++-------- .mod/drivers-evergreen-tools | 2 +- spec/integration/client_update_spec.rb | 4 +- spec/integration/query_cache_spec.rb | 2 +- .../snapshot_query_examples_spec.rb | 12 +++--- spec/shared | 2 +- spec/support/common_shortcuts.rb | 6 ++- 9 files changed, 57 insertions(+), 48 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index a7562f9210..e1d33c2773 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -668,6 +668,10 @@ axes: variables: MONGODB_VERSION: "latest" CRYPT_SHARED_VERSION: "latest" + - id: "7.0" + display_name: "7.0" + variables: + MONGODB_VERSION: "7.0" - id: "6.0" display_name: "6.0" variables: @@ -1059,21 +1063,21 @@ buildvariants: matrix_spec: auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] ruby: "ruby-3.1" - mongodb-version: "6.0" + mongodb-version: ["latest", "7.0", "6.0"] topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 display_name: ${auth-and-ssl} ${ruby} ${topology} tasks: - name: "test-mlaunch" - # 'latest' is broken until the QE2 changes are merged (RUBY-3211, RUBY-3226) - matrix_name: "mongo-recent" matrix_spec: ruby: ["ruby-3.1", "jruby-9.3"] - #mongodb-version: ['latest', '6.0'] - mongodb-version: ['6.0'] + mongodb-version: ["latest", "7.0", "6.0"] topology: ["standalone", "replica-set", "sharded-cluster"] - os: ['rhel8', 'ubuntu2204'] + os: ['rhel8'] + # There is no latest for ubuntu2204, so we can't test it here. + # os: ['rhel8', 'ubuntu2204'] display_name: "${mongodb-version} ${os} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" @@ -1358,19 +1362,17 @@ buildvariants: tasks: - name: "test-kerberos" - # 'latest' is broken until the QE2 changes are merged (RUBY-3211, RUBY-3226) - # - # - matrix_name: "fle-latest" - # matrix_spec: - # auth-and-ssl: "noauth-and-nossl" - # ruby: "ruby-3.1" - # topology: [replica-set, sharded-cluster] - # mongodb-version: [ 'latest' ] - # os: rhel8 - # fle: helper - # display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" - # tasks: - # - name: "test-fle" + - matrix_name: "fle-latest" + matrix_spec: + auth-and-ssl: "noauth-and-nossl" + ruby: "ruby-3.1" + topology: [replica-set, sharded-cluster] + mongodb-version: [ 'latest', '7.0' ] + os: rhel8 + fle: helper + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" + tasks: + - name: "test-fle" - matrix_name: "fle" matrix_spec: diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index 431da45a63..e6f101e8bd 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -18,6 +18,10 @@ axes: variables: MONGODB_VERSION: "latest" CRYPT_SHARED_VERSION: "latest" + - id: "7.0" + display_name: "7.0" + variables: + MONGODB_VERSION: "7.0" - id: "6.0" display_name: "6.0" variables: diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index fe04de6837..85c093c59a 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -41,6 +41,7 @@ latest_stable_mdb = "6.0".inspect # so it gets quoted as a string # A few of the most recent MongoDB versions + actual_and_upcoming_mdb = %w( latest 7.0 6.0 ) recent_mdb = %w( 6.0 5.3 ) latest_5x_mdb = "5.3".inspect # so it gets quoted as a string %> @@ -50,21 +51,21 @@ buildvariants: matrix_spec: auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] ruby: <%= latest_ruby %> - mongodb-version: <%= latest_stable_mdb %> + mongodb-version: <%= actual_and_upcoming_mdb %> topology: <%= topologies %> os: rhel8 display_name: ${auth-and-ssl} ${ruby} ${topology} tasks: - name: "test-mlaunch" - # 'latest' is broken until the QE2 changes are merged (RUBY-3211, RUBY-3226) - matrix_name: "mongo-recent" matrix_spec: ruby: <%= recent_rubies %> - #mongodb-version: ['latest', '6.0'] - mongodb-version: ['6.0'] + mongodb-version: <%= actual_and_upcoming_mdb %> topology: <%= topologies %> - os: ['rhel8', 'ubuntu2204'] + os: ['rhel8'] + # There is no latest for ubuntu2204, so we can't test it here. + # os: ['rhel8', 'ubuntu2204'] display_name: "${mongodb-version} ${os} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" @@ -349,19 +350,17 @@ buildvariants: tasks: - name: "test-kerberos" - # 'latest' is broken until the QE2 changes are merged (RUBY-3211, RUBY-3226) - # - # - matrix_name: "fle-latest" - # matrix_spec: - # auth-and-ssl: "noauth-and-nossl" - # ruby: <%= latest_ruby %> - # topology: [replica-set, sharded-cluster] - # mongodb-version: [ 'latest' ] - # os: rhel8 - # fle: helper - # display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" - # tasks: - # - name: "test-fle" + - matrix_name: "fle-latest" + matrix_spec: + auth-and-ssl: "noauth-and-nossl" + ruby: <%= latest_ruby %> + topology: [replica-set, sharded-cluster] + mongodb-version: [ 'latest', '7.0' ] + os: rhel8 + fle: helper + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" + tasks: + - name: "test-fle" - matrix_name: "fle" matrix_spec: diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index aa07574ff2..9186b53aeb 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit aa07574ff2dc491b8a3b99c35a87e753c3b34edf +Subproject commit 9186b53aebe04390d68e348946a4cefc33c6964d diff --git a/spec/integration/client_update_spec.rb b/spec/integration/client_update_spec.rb index 76d099174e..34fb0e269c 100644 --- a/spec/integration/client_update_spec.rb +++ b/spec/integration/client_update_spec.rb @@ -15,8 +15,8 @@ include_context 'with local kms_providers' before do - authorized_client.use(:keyvault)[:datakeys].drop - authorized_client.use(:keyvault)[:datakeys].insert_one(data_key) + authorized_client.use(:keyvault)[:datakeys, write_concern: { w: :majority }].drop + authorized_client.use(:keyvault)[:datakeys, write_concern: { w: :majority }].insert_one(data_key) authorized_client.use(:auto_encryption)[:users].drop authorized_client.use(:auto_encryption)[:users, { diff --git a/spec/integration/query_cache_spec.rb b/spec/integration/query_cache_spec.rb index 3145dc2fa3..81e1a640a9 100644 --- a/spec/integration/query_cache_spec.rb +++ b/spec/integration/query_cache_spec.rb @@ -192,7 +192,7 @@ min_server_fcv '3.6' before do - authorized_client['test'].drop + authorized_client['test', write_concern: { w: :majority }].drop end context 'when two queries have same read concern' do diff --git a/spec/integration/snapshot_query_examples_spec.rb b/spec/integration/snapshot_query_examples_spec.rb index 075bfc49fd..761a59052d 100644 --- a/spec/integration/snapshot_query_examples_spec.rb +++ b/spec/integration/snapshot_query_examples_spec.rb @@ -17,17 +17,17 @@ context "Snapshot Query Example 1" do before do client = authorized_client.use('pets') - client['cats'].delete_many - client['dogs'].delete_many + client['cats', write_concern: { w: :majority }].delete_many + client['dogs', write_concern: { w: :majority }].delete_many - client['cats'].insert_one( + client['cats', write_concern: { w: :majority }].insert_one( name: "Whiskers", color: "white", age: 10, adoptable: true ) - client['dogs'].insert_one( + client['dogs', write_concern: { w: :majority }].insert_one( name: "Pebbles", color: "Brown", age: 10, @@ -75,9 +75,9 @@ before do client = authorized_client.use('retail') - client['sales'].delete_many + client['sales', write_concern: { w: :majority }].delete_many - client['sales'].insert_one( + client['sales', write_concern: { w: :majority }].insert_one( shoeType: "boot", price: 30, saleDate: Time.now diff --git a/spec/shared b/spec/shared index 81298ee8ee..907504ce6b 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit 81298ee8ee4bac7f446abcbd13045332396d05fe +Subproject commit 907504ce6b61a56c38caeeb01fa3d21a86c19a5d diff --git a/spec/support/common_shortcuts.rb b/spec/support/common_shortcuts.rb index 6092129778..32d386b36a 100644 --- a/spec/support/common_shortcuts.rb +++ b/spec/support/common_shortcuts.rb @@ -373,7 +373,11 @@ def mock_dns(config) if $last_async_task.nil? STDERR.puts "No async task - server never started?" else - $last_async_task.stop + begin + $last_async_task.stop + rescue NoMethodError => e + STDERR.puts "Error stopping async task: #{e}" + end end thread.kill From d44d19fe80d3ef48a13dd2a4ce1f3d8a04833ed4 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 12 Jun 2023 08:15:51 -0600 Subject: [PATCH 133/198] RUBY-3200 add Ruby 3.2 to CI (#2728) * add ruby-3.2 to the matrices * fix (one of?) the failing tests under ruby 3.2 * basic style pass * fix another 3.2 error * bump the GH actions to also use ruby 3.2 * add aws-sdk-sts to dev/test dependencies CI under ruby-3.2 complains about missing constant Aws::STS *shrug* * tweak again--make sure both AWS sts and core are loaded * add Ruby 3.2, deprecate Ruby 2.5 and 2.6 --- .evergreen/config.yml | 80 ++-- .evergreen/config/standard.yml.erb | 14 +- .github/workflows/test.yml | 8 +- docs/reference/driver-compatibility.txt | 31 ++ gemfiles/standard.rb | 1 + .../retryable_writes_errors_spec.rb | 4 +- spec/mongo/client_spec.rb | 395 ++++++++---------- spec/support/aws_utils.rb | 5 +- 8 files changed, 252 insertions(+), 286 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index e1d33c2773..cc3dc63305 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1062,7 +1062,7 @@ buildvariants: - matrix_name: "auth/ssl" matrix_spec: auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: ["latest", "7.0", "6.0"] topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1072,7 +1072,7 @@ buildvariants: - matrix_name: "mongo-recent" matrix_spec: - ruby: ["ruby-3.1", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-3.1", "jruby-9.3"] mongodb-version: ["latest", "7.0", "6.0"] topology: ["standalone", "replica-set", "sharded-cluster"] os: ['rhel8'] @@ -1090,7 +1090,7 @@ buildvariants: # RUBY-3226) - matrix_name: "mongo-recent-arm" matrix_spec: - ruby: "ruby-3.1" + ruby: "ruby-3.2" #mongodb-version: ['latest', '6.0'] mongodb-version: ['6.0'] topology: ["standalone", "replica-set", "sharded-cluster"] @@ -1101,7 +1101,7 @@ buildvariants: - matrix_name: "mongo-5.x" matrix_spec: - ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] mongodb-version: ['5.3', '5.0'] topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1131,7 +1131,7 @@ buildvariants: - matrix_name: "single-lb" matrix_spec: - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: "6.0" topology: load-balanced single-mongos: single-mongos @@ -1142,7 +1142,7 @@ buildvariants: - matrix_name: "mongo-5.0-api-version" matrix_spec: - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: '5.0' topology: standalone api-version-required: yes @@ -1153,7 +1153,7 @@ buildvariants: - matrix_name: "single-mongos" matrix_spec: - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: "6.0" topology: "sharded-cluster" single-mongos: single-mongos @@ -1165,7 +1165,7 @@ buildvariants: - matrix_name: "no-retry-reads" matrix_spec: retry-reads: no-retry-reads - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: "6.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1176,7 +1176,7 @@ buildvariants: - matrix_name: "no-retry-writes" matrix_spec: retry-writes: no-retry-writes - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: "6.0" topology: [replica-set, sharded-cluster] os: rhel8 @@ -1198,7 +1198,7 @@ buildvariants: - matrix_name: "lint" matrix_spec: lint: on - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: "6.0" topology: '*' os: rhel8 @@ -1209,7 +1209,7 @@ buildvariants: - matrix_name: "fork" matrix_spec: fork: on - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: "6.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1220,7 +1220,7 @@ buildvariants: - matrix_name: "solo" matrix_spec: solo: on - ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] mongodb-version: "6.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1242,7 +1242,7 @@ buildvariants: - matrix_name: "stress" matrix_spec: stress: on - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: ["6.0", "5.3"] topology: replica-set os: rhel8 @@ -1256,7 +1256,7 @@ buildvariants: # - matrix_name: "x509-tests" # matrix_spec: # auth-and-ssl: "x509" - # ruby: "ruby-3.1" + # ruby: "ruby-3.2" # # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure # # the server for certain auth mechanisms. Once run-tests.sh is made smart # # enough to install mongosh, and then use either mongo or mongosh @@ -1283,7 +1283,7 @@ buildvariants: - matrix_name: "zlib" matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] - ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] mongodb-version: "6.0" topology: "replica-set" compressor: 'zlib' @@ -1295,7 +1295,7 @@ buildvariants: - matrix_name: "snappy" matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] - ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] mongodb-version: "6.0" topology: "replica-set" compressor: 'snappy' @@ -1311,7 +1311,7 @@ buildvariants: - matrix_name: "zstd-auth" matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] mongodb-version: "6.0" topology: "replica-set" compressor: 'zstd' @@ -1322,7 +1322,7 @@ buildvariants: - matrix_name: "activesupport" matrix_spec: - ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] mongodb-version: "6.0" topology: replica-set as: as @@ -1333,7 +1333,7 @@ buildvariants: - matrix_name: "bson" matrix_spec: - ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] mongodb-version: "6.0" topology: replica-set bson: "*" @@ -1345,7 +1345,7 @@ buildvariants: # kerberos integration tests are broken (RUBY-3266) # - matrix_name: "kerberos-integration" # matrix_spec: - # ruby: ["ruby-3.1", "ruby-2.7", "jruby-9.3"] + # ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] # os: rhel8 # display_name: "Kerberos integration ${os} ${ruby}" # tasks: @@ -1353,7 +1353,7 @@ buildvariants: - matrix_name: "kerberos-unit" matrix_spec: - ruby: "ruby-3.1" + ruby: "ruby-3.2" mongodb-version: "6.0" topology: standalone os: rhel8 @@ -1365,7 +1365,7 @@ buildvariants: - matrix_name: "fle-latest" matrix_spec: auth-and-ssl: "noauth-and-nossl" - ruby: "ruby-3.1" + ruby: "ruby-3.2" topology: [replica-set, sharded-cluster] mongodb-version: [ 'latest', '7.0' ] os: rhel8 @@ -1377,7 +1377,7 @@ buildvariants: - matrix_name: "fle" matrix_spec: auth-and-ssl: "noauth-and-nossl" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: [replica-set, sharded-cluster] mongodb-version: [ '6.0', '4.4', '4.2', '4.0' ] os: rhel8 @@ -1389,7 +1389,7 @@ buildvariants: - matrix_name: aws-auth-regular matrix_spec: auth-and-ssl: [ aws-regular, aws-assume-role, aws-ec2, aws-ecs, aws-web-identity ] - ruby: "ruby-3.1" + ruby: "ruby-3.2" topology: standalone # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure # the server for certain auth mechanisms. Once run-tests.sh is made smart @@ -1406,7 +1406,7 @@ buildvariants: matrix_spec: ocsp-verifier: true # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 - ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] + ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1419,7 +1419,7 @@ buildvariants: ocsp-algorithm: ecdsa ocsp-must-staple: on ocsp-delegate: on - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1432,7 +1432,7 @@ buildvariants: matrix_spec: ocsp-algorithm: rsa ocsp-status: unknown - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1448,7 +1448,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "none" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1462,7 +1462,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "none" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1476,7 +1476,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: fail extra-uri-options: "none" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1490,7 +1490,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1504,7 +1504,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1518,7 +1518,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1532,7 +1532,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1546,7 +1546,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1560,7 +1560,7 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: ["ruby-3.1", "ruby-2.7"] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone mongodb-version: "6.0" os: rhel8 @@ -1590,7 +1590,7 @@ buildvariants: - matrix_name: testgcpkms-variant matrix_spec: - ruby: "ruby-3.1" + ruby: "ruby-3.2" fle: helper topology: standalone os: rhel8 @@ -1614,7 +1614,7 @@ buildvariants: - matrix_name: "atlas" matrix_spec: - ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] os: rhel8 display_name: "Atlas tests ${ruby}" tasks: @@ -1623,8 +1623,8 @@ buildvariants: - matrix_name: "serverless" matrix_spec: # https://jira.mongodb.org/browse/RUBY-3217 - # ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] - ruby: ["ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] + # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] fle: path os: rhel8 display_name: "Atlas serverless ${ruby} single mongos" diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index 85c093c59a..8e56bcf887 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -1,12 +1,6 @@ <% topologies = %w( standalone replica-set sharded-cluster ) - # ruby-3.2 is available in the toolchain, but tests break. It will - # require some investigation. Once available: - # 1. set `latest_ruby` to ruby-3.2 - # 2. add ruby-3.2 to `recent_rubies` and `supported_mri_rubies` - # 3. replace ruby-3.1 with ruby-3.2 in `sample_mri_rubies`. - # jruby-9.4 is available in the toolchain, but tests break. It will # require some investigation. Once available: # 1. replace jruby-9.3 with jruby-9.4 in `recent_rubies` and @@ -15,13 +9,13 @@ # latest_ruby = the most recently released, stable version of Ruby # (make sure this version is being built by 10gen/mongo-ruby-toolchain) - latest_ruby = "ruby-3.1".inspect # so it gets quoted as a string + latest_ruby = "ruby-3.2".inspect # so it gets quoted as a string # these are used for testing against a few recent ruby versions - recent_rubies = %w( ruby-3.1 jruby-9.3 ) + recent_rubies = %w( ruby-3.2 ruby-3.1 jruby-9.3 ) # this is a list of the most most recent 3.x and 2.x MRI ruby versions - sample_mri_rubies = %w( ruby-3.1 ruby-2.7 ) + sample_mri_rubies = %w( ruby-3.2 ruby-2.7 ) # as above, but including the most recent JRuby release sample_rubies = sample_mri_rubies + %w( jruby-9.3 ) @@ -32,7 +26,7 @@ # all supported JRuby versions provided by 10gen/mongo-ruby-toolchain jrubies = %w( jruby-9.3 jruby-9.2 ) - supported_mri_rubies = %w( ruby-3.1 ruby-3.0 + supported_mri_rubies = %w( ruby-3.2 ruby-3.1 ruby-3.0 ruby-2.7 ruby-2.6 ruby-2.5 ) supported_rubies = supported_mri_rubies + jrubies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d4ee5e4168..44c7b6b2bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-20.04 ] - ruby: ["2.5", "2.6", "2.7", "3.0", "3.1"] + ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2"] mongodb: ["3.6", "4.4", "5.0"] topology: [replica_set, sharded_cluster] include: @@ -41,11 +41,11 @@ jobs: mongodb: "5.0" topology: server - os: ubuntu-latest - ruby: "3.0" + ruby: "3.1" mongodb: "5.0" topology: server - os: ubuntu-latest - ruby: "3.1" + ruby: "3.2" mongodb: "5.0" topology: server - os: ubuntu-18.04 @@ -53,7 +53,7 @@ jobs: mongodb: "3.6" topology: replica_set - os: ubuntu-latest - ruby: "3.1" + ruby: "3.2" mongodb: "6.0" topology: replica_set steps: diff --git a/docs/reference/driver-compatibility.txt b/docs/reference/driver-compatibility.txt index 179d21181a..bcb35561fb 100644 --- a/docs/reference/driver-compatibility.txt +++ b/docs/reference/driver-compatibility.txt @@ -245,6 +245,7 @@ for that Ruby version is deprecated. :class: compatibility-large no-padding * - Ruby Driver + - Ruby 3.2 - Ruby 3.1 - Ruby 3.0 - Ruby 2.7 @@ -260,12 +261,30 @@ for that Ruby version is deprecated. - JRuby 9.2 - JRuby 9.1 + * - 2.19 + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - D + - D + - + - + - + - + - + - + - |checkmark| + - |checkmark| + - + * - 2.18 - |checkmark| - |checkmark| - |checkmark| - |checkmark| - |checkmark| + - |checkmark| - - - @@ -277,6 +296,7 @@ for that Ruby version is deprecated. - * - 2.17 + - - |checkmark| - |checkmark| - |checkmark| @@ -293,6 +313,7 @@ for that Ruby version is deprecated. - * - 2.16 + - - - |checkmark| - |checkmark| @@ -309,6 +330,7 @@ for that Ruby version is deprecated. - * - 2.15 + - - - |checkmark| - |checkmark| @@ -325,6 +347,7 @@ for that Ruby version is deprecated. - * - 2.14 + - - - - |checkmark| @@ -341,6 +364,7 @@ for that Ruby version is deprecated. - * - 2.13 + - - - - |checkmark| @@ -357,6 +381,7 @@ for that Ruby version is deprecated. - * - 2.12 + - - - - |checkmark| @@ -373,6 +398,7 @@ for that Ruby version is deprecated. - * - 2.11 + - - - - |checkmark| @@ -389,6 +415,7 @@ for that Ruby version is deprecated. - * - 2.10 + - - - - |checkmark| @@ -408,6 +435,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -424,6 +452,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -440,6 +469,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -456,6 +486,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - |checkmark| - |checkmark| diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index 89a68c5548..ca63921b8f 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -21,6 +21,7 @@ def standard_dependencies gem 'aws-sdk-ec2' gem 'aws-sdk-ecs' gem 'aws-sdk-iam' + gem 'aws-sdk-sts' gem 'paint' # for benchmark tests diff --git a/spec/integration/retryable_writes_errors_spec.rb b/spec/integration/retryable_writes_errors_spec.rb index 1c149e080e..2769089b11 100644 --- a/spec/integration/retryable_writes_errors_spec.rb +++ b/spec/integration/retryable_writes_errors_spec.rb @@ -71,10 +71,10 @@ authorized_client.subscribe(Mongo::Monitoring::COMMAND, subscriber) authorized_client.use(:admin).command(failpoint1) - expect(authorized_collection.write_worker).to receive(:retry_write).once.and_wrap_original do |m, *args, &block| + expect(authorized_collection.write_worker).to receive(:retry_write).once.and_wrap_original do |m, *args, **kwargs, &block| expect(args.first.code).to eq(91) authorized_client.use(:admin).command(failpoint2) - m.call(*args, &block) + m.call(*args, **kwargs, &block) end end diff --git a/spec/mongo/client_spec.rb b/spec/mongo/client_spec.rb index 5427d1f72f..ac8bcc9148 100644 --- a/spec/mongo/client_spec.rb +++ b/spec/mongo/client_spec.rb @@ -3,11 +3,13 @@ require 'spec_helper' +DEFAULT_LOCAL_HOST = '127.0.0.1:27017' +ALT_LOCAL_HOST = '127.0.0.1:27010' + # NB: tests for .new, #initialize, #use, #with and #dup are in # client_construction_spec.rb. describe Mongo::Client do - let(:subscriber) { Mrss::EventSubscriber.new } let(:monitored_client) do @@ -17,24 +19,21 @@ end describe '#==' do - let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end context 'when the other is a client' do - context 'when the options and cluster are equal' do - let(:other) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end @@ -44,12 +43,11 @@ end context 'when the options are not equal' do - let(:other) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :secondary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :secondary }, + database: SpecConfig.instance.test_db ) end @@ -59,12 +57,11 @@ end context 'when cluster is not equal' do - let(:other) do new_local_client_nmio( - ['127.0.0.1:27010'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ ALT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end @@ -75,7 +72,6 @@ end context 'when the other is not a client' do - it 'returns false' do expect(client).not_to eq('test') end @@ -83,14 +79,12 @@ end describe '#[]' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - :database => SpecConfig.instance.test_db) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], + database: SpecConfig.instance.test_db) end shared_examples_for 'a collection switching object' do - before do client.use(:dbtest) end @@ -101,7 +95,6 @@ end context 'when provided a string' do - let(:collection) do client['users'] end @@ -110,7 +103,6 @@ end context 'when provided a symbol' do - let(:collection) do client[:users] end @@ -120,24 +112,21 @@ end describe '#eql' do - let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end context 'when the other is a client' do - context 'when the options and cluster are equal' do - let(:other) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end @@ -147,12 +136,11 @@ end context 'when the options are not equal' do - let(:other) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :secondary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :secondary }, + database: SpecConfig.instance.test_db ) end @@ -162,12 +150,11 @@ end context 'when the cluster is not equal' do - let(:other) do new_local_client_nmio( - ['127.0.0.1:27010'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ ALT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end @@ -178,12 +165,11 @@ end context 'when the other is not a client' do - let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end @@ -194,14 +180,13 @@ end describe '#hash' do - let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :local_threshold => 0.010, - :server_selection_timeout => 10000, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + local_threshold: 0.010, + server_selection_timeout: 10000, + database: SpecConfig.instance.test_db ) end @@ -209,14 +194,14 @@ retry_writes: true, retry_reads: true, monitoring_io: false) } let(:options) do - Mongo::Options::Redacted.new(:read => { :mode => :primary }, - :local_threshold => 0.010, - :server_selection_timeout => 10000, - :database => SpecConfig.instance.test_db) + Mongo::Options::Redacted.new(read: { mode: :primary }, + local_threshold: 0.010, + server_selection_timeout: 10000, + database: SpecConfig.instance.test_db) end let(:expected) do - [client.cluster, default_options.merge(options)].hash + [ client.cluster, default_options.merge(options) ].hash end it 'returns a hash of the cluster and options' do @@ -225,12 +210,11 @@ end describe '#inspect' do - let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end @@ -239,14 +223,13 @@ end context 'when there is sensitive data in the options' do - let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db, - :password => 'some_password', - :user => 'emily' + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db, + password: 'some_password', + user: 'emily' ) end @@ -257,14 +240,12 @@ end describe '#server_selector' do - context 'when there is a read preference set' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - :database => SpecConfig.instance.test_db, - :read => mode, - :server_selection_timeout => 2) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], + database: SpecConfig.instance.test_db, + read: mode, + server_selection_timeout: 2) end let(:server_selector) do @@ -272,9 +253,8 @@ end context 'when mode is primary' do - let(:mode) do - { :mode => :primary } + { mode: :primary } end it 'returns a primary server selector' do @@ -287,9 +267,8 @@ end context 'when mode is primary_preferred' do - let(:mode) do - { :mode => :primary_preferred } + { mode: :primary_preferred } end it 'returns a primary preferred server selector' do @@ -298,9 +277,8 @@ end context 'when mode is secondary' do - let(:mode) do - { :mode => :secondary } + { mode: :secondary } end it 'uses a Secondary server selector' do @@ -309,9 +287,8 @@ end context 'when mode is secondary preferred' do - let(:mode) do - { :mode => :secondary_preferred } + { mode: :secondary_preferred } end it 'uses a Secondary server selector' do @@ -320,9 +297,8 @@ end context 'when mode is nearest' do - let(:mode) do - { :mode => :nearest } + { mode: :nearest } end it 'uses a Secondary server selector' do @@ -331,11 +307,10 @@ end context 'when no mode provided' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - :database => SpecConfig.instance.test_db, - :server_selection_timeout => 2) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], + database: SpecConfig.instance.test_db, + server_selection_timeout: 2) end it 'returns a primary server selector' do @@ -344,7 +319,6 @@ end context 'when the read preference is printed' do - let(:client) do new_local_client_nmio(SpecConfig.instance.addresses, options) end @@ -360,7 +334,7 @@ let(:error) do begin client.database.command(ping: 1) - rescue => e + rescue StandardError => e e end end @@ -373,12 +347,11 @@ end describe '#read_preference' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - :database => SpecConfig.instance.test_db, - :read => mode, - :server_selection_timeout => 2) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], + database: SpecConfig.instance.test_db, + read: mode, + server_selection_timeout: 2) end let(:preference) do @@ -386,9 +359,8 @@ end context 'when mode is primary' do - let(:mode) do - { :mode => :primary } + { mode: :primary } end it 'returns a primary read preference' do @@ -397,9 +369,8 @@ end context 'when mode is primary_preferred' do - let(:mode) do - { :mode => :primary_preferred } + { mode: :primary_preferred } end it 'returns a primary preferred read preference' do @@ -408,9 +379,8 @@ end context 'when mode is secondary' do - let(:mode) do - { :mode => :secondary } + { mode: :secondary } end it 'returns a secondary read preference' do @@ -419,9 +389,8 @@ end context 'when mode is secondary preferred' do - let(:mode) do - { :mode => :secondary_preferred } + { mode: :secondary_preferred } end it 'returns a secondary preferred read preference' do @@ -430,9 +399,8 @@ end context 'when mode is nearest' do - let(:mode) do - { :mode => :nearest } + { mode: :nearest } end it 'returns a nearest read preference' do @@ -441,11 +409,10 @@ end context 'when no mode provided' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], - :database => SpecConfig.instance.test_db, - :server_selection_timeout => 2) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], + database: SpecConfig.instance.test_db, + server_selection_timeout: 2) end it 'returns nil' do @@ -455,12 +422,10 @@ end describe '#write_concern' do - let(:concern) { client.write_concern } context 'when no option was provided to the client' do - - let(:client) { new_local_client_nmio(['127.0.0.1:27017'], :database => SpecConfig.instance.test_db) } + let(:client) { new_local_client_nmio([ DEFAULT_LOCAL_HOST ], database: SpecConfig.instance.test_db) } it 'does not set the write concern' do expect(concern).to be_nil @@ -468,24 +433,20 @@ end context 'when an option is provided' do - context 'when the option is acknowledged' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :write => { :j => true }, :database => SpecConfig.instance.test_db) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], write: { j: true }, database: SpecConfig.instance.test_db) end it 'returns a acknowledged write concern' do - expect(concern.get_last_error).to eq(:getlasterror => 1, :j => true) + expect(concern.get_last_error).to eq(getlasterror: 1, j: true) end end context 'when the option is unacknowledged' do - context 'when the w is 0' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :write => { :w => 0 }, :database => SpecConfig.instance.test_db) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], write: { w: 0 }, database: SpecConfig.instance.test_db) end it 'returns an unacknowledged write concern' do @@ -494,9 +455,8 @@ end context 'when the w is -1' do - let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], :write => { :w => -1 }, :database => SpecConfig.instance.test_db) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], write: { w: -1 }, database: SpecConfig.instance.test_db) end it 'raises an error' do @@ -510,15 +470,15 @@ end [ - [:max_read_retries, 1], - [:read_retry_interval, 5], - [:max_write_retries, 1], + [ :max_read_retries, 1 ], + [ :read_retry_interval, 5 ], + [ :max_write_retries, 1 ], ].each do |opt, default| describe "##{opt}" do let(:client_options) { {} } let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], client_options) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ], client_options) end it "defaults to #{default}" do @@ -528,7 +488,7 @@ end context 'specified on client' do - let(:client_options) { {opt => 2} } + let(:client_options) { { opt => 2 } } it 'inherits from client' do expect(client.options[opt]).to eq(2) @@ -550,24 +510,21 @@ end describe '#database' do - let(:database) { client.database } context 'when client has :server_api option' do - let(:client) do - new_local_client_nmio(['localhost'], server_api: {version: '1'}) + new_local_client_nmio([ 'localhost' ], server_api: { version: '1' }) end it 'is not transfered to the collection' do - database.options[:server_api].should be nil + expect(database.options[:server_api]).to be_nil end end end describe '#database_names' do - it 'returns a list of database names' do expect(root_authorized_client.database_names).to include( 'admin' @@ -597,17 +554,16 @@ min_server_version '4.4' it 'returns a list of database names and send comment' do - result = monitored_client.database_names({}, comment: "comment") + result = monitored_client.database_names({}, comment: 'comment') expect(result).to include('admin') - command = subscriber.command_started_events("listDatabases").last&.command + command = subscriber.command_started_events('listDatabases').last&.command expect(command).not_to be_nil - expect(command["comment"]).to eq("comment") + expect(command['comment']).to eq('comment') end end end describe '#list_databases' do - it 'returns a list of database info documents' do expect( root_authorized_client.list_databases.collect do |i| @@ -702,19 +658,18 @@ min_server_version '4.4' it 'returns a list of database names and send comment' do - result = monitored_client.list_databases({}, false, comment: "comment").collect do |i| + result = monitored_client.list_databases({}, false, comment: 'comment').collect do |i| i['name'] end expect(result).to include('admin') - command = subscriber.command_started_events("listDatabases").last&.command + command = subscriber.command_started_events('listDatabases').last&.command expect(command).not_to be_nil - expect(command["comment"]).to eq("comment") + expect(command['comment']).to eq('comment') end end end describe '#list_mongo_databases' do - let(:options) do { read: { mode: :secondary } } end @@ -758,18 +713,18 @@ min_server_version '4.4' it 'returns a list of database names and send comment' do - result = monitored_client.list_mongo_databases({}, comment: "comment") + result = monitored_client.list_mongo_databases({}, comment: 'comment') expect(result).to all(be_a(Mongo::Database)) - command = subscriber.command_started_events("listDatabases").last&.command + command = subscriber.command_started_events('listDatabases').last&.command expect(command).not_to be_nil - expect(command["comment"]).to eq("comment") + expect(command['comment']).to eq('comment') end end end describe '#close' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017']) + new_local_client_nmio([ DEFAULT_LOCAL_HOST ]) end it 'disconnects the cluster and returns true' do @@ -781,9 +736,8 @@ end describe '#reconnect' do - let(:client) do - new_local_client_nmio([ClusterConfig.instance.primary_address_str]) + new_local_client_nmio([ ClusterConfig.instance.primary_address_str ]) end it 'replaces the cluster' do @@ -806,7 +760,6 @@ end describe '#collections' do - before do authorized_client.database[:users].drop authorized_client.database[:users].create @@ -823,7 +776,6 @@ end describe '#start_session' do - let(:session) do authorized_client.start_session end @@ -842,7 +794,6 @@ end context 'when options are provided' do - let(:options) do { causal_consistency: true } end @@ -857,14 +808,12 @@ end context 'when options are not provided' do - it 'does not set options on the session' do expect(session.options).to eq({ implicit: false }) end end context 'when a session is checked out and checked back in' do - let!(:session_a) do authorized_client.start_session end @@ -902,7 +851,6 @@ end context 'when an implicit session is used' do - before do authorized_client.database.command(ping: 1) end @@ -932,10 +880,10 @@ end let(:options) do - { :max_pool_size => 1, :retry_writes => true } + { max_pool_size: 1, retry_writes: true } end - shared_examples "a single connection" do + shared_examples 'a single connection' do # JRuby, due to being concurrent, does not like rspec setting mocks # in threads while other threads are calling the methods being mocked. # My theory is that rspec removes & redefines methods as part of @@ -947,226 +895,218 @@ before do sessions_checked_out = 0 - allow_any_instance_of(Mongo::Server).to receive(:with_connection).and_wrap_original do |m, *args, &block| - m.call(*args) do |connection| + allow_any_instance_of(Mongo::Server).to receive(:with_connection).and_wrap_original do |m, *args, **kwargs, &block| + m.call(*args, **kwargs) do |connection| sessions_checked_out = 0 res = block.call(connection) expect(sessions_checked_out).to be < 2 res end end - - allow_any_instance_of(Mongo::Session).to receive(:materialize).and_wrap_original do |m, *args| - sessions_checked_out += 1 - m.call(*args).tap do - checked_out_connections = args[0].connection_pool.instance_variable_get("@checked_out_connections") - expect(checked_out_connections.length).to eq 1 - end - end end - it 'doesn\'t have any live sessions' do + it "doesn't have any live sessions" do threads.each do |thread| thread.join end end end - context "when doing three inserts" do + context 'when doing three inserts' do let(:threads) do (1..3).map do |i| Thread.new do - client['test'].insert_one({test: "test#{i}"}) + client['test'].insert_one({ test: "test#{i}" }) end end end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing an insert and two updates" do + context 'when doing an insert and two updates' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing an insert, update and delete" do + context 'when doing an insert, update and delete' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads << Thread.new do - client['test'].delete_one({test: "test"}) + client['test'].delete_one({ test: 'test' }) end threads end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing an insert, update and find" do + context 'when doing an insert, update and find' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads << Thread.new do - client['test'].find({test: "test"}).to_a + client['test'].find({ test: 'test' }).to_a end threads end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing an insert, update and bulk write" do + context 'when doing an insert, update and bulk write' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads << Thread.new do - client['test'].bulk_write([{ insert_one: { test: "test1" } }, - { update_one: { filter: { test: "test1" }, update: { "$set" => { test: "test2" } } } } ]) + client['test'].bulk_write([ { insert_one: { test: 'test1' } }, + { update_one: { filter: { test: 'test1' }, update: { '$set' => { test: 'test2' } } } } ]) end threads end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing an insert, update and find_one_and_delete" do + context 'when doing an insert, update and find_one_and_delete' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads << Thread.new do - client['test'].find_one_and_delete({test: "test"}) + client['test'].find_one_and_delete({ test: 'test' }) end threads end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing an insert, update and find_one_and_update" do + context 'when doing an insert, update and find_one_and_update' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads << Thread.new do - client['test'].find_one_and_update({test: "test"}, {test: "test2"}) + client['test'].find_one_and_update({ test: 'test' }, { test: 'test2' }) end threads end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing an insert, update and find_one_and_replace" do + context 'when doing an insert, update and find_one_and_replace' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads << Thread.new do - client['test'].find_one_and_replace({test: "test"}, {test: "test2"}) + client['test'].find_one_and_replace({ test: 'test' }, { test: 'test2' }) end threads end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing an insert, update and a replace" do + context 'when doing an insert, update and a replace' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({test: "test"}, { "$set" => { test: "test2" } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 'test2' } }) end threads << Thread.new do - client['test'].replace_one({test: "test"}, {test: "test2"}) + client['test'].replace_one({ test: 'test' }, { test: 'test2' }) end threads end - include_examples "a single connection" + include_examples 'a single connection' end - context "when doing all of the operations" do + context 'when doing all of the operations' do let(:threads) do threads = [] threads << Thread.new do - client['test'].insert_one({test: "test"}) + client['test'].insert_one({ test: 'test' }) end threads << Thread.new do - client['test'].update_one({ test: "test" }, { "$set" => { test: 1 } }) + client['test'].update_one({ test: 'test' }, { '$set' => { test: 1 } }) end threads << Thread.new do - client['test'].find_one_and_replace({test: "test"}, {test: "test2"}) + client['test'].find_one_and_replace({ test: 'test' }, { test: 'test2' }) end threads << Thread.new do - client['test'].delete_one({test: "test"}) + client['test'].delete_one({ test: 'test' }) end threads << Thread.new do - client['test'].find({test: "test"}).to_a + client['test'].find({ test: 'test' }).to_a end threads << Thread.new do - client['test'].bulk_write([{ insert_one: { test: "test1" } }, - { update_one: { filter: { test: "test1" }, update: { "$set" => { test: "test2" } } } } ]) + client['test'].bulk_write([ { insert_one: { test: 'test1' } }, + { update_one: { filter: { test: 'test1' }, update: { '$set' => { test: 'test2' } } } } ]) end threads << Thread.new do - client['test'].find_one_and_delete({test: "test"}) + client['test'].find_one_and_delete({ test: 'test' }) end threads << Thread.new do - client['test'].find_one_and_update({test: "test"}, {test: "test2"}) + client['test'].find_one_and_update({ test: 'test' }, { test: 'test2' }) end threads << Thread.new do - client['test'].find_one_and_replace({test: "test"}, {test: "test2"}) + client['test'].find_one_and_replace({ test: 'test' }, { test: 'test2' }) end threads << Thread.new do - client['test'].replace_one({test: "test"}, {test: "test2"}) + client['test'].replace_one({ test: 'test' }, { test: 'test2' }) end threads end - include_examples "a single connection" + include_examples 'a single connection' end end end @@ -1219,18 +1159,17 @@ end describe '#summary' do - context 'monitoring omitted' do let(:client) do new_local_client_nmio( - ['127.0.0.1:27017'], - :read => { :mode => :primary }, - :database => SpecConfig.instance.test_db + [ DEFAULT_LOCAL_HOST ], + read: { mode: :primary }, + database: SpecConfig.instance.test_db ) end it 'indicates lack of monitoring' do - client.summary.should =~ /servers=.*UNKNOWN.*NO-MONITORING/ + expect(client.summary).to match /servers=.*UNKNOWN.*NO-MONITORING/ end end @@ -1242,8 +1181,8 @@ end it 'does not indicate lack of monitoring' do - client.summary.should =~ /servers=.*(?:STANDALONE|PRIMARY|MONGOS)/ - client.summary.should_not =~ /servers=.*(?:STANDALONE|PRIMARY|MONGOS).*NO-MONITORING/ + expect(client.summary).to match /servers=.*(?:STANDALONE|PRIMARY|MONGOS)/ + expect(client.summary).not_to match /servers=.*(?:STANDALONE|PRIMARY|MONGOS).*NO-MONITORING/ end end @@ -1257,7 +1196,7 @@ end it 'does not indicate lack of monitoring' do - client.summary.should =~ /servers=.*(STANDALONE|PRIMARY|MONGOS|\bLB\b).*NO-MONITORING/ + expect(client.summary).to match /servers=.*(STANDALONE|PRIMARY|MONGOS|\bLB\b).*NO-MONITORING/ end end end diff --git a/spec/support/aws_utils.rb b/spec/support/aws_utils.rb index 00c54ec662..b6dd43c677 100644 --- a/spec/support/aws_utils.rb +++ b/spec/support/aws_utils.rb @@ -4,13 +4,14 @@ autoload :Byebug, 'byebug' autoload :Paint, 'paint' +require 'aws-sdk-core' + module Aws autoload :CloudWatchLogs, 'aws-sdk-cloudwatchlogs' - autoload :Credentials, 'aws-sdk-core' autoload :EC2, 'aws-sdk-ec2' autoload :ECS, 'aws-sdk-ecs' autoload :IAM, 'aws-sdk-iam' - autoload :STS, 'aws-sdk-core' + autoload :STS, 'aws-sdk-sts' end module AwsUtils From 990d02c4d41dd12efbfa9db4e6fba7f8b52d7714 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 12 Jun 2023 19:00:16 +0200 Subject: [PATCH 134/198] RUBY-3252 Show user friendly error if no ffi (#2730) * RUBY-3252 Show user friendly error if no ffi * Cleanup * Remove one test * Add comment explaining why test is omited --- lib/mongo/crypt.rb | 11 +++++++++++ lib/mongo/crypt/auto_encrypter.rb | 1 + lib/mongo/crypt/explicit_encrypter.rb | 1 + spec/mongo/crypt_spec.rb | 21 +++++++++++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 spec/mongo/crypt_spec.rb diff --git a/lib/mongo/crypt.rb b/lib/mongo/crypt.rb index 09a78b5d16..5701824990 100644 --- a/lib/mongo/crypt.rb +++ b/lib/mongo/crypt.rb @@ -36,5 +36,16 @@ module Crypt autoload(:ExplicitEncrypter, 'mongo/crypt/explicit_encrypter') autoload(:AutoEncrypter, 'mongo/crypt/auto_encrypter') autoload(:KMS, 'mongo/crypt/kms') + + def validate_ffi! + return if defined?(FFI) + + require 'ffi' + rescue LoadError => e + raise Error::UnmetDependency, 'Cannot enable encryption because the ffi gem ' \ + "has not been installed. Add \"gem 'ffi'\" to your Gemfile and run " \ + "\"bundle install\" to install the gem. (#{e.class}: #{e})" + end + module_function :validate_ffi! end end diff --git a/lib/mongo/crypt/auto_encrypter.rb b/lib/mongo/crypt/auto_encrypter.rb index d26db87566..a98f7d93a8 100644 --- a/lib/mongo/crypt/auto_encrypter.rb +++ b/lib/mongo/crypt/auto_encrypter.rb @@ -91,6 +91,7 @@ class AutoEncrypter # @raise [ ArgumentError ] If required options are missing or incorrectly # formatted. def initialize(options) + Crypt.validate_ffi! # Note that this call may eventually, via other method invocations, # create additional clients which have to be cleaned up. @options = set_default_options(options).freeze diff --git a/lib/mongo/crypt/explicit_encrypter.rb b/lib/mongo/crypt/explicit_encrypter.rb index a38c2705a1..946f97d7bd 100644 --- a/lib/mongo/crypt/explicit_encrypter.rb +++ b/lib/mongo/crypt/explicit_encrypter.rb @@ -36,6 +36,7 @@ class ExplicitEncrypter # should be hashes of TLS connection options. The options are equivalent # to TLS connection options of Mongo::Client. def initialize(key_vault_client, key_vault_namespace, kms_providers, kms_tls_options) + Crypt.validate_ffi! @crypt_handle = Handle.new( kms_providers, kms_tls_options, diff --git a/spec/mongo/crypt_spec.rb b/spec/mongo/crypt_spec.rb new file mode 100644 index 0000000000..c341c8ed5c --- /dev/null +++ b/spec/mongo/crypt_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mongo::Crypt do + describe '.validate_ffi!' do + context 'when ffi is available' do + context 'when ffi is loaded' do + it 'does not raise' do + expect do + described_class.validate_ffi! + end.not_to raise_error + end + end + end + # There is no reasonably simple way to test the path where ffi is not + # available. The ffi gem is a part of our standard test dependencies, so + # it's always available. So, we would need a dedicated configuration + # just to test this feature; it seems to be an overhead. + end +end From dc0865cf9aabadd1edb652446b0bd223079c8b37 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 13 Jun 2023 17:28:36 +0200 Subject: [PATCH 135/198] RUBY-3275 Update Atlas certificate (#2735) * RUBY-3275 Update Atlas certificate * Add a custom error message --- spec/integration/ocsp_verifier_spec.rb | 2 +- spec/support/certificates/atlas-ocsp-ca.crt | 134 ++++++------- spec/support/certificates/atlas-ocsp.crt | 206 ++++++++++---------- 3 files changed, 171 insertions(+), 171 deletions(-) diff --git a/spec/integration/ocsp_verifier_spec.rb b/spec/integration/ocsp_verifier_spec.rb index da9666adf9..7cdfaabc70 100644 --- a/spec/integration/ocsp_verifier_spec.rb +++ b/spec/integration/ocsp_verifier_spec.rb @@ -352,7 +352,7 @@ it 'verifies' do # TODO This test will fail if the certificate expires - verifier.verify.should be true + expect(verifier.verify).to be(true), "If atlas-ocsp certificates have expired, run spec/support/certificates/retrieve-atlas-cert to get a new ones" end end end diff --git a/spec/support/certificates/atlas-ocsp-ca.crt b/spec/support/certificates/atlas-ocsp-ca.crt index 06fc1a64a3..1712595dec 100644 --- a/spec/support/certificates/atlas-ocsp-ca.crt +++ b/spec/support/certificates/atlas-ocsp-ca.crt @@ -2,53 +2,53 @@ Certificate: Data: Version: 3 (0x2) Serial Number: - 06:d8:d9:04:d5:58:43:46:f6:8a:2f:a7:54:22:7e:c4 + 0c:f5:bd:06:2b:56:02:f4:7a:b8:50:2c:23:cc:f0:66 Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA + Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root G2 Validity - Not Before: Apr 14 00:00:00 2021 GMT - Not After : Apr 13 23:59:59 2031 GMT - Subject: C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1 + Not Before: Mar 30 00:00:00 2021 GMT + Not After : Mar 29 23:59:59 2031 GMT + Subject: C = US, O = DigiCert Inc, CN = DigiCert Global G2 TLS RSA SHA256 2020 CA1 Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: - 00:c1:4b:b3:65:47:70:bc:dd:4f:58:db:ec:9c:ed: - c3:66:e5:1f:31:13:54:ad:4a:66:46:1f:2c:0a:ec: - 64:07:e5:2e:dc:dc:b9:0a:20:ed:df:e3:c4:d0:9e: - 9a:a9:7a:1d:82:88:e5:11:56:db:1e:9f:58:c2:51: - e7:2c:34:0d:2e:d2:92:e1:56:cb:f1:79:5f:b3:bb: - 87:ca:25:03:7b:9a:52:41:66:10:60:4f:57:13:49: - f0:e8:37:67:83:df:e7:d3:4b:67:4c:22:51:a6:df: - 0e:99:10:ed:57:51:74:26:e2:7d:c7:ca:62:2e:13: - 1b:7f:23:88:25:53:6f:c1:34:58:00:8b:84:ff:f8: - be:a7:58:49:22:7b:96:ad:a2:88:9b:15:bc:a0:7c: - df:e9:51:a8:d5:b0:ed:37:e2:36:b4:82:4b:62:b5: - 49:9a:ec:c7:67:d6:e3:3e:f5:e3:d6:12:5e:44:f1: - bf:71:42:7d:58:84:03:80:b1:81:01:fa:f9:ca:32: - bb:b4:8e:27:87:27:c5:2b:74:d4:a8:d6:97:de:c3: - 64:f9:ca:ce:53:a2:56:bc:78:17:8e:49:03:29:ae: - fb:49:4f:a4:15:b9:ce:f2:5c:19:57:6d:6b:79:a7: - 2b:a2:27:20:13:b5:d0:3d:40:d3:21:30:07:93:ea: - 99:f5 + 00:cc:f7:10:62:4f:a6:bb:63:6f:ed:90:52:56:c5: + 6d:27:7b:7a:12:56:8a:f1:f4:f9:d6:e7:e1:8f:bd: + 95:ab:f2:60:41:15:70:db:12:00:fa:27:0a:b5:57: + 38:5b:7d:b2:51:93:71:95:0e:6a:41:94:5b:35:1b: + fa:7b:fa:bb:c5:be:24:30:fe:56:ef:c4:f3:7d:97: + e3:14:f5:14:4d:cb:a7:10:f2:16:ea:ab:22:f0:31: + 22:11:61:69:90:26:ba:78:d9:97:1f:e3:7d:66:ab: + 75:44:95:73:c8:ac:ff:ef:5d:0a:8a:59:43:e1:ac: + b2:3a:0f:f3:48:fc:d7:6b:37:c1:63:dc:de:46:d6: + db:45:fe:7d:23:fd:90:e8:51:07:1e:51:a3:5f:ed: + 49:46:54:7f:2c:88:c5:f4:13:9c:97:15:3c:03:e8: + a1:39:dc:69:0c:32:c1:af:16:57:4c:94:47:42:7c: + a2:c8:9c:7d:e6:d4:4d:54:af:42:99:a8:c1:04:c2: + 77:9c:d6:48:e4:ce:11:e0:2a:80:99:f0:43:70:cf: + 3f:76:6b:d1:4c:49:ab:24:5e:c2:0d:82:fd:46:a8: + ab:6c:93:cc:62:52:42:75:92:f8:9a:fa:5e:5e:b2: + b0:61:e5:1f:1f:b9:7f:09:98:e8:3d:fa:83:7f:47: + 69:a1 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Subject Key Identifier: - B7:6B:A2:EA:A8:AA:84:8C:79:EA:B4:DA:0F:98:B2:C5:95:76:B9:F4 + 74:85:80:C0:66:C7:DF:37:DE:CF:BD:29:37:AA:03:1D:BE:ED:CD:17 X509v3 Authority Key Identifier: - 03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55 + 4E:22:54:20:18:95:E6:E3:6E:E6:0F:FA:FA:B9:12:ED:06:17:8F:39 X509v3 Key Usage: critical Digital Signature, Certificate Sign, CRL Sign X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication Authority Information Access: OCSP - URI:http://ocsp.digicert.com - CA Issuers - URI:http://cacerts.digicert.com/DigiCertGlobalRootCA.crt + CA Issuers - URI:http://cacerts.digicert.com/DigiCertGlobalRootG2.crt X509v3 CRL Distribution Points: Full Name: - URI:http://crl3.digicert.com/DigiCertGlobalRootCA.crl + URI:http://crl3.digicert.com/DigiCertGlobalRootG2.crl X509v3 Certificate Policies: Policy: 2.16.840.1.114412.2.1 Policy: 2.23.140.1.1 @@ -57,47 +57,47 @@ Certificate: Policy: 2.23.140.1.2.3 Signature Algorithm: sha256WithRSAEncryption Signature Value: - 80:32:ce:5e:0b:dd:6e:5a:0d:0a:af:e1:d6:84:cb:c0:8e:fa: - 85:70:ed:da:5d:b3:0c:f7:2b:75:40:fe:85:0a:fa:f3:31:78: - b7:70:4b:1a:89:58:ba:80:bd:f3:6b:1d:e9:7e:cf:0b:ba:58: - 9c:59:d4:90:d3:fd:6c:fd:d0:98:6d:b7:71:82:5b:cf:6d:0b: - 5a:09:d0:7b:de:c4:43:d8:2a:a4:de:9e:41:26:5f:bb:8f:99: - cb:dd:ae:e1:a8:6f:9f:87:fe:74:b7:1f:1b:20:ab:b1:4f:c6: - f5:67:5d:5d:9b:3c:e9:ff:69:f7:61:6c:d6:d9:f3:fd:36:c6: - ab:03:88:76:d2:4b:2e:75:86:e3:fc:d8:55:7d:26:c2:11:77: - df:3e:02:b6:7c:f3:ab:7b:7a:86:36:6f:b8:f7:d8:93:71:cf: - 86:df:73:30:fa:7b:ab:ed:2a:59:c8:42:84:3b:11:17:1a:52: - f3:c9:0e:14:7d:a2:5b:72:67:ba:71:ed:57:47:66:c5:b8:02: - 4a:65:34:5e:8b:d0:2a:3c:20:9c:51:99:4c:e7:52:9e:f7:6b: - 11:2b:0d:92:7e:1d:e8:8a:eb:36:16:43:87:ea:2a:63:bf:75: - 3f:eb:de:c4:03:bb:0a:3c:f7:30:ef:eb:af:4c:fc:8b:36:10: - 73:3e:f3:a4 + 90:f1:70:cb:28:97:69:97:7c:74:fd:c0:fa:26:7b:53:ab:ad: + cd:65:fd:ba:9c:06:9c:8a:d7:5a:43:87:ed:4d:4c:56:5f:ad: + c1:c5:b5:05:20:2e:59:d1:ff:4a:f5:a0:2a:d8:b0:95:ad:c9: + 2e:4a:3b:d7:a7:f6:6f:88:29:fc:30:3f:24:84:bb:c3:b7:7b: + 93:07:2c:af:87:6b:76:33:ed:00:55:52:b2:59:9e:e4:b9:d0: + f3:df:e7:0f:fe:dd:f8:c4:b9:10:72:81:09:04:5f:cf:97:9e: + 2e:32:75:8e:cf:9a:58:d2:57:31:7e:37:01:81:b2:66:6d:29: + 1a:b1:66:09:6d:d1:6e:90:f4:b9:fa:2f:01:14:c5:5c:56:64: + 01:d9:7d:87:a8:38:53:9f:8b:5d:46:6d:5c:c6:27:84:81:d4: + 7e:8c:8c:a3:9b:52:e7:c6:88:ec:37:7c:2a:fb:f0:55:5a:38: + 72:10:d8:00:13:cf:4c:73:db:aa:37:35:a8:29:81:69:9c:76: + bc:de:18:7b:90:d4:ca:cf:ef:67:03:fd:04:5a:21:16:b1:ff: + ea:3f:df:dc:82:f5:eb:f4:59:92:23:0d:24:2a:95:25:4c:ca: + a1:91:e6:d4:b7:ac:87:74:b3:f1:6d:a3:99:db:f9:d5:bd:84: + 40:9f:07:98 -----BEGIN CERTIFICATE----- -MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBh +MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBS -U0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6a -qXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddn -g9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuW -raKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGB -Afr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21r -eacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB -/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAU -A95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG -CCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGG -GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2Nh -Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNV -HR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRH -bG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEB -MAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IB -AQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3z -ax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7h -qG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbC -EXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6 -ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697E -A7sKPPcw7+uvTPyLNhBzPvOk +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh +bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV +cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy +FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc +3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8 +osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT +zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud +EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G +A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG +CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG +NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH +Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t +L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC +ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG +9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t +wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS +slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R +bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4 +chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN +JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA== -----END CERTIFICATE----- \ No newline at end of file diff --git a/spec/support/certificates/atlas-ocsp.crt b/spec/support/certificates/atlas-ocsp.crt index 44ca65c775..de51c17cef 100644 --- a/spec/support/certificates/atlas-ocsp.crt +++ b/spec/support/certificates/atlas-ocsp.crt @@ -2,41 +2,41 @@ Certificate: Data: Version: 3 (0x2) Serial Number: - 0e:fb:8b:f4:73:3e:98:df:1d:48:6d:da:d2:08:ca:0d + 03:a8:39:03:e8:84:cd:24:1c:9c:b1:27:8c:0a:7a:9b Signature Algorithm: sha256WithRSAEncryption - Issuer: C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1 + Issuer: C = US, O = DigiCert Inc, CN = DigiCert Global G2 TLS RSA SHA256 2020 CA1 Validity - Not Before: May 5 00:00:00 2022 GMT - Not After : Jun 2 23:59:59 2023 GMT + Not Before: Jun 2 00:00:00 2023 GMT + Not After : Jun 4 23:59:59 2024 GMT Subject: C = US, ST = New York, L = New York, O = "MongoDB, Inc.", CN = *.mongodb-dev.net Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) Modulus: - 00:b3:40:eb:4a:26:af:07:a3:ea:58:c7:3b:50:5c: - 94:47:5a:0a:9d:86:b7:bb:03:9c:5a:4a:23:5e:7c: - b1:cd:fc:4f:56:d5:05:4e:ce:f8:f2:b1:55:09:13: - ff:cc:e6:a4:fb:41:2e:0c:8f:1a:a3:ea:4b:a3:51: - c6:f7:91:e6:47:49:ae:3b:94:df:68:80:68:2a:62: - a2:d1:42:5b:89:c1:70:2b:1c:a7:84:df:98:86:1a: - 69:eb:88:c4:51:4d:c9:7f:10:90:a0:0a:c5:7c:07: - 12:9e:c2:94:e1:88:2e:2d:21:94:a4:8a:27:1f:20: - 1b:6f:14:27:21:0c:4b:6f:72:5b:18:e7:75:7a:90: - 4c:a8:18:e7:4d:0f:ed:e4:03:f4:10:b7:65:23:9f: - b2:5f:0d:eb:85:3c:b0:34:d3:9a:2c:80:85:2e:53: - 35:19:44:03:13:b3:df:fb:0d:db:80:6f:12:60:1a: - b8:d6:25:0d:6d:23:fd:a6:d8:aa:dc:82:73:cc:d2: - 26:9b:71:50:9a:09:cf:ef:60:ba:d1:22:cb:fb:2f: - 69:a2:a6:c6:a9:30:77:1c:d7:bc:c2:6e:6d:e6:69: - 65:f3:91:3d:ee:04:f9:a0:41:5f:50:dc:75:15:4d: - 15:43:76:35:e9:97:65:7a:d8:3f:00:4d:44:29:7c: - 5d:25 + 00:df:91:d8:18:9b:41:9c:83:33:58:e9:c2:42:00: + 5a:a5:c0:61:e8:df:39:20:46:33:ae:87:34:c4:13: + 89:d2:c9:4c:eb:6f:a5:cd:dc:31:07:b4:cf:53:74: + 9b:3c:32:e8:df:4b:64:88:a2:03:87:e9:27:f9:7b: + 57:ca:be:7b:51:0f:f2:b1:78:45:0e:5f:06:e9:0a: + c9:fe:67:d0:19:3c:8c:41:01:5f:b7:66:a0:a4:3b: + 54:18:c2:5b:5e:b1:d7:9e:6a:78:f3:bd:24:1d:fc: + ce:43:d2:74:60:83:f4:f3:55:a4:dd:82:f0:e9:f1: + 3d:ff:d1:b6:32:0f:c8:e4:aa:95:bf:52:33:8f:2f: + be:28:ea:ea:cf:a7:13:65:7e:3c:61:2c:38:a0:95: + 45:b8:2d:7e:20:33:38:36:fd:6f:c8:a8:e2:ad:5b: + 3c:91:95:d9:ec:0b:08:7d:81:25:0c:b9:40:6b:97: + 8a:d1:c4:f9:4d:9b:45:57:03:b9:5c:03:90:d0:68: + 29:50:12:87:59:48:f0:19:c3:4d:42:3e:80:c3:4e: + 7e:30:e1:bb:fe:f8:30:55:83:15:ca:af:12:13:3a: + da:e9:91:a3:10:6d:fc:b6:64:5d:89:a3:b4:18:9a: + 7e:ab:90:dd:11:fa:db:af:98:12:58:3c:8f:47:22: + f2:53 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Authority Key Identifier: - B7:6B:A2:EA:A8:AA:84:8C:79:EA:B4:DA:0F:98:B2:C5:95:76:B9:F4 + 74:85:80:C0:66:C7:DF:37:DE:CF:BD:29:37:AA:03:1D:BE:ED:CD:17 X509v3 Subject Key Identifier: - 22:7C:2F:C7:F4:D6:75:0E:A1:02:32:21:1A:A6:29:25:46:48:B8:6A + 97:FB:A6:29:30:AE:9D:F0:D9:85:05:12:AA:D4:C1:D9:EC:0F:5B:CE X509v3 Subject Alternative Name: DNS:*.mongodb-dev.net, DNS:mongodb-dev.net X509v3 Key Usage: critical @@ -45,108 +45,108 @@ Certificate: TLS Web Server Authentication, TLS Web Client Authentication X509v3 CRL Distribution Points: Full Name: - URI:http://crl3.digicert.com/DigiCertTLSRSASHA2562020CA1-4.crl + URI:http://crl3.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crl Full Name: - URI:http://crl4.digicert.com/DigiCertTLSRSASHA2562020CA1-4.crl + URI:http://crl4.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crl X509v3 Certificate Policies: Policy: 2.23.140.1.2.2 CPS: http://www.digicert.com/CPS Authority Information Access: OCSP - URI:http://ocsp.digicert.com - CA Issuers - URI:http://cacerts.digicert.com/DigiCertTLSRSASHA2562020CA1-1.crt + CA Issuers - URI:http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt X509v3 Basic Constraints: CA:FALSE CT Precertificate SCTs: Signed Certificate Timestamp: Version : v1 (0x0) - Log ID : E8:3E:D0:DA:3E:F5:06:35:32:E7:57:28:BC:89:6B:C9: - 03:D3:CB:D1:11:6B:EC:EB:69:E1:77:7D:6D:06:BD:6E - Timestamp : May 5 16:12:54.726 2022 GMT + Log ID : EE:CD:D0:64:D5:DB:1A:CE:C5:5C:B7:9D:B4:CD:13:A2: + 32:87:46:7C:BC:EC:DE:C3:51:48:59:46:71:1F:B5:9B + Timestamp : Jun 2 08:44:16.090 2023 GMT Extensions: none Signature : ecdsa-with-SHA256 - 30:45:02:21:00:C4:D8:1A:00:3C:BD:E5:E1:13:B2:5D: - DA:7F:69:A9:4C:83:AB:CC:C2:00:9F:FB:98:09:44:5F: - 93:0C:92:CD:F7:02:20:2D:E2:D6:A4:3B:61:06:25:1E: - 22:2F:5E:2A:D4:76:0D:FE:12:E9:91:65:26:69:25:6C: - 53:E4:C3:2F:4B:89:AF + 30:46:02:21:00:88:AC:04:93:84:2C:2C:99:4F:B8:C4: + 06:B5:7F:9B:92:D3:51:25:72:1D:DB:12:D5:C0:AA:8F: + D1:97:39:EF:4C:02:21:00:96:B1:11:40:0F:BA:03:28: + 88:BF:0C:DF:4B:4A:68:67:05:97:7E:F6:BD:6F:F3:0D: + D8:9C:EB:90:75:FF:79:AB Signed Certificate Timestamp: Version : v1 (0x0) - Log ID : 35:CF:19:1B:BF:B1:6C:57:BF:0F:AD:4C:6D:42:CB:BB: - B6:27:20:26:51:EA:3F:E1:2A:EF:A8:03:C3:3B:D6:4C - Timestamp : May 5 16:12:54.786 2022 GMT + Log ID : 48:B0:E3:6B:DA:A6:47:34:0F:E5:6A:02:FA:9D:30:EB: + 1C:52:01:CB:56:DD:2C:81:D9:BB:BF:AB:39:D8:84:73 + Timestamp : Jun 2 08:44:16.066 2023 GMT Extensions: none Signature : ecdsa-with-SHA256 - 30:45:02:20:46:F0:9E:AE:32:A7:A7:CA:22:C6:64:EE: - 7F:9F:C6:EA:3C:BE:3B:2F:E2:09:C0:ED:A9:E1:EE:16: - F9:F1:6C:1E:02:21:00:BD:1A:42:73:A5:CE:E6:8A:CE: - BC:4C:3E:42:D4:0B:FC:89:3B:D5:1E:29:E8:38:F7:44: - 5B:22:9A:FA:1D:B3:57 + 30:45:02:21:00:B0:C8:4E:75:71:84:DE:71:8F:D0:27: + 71:6C:4D:B1:E8:20:A3:54:AF:27:A8:A2:FA:AC:46:43: + 4A:08:C1:A9:94:02:20:1A:E1:63:79:E6:DB:4A:98:72: + FF:BD:C4:A2:D8:F5:11:36:4C:95:6E:68:B4:CF:E7:35: + 84:BB:AC:A2:E6:D0:A0 Signed Certificate Timestamp: Version : v1 (0x0) - Log ID : B7:3E:FB:24:DF:9C:4D:BA:75:F2:39:C5:BA:58:F4:6C: - 5D:FC:42:CF:7A:9F:35:C4:9E:1D:09:81:25:ED:B4:99 - Timestamp : May 5 16:12:54.779 2022 GMT + Log ID : DA:B6:BF:6B:3F:B5:B6:22:9F:9B:C2:BB:5C:6B:E8:70: + 91:71:6C:BB:51:84:85:34:BD:A4:3D:30:48:D7:FB:AB + Timestamp : Jun 2 08:44:16.030 2023 GMT Extensions: none Signature : ecdsa-with-SHA256 - 30:45:02:20:2F:72:9B:CC:88:04:F1:3B:8B:BC:01:85: - B2:16:B4:AF:4A:CB:45:E8:0B:29:D1:77:6C:2C:56:0C: - 8D:1C:61:7C:02:21:00:A4:67:AC:08:40:83:82:9E:22: - 75:3A:92:91:1E:12:23:01:B9:04:3B:A3:60:5F:E8:D1: - 59:4E:88:DB:C5:DE:DD + 30:44:02:20:51:59:38:3B:FF:76:EC:A2:3E:15:4B:60: + 23:84:20:88:66:C3:64:7C:94:51:B9:1A:BD:87:15:73: + 43:F5:96:BA:02:20:22:F3:7B:75:F4:58:58:C7:48:18: + 85:22:17:FC:ED:07:A2:8B:A0:07:0E:32:0F:16:9C:8B: + E5:AA:6B:86:86:6B Signature Algorithm: sha256WithRSAEncryption Signature Value: - 33:bb:fa:5f:59:e5:d0:5c:a1:22:4e:73:c8:35:e4:d7:8c:28: - 67:b2:85:b0:1b:0c:e4:ec:aa:a7:49:09:e7:52:63:e5:8a:51: - a9:de:b8:35:74:66:0f:66:fa:b4:91:1f:b0:3a:5e:0a:21:0d: - a2:73:5b:1f:2c:fa:cd:59:72:85:8c:db:95:33:66:64:a2:e7: - 6f:0d:9e:cf:a7:e3:de:82:7a:24:55:56:f8:b8:c0:95:0d:ba: - 07:01:fe:ce:35:48:2f:23:2d:29:7b:6b:00:e6:f7:2c:31:6f: - 9c:67:35:90:f0:65:9f:41:f9:bf:ad:2c:e9:8e:f0:c4:cf:8c: - 5d:02:c4:8e:22:7f:62:bb:62:21:cf:9d:59:7b:96:4e:c4:7d: - ca:a2:6f:37:66:34:88:ac:a3:c1:6e:95:ec:e3:3a:07:27:6d: - 31:b7:38:70:ce:93:42:86:20:3b:f8:d8:f9:ef:a9:9e:43:4f: - ed:9b:4b:c7:90:5b:d1:9b:16:66:8f:42:11:8a:97:bb:c7:e1: - b5:67:f5:a1:f4:7b:8b:f0:88:a8:5f:39:83:4c:2e:3d:e9:8f: - 34:b8:fb:e8:1e:e2:0f:90:02:7c:ad:c7:9e:00:c2:fa:0a:41: - 43:61:e2:2b:6d:f8:b7:fa:70:71:04:f3:2c:7f:c0:16:7a:6b: - a1:34:45:c0 + be:97:77:21:55:1d:c7:18:5f:52:de:a8:85:a5:d2:ee:48:29: + b3:46:2d:31:02:12:5b:88:1e:09:ac:f4:7e:fc:4d:e4:e8:c9: + 56:7e:de:1e:dc:53:1f:27:1b:dd:07:68:39:1c:2e:91:86:20: + 2a:f0:9a:d1:ff:c4:b5:c2:85:a7:ec:9c:b8:62:31:aa:5b:69: + 8e:37:d4:cc:ec:7b:86:86:b2:a9:6d:60:8b:70:44:ca:72:7a: + d9:a8:54:96:f8:5a:e5:2b:2a:48:5d:4f:53:fb:b3:c9:67:70: + a3:a6:d5:be:6d:99:11:51:89:31:e3:b1:43:54:e0:7c:cd:96: + fd:2d:12:12:eb:a3:8e:b4:12:25:23:bf:a6:5d:70:c5:3f:8c: + 9f:0e:5d:38:d0:4b:aa:67:30:d7:c3:47:76:20:bd:1a:55:2a: + b2:df:37:92:67:88:65:11:15:62:a8:08:3f:93:ee:c1:b1:79: + 33:03:03:0b:46:d4:5f:d2:82:31:77:6c:42:12:89:f4:f8:af: + 32:ab:45:59:91:bf:82:44:69:d2:49:7f:1b:f3:49:28:37:85: + 74:3c:4d:92:1b:56:30:4c:69:79:5d:fc:4d:a6:7b:c6:e7:f5: + 34:e6:dd:c0:25:f8:69:b2:86:3a:44:a9:41:ba:34:cd:fe:aa: + 20:d2:57:ad -----BEGIN CERTIFICATE----- -MIIGxDCCBaygAwIBAgIQDvuL9HM+mN8dSG3a0gjKDTANBgkqhkiG9w0BAQsFADBP -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE -aWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMjA1MDUwMDAwMDBa -Fw0yMzA2MDIyMzU5NTlaMGcxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhOZXcgWW9y -azERMA8GA1UEBxMITmV3IFlvcmsxFjAUBgNVBAoTDU1vbmdvREIsIEluYy4xGjAY -BgNVBAMMESoubW9uZ29kYi1kZXYubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAs0DrSiavB6PqWMc7UFyUR1oKnYa3uwOcWkojXnyxzfxPVtUFTs74 -8rFVCRP/zOak+0EuDI8ao+pLo1HG95HmR0muO5TfaIBoKmKi0UJbicFwKxynhN+Y -hhpp64jEUU3JfxCQoArFfAcSnsKU4YguLSGUpIonHyAbbxQnIQxLb3JbGOd1epBM -qBjnTQ/t5AP0ELdlI5+yXw3rhTywNNOaLICFLlM1GUQDE7Pf+w3bgG8SYBq41iUN -bSP9ptiq3IJzzNImm3FQmgnP72C60SLL+y9poqbGqTB3HNe8wm5t5mll85E97gT5 -oEFfUNx1FU0VQ3Y16Zdletg/AE1EKXxdJQIDAQABo4IDgjCCA34wHwYDVR0jBBgw -FoAUt2ui6qiqhIx56rTaD5iyxZV2ufQwHQYDVR0OBBYEFCJ8L8f01nUOoQIyIRqm -KSVGSLhqMC0GA1UdEQQmMCSCESoubW9uZ29kYi1kZXYubmV0gg9tb25nb2RiLWRl -di5uZXQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF -BQcDAjCBjwYDVR0fBIGHMIGEMECgPqA8hjpodHRwOi8vY3JsMy5kaWdpY2VydC5j -b20vRGlnaUNlcnRUTFNSU0FTSEEyNTYyMDIwQ0ExLTQuY3JsMECgPqA8hjpodHRw -Oi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNSU0FTSEEyNTYyMDIwQ0Ex -LTQuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6 -Ly93d3cuZGlnaWNlcnQuY29tL0NQUzB/BggrBgEFBQcBAQRzMHEwJAYIKwYBBQUH -MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBJBggrBgEFBQcwAoY9aHR0cDov -L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTUlNBU0hBMjU2MjAyMENB -MS0xLmNydDAJBgNVHRMEAjAAMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgDo -PtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYCU/uZGAAAEAwBHMEUC -IQDE2BoAPL3l4ROyXdp/aalMg6vMwgCf+5gJRF+TDJLN9wIgLeLWpDthBiUeIi9e -KtR2Df4S6ZFlJmklbFPkwy9Lia8AdgA1zxkbv7FsV78PrUxtQsu7ticgJlHqP+Eq -76gDwzvWTAAAAYCU/uaCAAAEAwBHMEUCIEbwnq4yp6fKIsZk7n+fxuo8vjsv4gnA -7anh7hb58WweAiEAvRpCc6XO5orOvEw+QtQL/Ik71R4p6Dj3RFsimvods1cAdgC3 -Pvsk35xNunXyOcW6WPRsXfxCz3qfNcSeHQmBJe20mQAAAYCU/uZ7AAAEAwBHMEUC -IC9ym8yIBPE7i7wBhbIWtK9Ky0XoCynRd2wsVgyNHGF8AiEApGesCECDgp4idTqS -kR4SIwG5BDujYF/o0VlOiNvF3t0wDQYJKoZIhvcNAQELBQADggEBADO7+l9Z5dBc -oSJOc8g15NeMKGeyhbAbDOTsqqdJCedSY+WKUaneuDV0Zg9m+rSRH7A6XgohDaJz -Wx8s+s1ZcoWM25UzZmSi528Nns+n496CeiRVVvi4wJUNugcB/s41SC8jLSl7awDm -9ywxb5xnNZDwZZ9B+b+tLOmO8MTPjF0CxI4if2K7YiHPnVl7lk7EfcqibzdmNIis -o8FulezjOgcnbTG3OHDOk0KGIDv42PnvqZ5DT+2bS8eQW9GbFmaPQhGKl7vH4bVn -9aH0e4vwiKhfOYNMLj3pjzS4++ge4g+QAnytx54AwvoKQUNh4itt+Lf6cHEE8yx/ -wBZ6a6E0RcA= +MIIG5zCCBc+gAwIBAgIQA6g5A+iEzSQcnLEnjAp6mzANBgkqhkiG9w0BAQsFADBZ +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTMwMQYDVQQDEypE +aWdpQ2VydCBHbG9iYWwgRzIgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTEwHhcNMjMw +NjAyMDAwMDAwWhcNMjQwNjA0MjM1OTU5WjBnMQswCQYDVQQGEwJVUzERMA8GA1UE +CBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYwFAYDVQQKEw1Nb25nb0RC +LCBJbmMuMRowGAYDVQQDDBEqLm1vbmdvZGItZGV2Lm5ldDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAN+R2BibQZyDM1jpwkIAWqXAYejfOSBGM66HNMQT +idLJTOtvpc3cMQe0z1N0mzwy6N9LZIiiA4fpJ/l7V8q+e1EP8rF4RQ5fBukKyf5n +0Bk8jEEBX7dmoKQ7VBjCW16x155qePO9JB38zkPSdGCD9PNVpN2C8OnxPf/RtjIP +yOSqlb9SM48vvijq6s+nE2V+PGEsOKCVRbgtfiAzODb9b8io4q1bPJGV2ewLCH2B +JQy5QGuXitHE+U2bRVcDuVwDkNBoKVASh1lI8BnDTUI+gMNOfjDhu/74MFWDFcqv +EhM62umRoxBt/LZkXYmjtBiafquQ3RH626+YElg8j0ci8lMCAwEAAaOCA5swggOX +MB8GA1UdIwQYMBaAFHSFgMBmx9833s+9KTeqAx2+7c0XMB0GA1UdDgQWBBSX+6Yp +MK6d8NmFBRKq1MHZ7A9bzjAtBgNVHREEJjAkghEqLm1vbmdvZGItZGV2Lm5ldIIP +bW9uZ29kYi1kZXYubmV0MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwgZ8GA1UdHwSBlzCBlDBIoEagRIZCaHR0cDovL2NybDMu +ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsRzJUTFNSU0FTSEEyNTYyMDIwQ0Ex +LTEuY3JsMEigRqBEhkJodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRH +bG9iYWxHMlRMU1JTQVNIQTI1NjIwMjBDQTEtMS5jcmwwPgYDVR0gBDcwNTAzBgZn +gQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BT +MIGHBggrBgEFBQcBAQR7MHkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj +ZXJ0LmNvbTBRBggrBgEFBQcwAoZFaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t +L0RpZ2lDZXJ0R2xvYmFsRzJUTFNSU0FTSEEyNTYyMDIwQ0ExLTEuY3J0MAkGA1Ud +EwQCMAAwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB3AO7N0GTV2xrOxVy3nbTN +E6Iyh0Z8vOzew1FIWUZxH7WbAAABiHtHY1oAAAQDAEgwRgIhAIisBJOELCyZT7jE +BrV/m5LTUSVyHdsS1cCqj9GXOe9MAiEAlrERQA+6AyiIvwzfS0poZwWXfva9b/MN +2JzrkHX/easAdgBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZu7+rOdiEcwAAAYh7 +R2NCAAAEAwBHMEUCIQCwyE51cYTecY/QJ3FsTbHoIKNUryeoovqsRkNKCMGplAIg +GuFjeebbSphy/73Eotj1ETZMlW5otM/nNYS7rKLm0KAAdQDatr9rP7W2Ip+bwrtc +a+hwkXFsu1GEhTS9pD0wSNf7qwAAAYh7R2MeAAAEAwBGMEQCIFFZODv/duyiPhVL +YCOEIIhmw2R8lFG5Gr2HFXND9Za6AiAi83t19FhYx0gYhSIX/O0HoougBw4yDxac +i+Wqa4aGazANBgkqhkiG9w0BAQsFAAOCAQEAvpd3IVUdxxhfUt6ohaXS7kgps0Yt +MQISW4geCaz0fvxN5OjJVn7eHtxTHycb3QdoORwukYYgKvCa0f/EtcKFp+ycuGIx +qltpjjfUzOx7hoayqW1gi3BEynJ62ahUlvha5SsqSF1PU/uzyWdwo6bVvm2ZEVGJ +MeOxQ1TgfM2W/S0SEuujjrQSJSO/pl1wxT+Mnw5dONBLqmcw18NHdiC9GlUqst83 +kmeIZREVYqgIP5PuwbF5MwMDC0bUX9KCMXdsQhKJ9PivMqtFWZG/gkRp0kl/G/NJ +KDeFdDxNkhtWMExpeV38TaZ7xuf1NObdwCX4abKGOkSpQbo0zf6qINJXrQ== -----END CERTIFICATE----- \ No newline at end of file From adda483ff6e67a0a02881a8b4db0c2fc0a41bd23 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 14 Jun 2023 09:22:21 +0200 Subject: [PATCH 136/198] RUBY-3225 New spec test for timeseries (#2733) --- .../timeseries-collection.yml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/spec/spec_tests/data/collection_management/timeseries-collection.yml b/spec/spec_tests/data/collection_management/timeseries-collection.yml index cbc09b34ca..00f22709b0 100644 --- a/spec/spec_tests/data/collection_management/timeseries-collection.yml +++ b/spec/spec_tests/data/collection_management/timeseries-collection.yml @@ -127,3 +127,38 @@ tests: filter: {} sort: { time: 1 } databaseName: *database0Name + + - description: "createCollection with bucketing options" + runOnRequirements: + - minServerVersion: "7.0" + operations: + - name: dropCollection + object: *database0 + arguments: + collection: *collection0Name + - name: createCollection + object: *database0 + arguments: + collection: *collection0Name + timeseries: ×eries1 + timeField: "time" + bucketMaxSpanSeconds: 3600 + bucketRoundingSeconds: 3600 + - name: assertCollectionExists + object: testRunner + arguments: + databaseName: *database0Name + collectionName: *collection0Name + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + drop: *collection0Name + databaseName: *database0Name + - commandStartedEvent: + command: + create: *collection0Name + timeseries: *timeseries1 + databaseName: *database0Name + From bc9da14435c66c1a1db1625ef300d3c85561cc3a Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 15 Jun 2023 16:31:58 +0200 Subject: [PATCH 137/198] RUBY-3216 Fix wildcard projection spec (#2734) * RUBY-3225 New spec test for timeseries * RUBY-3216 Fix tests for wildcard projection * Tweak spec * Bump constraint --- spec/mongo/index/view_spec.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/mongo/index/view_spec.rb b/spec/mongo/index/view_spec.rb index 24cf8897f6..1bab5470f5 100644 --- a/spec/mongo/index/view_spec.rb +++ b/spec/mongo/index/view_spec.rb @@ -956,12 +956,11 @@ end end - context 'on server versions >= 5.0' do - min_server_fcv '5.0' + context 'on server versions > 5.3' do + min_server_fcv '5.4' it 'passes wildcardProjection correctly' do - skip 'https://jira.mongodb.org/browse/RUBY-3216' - expect(indexes[:wildcardProjection]).to eq({ '_id' => false, 'rating' => true }) + expect(indexes[:wildcardProjection]).to eq({ 'rating' => 1 }) end end end From c1835c7431f7d59df6dae1daf8f9c687f3875965 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 20 Jun 2023 08:48:34 -0600 Subject: [PATCH 138/198] RUBY-3262 enable QEv2 tests on Serverless (#2731) * RUBY-3262 enable QEv2 tests on Serverless * also address RUBY-3270 this is just removing a bandaid I added a few weeks ago to work around this very issue * add CSE specs to serverless also some incidental cleanup of the version parsing code, which turned out to be unnecessary * bump spec/shared for better version parsing * Check wire version on connection --------- Co-authored-by: Dmitry Rybakov --- .evergreen/run-tests-serverless.sh | 4 +- lib/mongo/collection/queryable_encryption.rb | 21 +++++---- lib/mongo/crypt/binding.rb | 44 ++++++++++-------- spec/mongo/crypt/binding/version_spec.rb | 22 +++++++-- spec/shared | 2 +- .../fle2v2-BypassQueryAnalysis.yml | 1 - .../client_side_encryption/fle2v2-Compact.yml | 1 - .../fle2v2-CreateCollection-OldServer.yml | 22 +++++++++ .../fle2v2-CreateCollection.yml | 7 +-- .../fle2v2-DecryptExistingData.yml | 1 - .../client_side_encryption/fle2v2-Delete.yml | 1 - ...-EncryptedFields-vs-EncryptedFieldsMap.yml | 1 - .../fle2v2-EncryptedFields-vs-jsonSchema.yml | 1 - .../fle2v2-EncryptedFieldsMap-defaults.yml | 1 - .../fle2v2-FindOneAndUpdate.yml | 1 - .../fle2v2-InsertFind-Indexed.yml | 1 - .../fle2v2-InsertFind-Unindexed.yml | 1 - .../fle2v2-MissingKey.yml | 1 - .../fle2v2-NoEncryption.yml | 1 - .../fle2v2-Range-Date-Aggregate.yml | 1 - .../fle2v2-Range-Date-Correctness.yml | 1 - .../fle2v2-Range-Date-Delete.yml | 1 - .../fle2v2-Range-Date-FindOneAndUpdate.yml | 1 - .../fle2v2-Range-Date-InsertFind.yml | 1 - .../fle2v2-Range-Date-Update.yml | 1 - .../fle2v2-Range-Decimal-Aggregate.yml | 19 ++++---- .../fle2v2-Range-Decimal-Correctness.yml | 45 +++++++++---------- .../fle2v2-Range-Decimal-Delete.yml | 17 ++++--- .../fle2v2-Range-Decimal-FindOneAndUpdate.yml | 23 +++++----- .../fle2v2-Range-Decimal-InsertFind.yml | 19 ++++---- .../fle2v2-Range-Decimal-Update.yml | 23 +++++----- ...le2v2-Range-DecimalPrecision-Aggregate.yml | 1 - ...2v2-Range-DecimalPrecision-Correctness.yml | 1 - .../fle2v2-Range-DecimalPrecision-Delete.yml | 1 - ...ange-DecimalPrecision-FindOneAndUpdate.yml | 1 - ...e2v2-Range-DecimalPrecision-InsertFind.yml | 1 - .../fle2v2-Range-DecimalPrecision-Update.yml | 1 - .../fle2v2-Range-Double-Aggregate.yml | 19 ++++---- .../fle2v2-Range-Double-Correctness.yml | 45 +++++++++---------- .../fle2v2-Range-Double-Delete.yml | 17 ++++--- .../fle2v2-Range-Double-FindOneAndUpdate.yml | 23 +++++----- .../fle2v2-Range-Double-InsertFind.yml | 19 ++++---- .../fle2v2-Range-Double-Update.yml | 23 +++++----- ...fle2v2-Range-DoublePrecision-Aggregate.yml | 1 - ...e2v2-Range-DoublePrecision-Correctness.yml | 1 - .../fle2v2-Range-DoublePrecision-Delete.yml | 1 - ...Range-DoublePrecision-FindOneAndUpdate.yml | 1 - ...le2v2-Range-DoublePrecision-InsertFind.yml | 1 - .../fle2v2-Range-DoublePrecision-Update.yml | 1 - .../fle2v2-Range-Int-Aggregate.yml | 1 - .../fle2v2-Range-Int-Correctness.yml | 1 - .../fle2v2-Range-Int-Delete.yml | 1 - .../fle2v2-Range-Int-FindOneAndUpdate.yml | 1 - .../fle2v2-Range-Int-InsertFind.yml | 1 - .../fle2v2-Range-Int-Update.yml | 1 - .../fle2v2-Range-Long-Aggregate.yml | 1 - .../fle2v2-Range-Long-Correctness.yml | 1 - .../fle2v2-Range-Long-Delete.yml | 1 - .../fle2v2-Range-Long-FindOneAndUpdate.yml | 1 - .../fle2v2-Range-Long-InsertFind.yml | 1 - .../fle2v2-Range-Long-Update.yml | 1 - .../fle2v2-Range-WrongType.yml | 3 +- .../client_side_encryption/fle2v2-Update.yml | 1 - ...2v2-validatorAndPartialFieldExpression.yml | 1 - spec/support/utils.rb | 5 --- 65 files changed, 223 insertions(+), 243 deletions(-) diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index 91171c1133..4bcd983ade 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -76,6 +76,7 @@ cd - echo "Running specs" bundle exec rspec \ + spec/spec_tests/client_side_encryption_spec.rb \ spec/spec_tests/crud_spec.rb \ spec/spec_tests/retryable_reads_spec.rb \ spec/spec_tests/retryable_writes_spec.rb \ @@ -91,9 +92,6 @@ bundle exec rspec \ spec/spec_tests/sessions_unified_spec.rb \ spec/spec_tests/transactions_unified_spec.rb -# https://jira.mongodb.org/browse/RUBY-3249 -# Add when fixed: spec/spec_tests/client_side_encryption_spec.rb \ - kill_jruby # Terminate all kmip servers... and whatever else happens to be running # that is a python script. diff --git a/lib/mongo/collection/queryable_encryption.rb b/lib/mongo/collection/queryable_encryption.rb index 2bb93197e3..8beb17c45f 100644 --- a/lib/mongo/collection/queryable_encryption.rb +++ b/lib/mongo/collection/queryable_encryption.rb @@ -36,12 +36,14 @@ def maybe_create_qe_collections(encrypted_fields, client, session) encrypted_fields = encrypted_fields_from(encrypted_fields) return yield if encrypted_fields.empty? - check_wire_version! - - emm_collections(encrypted_fields).each do |coll| - context = Operation::Context.new(client: client, session: session) - create_operation_for(coll) - .execute(next_primary(nil, session), context: context) + server = next_primary(nil, session) + context = Operation::Context.new(client: client, session: session) + server.with_connection do |connection| + check_wire_version!(connection) + emm_collections(encrypted_fields).each do |coll| + create_operation_for(coll) + .execute_with_connection(connection, context: context) + end end yield(encrypted_fields).tap do |result| @@ -99,10 +101,13 @@ def emm_collections(encrypted_fields) # Creating encrypted collections is only supported on 7.0.0 and later # (wire version 21+). # + # @param [ Mongo::Connection ] connection The connection to check + # the wire version of. + # # @raise [ Mongo::Error ] if the wire version is not # recent enough - def check_wire_version! - return unless next_primary.max_wire_version < QE2_MIN_WIRE_VERSION + def check_wire_version!(connection) + return unless connection.description.max_wire_version < QE2_MIN_WIRE_VERSION raise Mongo::Error, 'Driver support of Queryable Encryption is incompatible with server. ' \ diff --git a/lib/mongo/crypt/binding.rb b/lib/mongo/crypt/binding.rb index f3b8e8415a..906f76facc 100644 --- a/lib/mongo/crypt/binding.rb +++ b/lib/mongo/crypt/binding.rb @@ -94,34 +94,42 @@ class Binding # @return [ String ] A version string for libmongocrypt. attach_function :mongocrypt_version, [:pointer], :string - # Validates if provided version of libmongocrypt is valid, i.e. equal or - # greater than minimum required version. Raises a LoadError if not. + # Given a string representing a version number, parses it into a + # Gem::Version object. This handles the case where the string is not + # in a format supported by Gem::Version by doing some custom parsing. # - # @param [ String ] lmc_version String representing libmongocrypt version. + # @param [ String ] version String representing a version number. # - # @raise [ LoadError ] if given version is lesser than minimum required version. + # @return [ Gem::Version ] the version number + # + # @raise [ ArgumentError ] if the string cannot be parsed. # # @api private - def self.validate_version(lmc_version) - if (actual_version = Gem::Version.new(lmc_version)) < MIN_LIBMONGOCRYPT_VERSION - raise LoadError, "libmongocrypt version #{MIN_LIBMONGOCRYPT_VERSION} or above is required, " + - "but version #{actual_version} was found." - end - rescue ArgumentError => e - # Some lmc versions cannot be parsed with Gem::Version class, - # so we fall back to regex. - match = lmc_version.match(/\A(?\d+)\.(?\d+)\.(?\d+)?(-[A-Za-z\+\d]+)?\z/) - if match.nil? - raise ArgumentError.new("Malformed version number string #{lmc_version}") - end - actual_version = Gem::Version.new( + def self.parse_version(version) + Gem::Version.new(version) + rescue ArgumentError + match = version.match(/\A(?\d+)\.(?\d+)\.(?\d+)?(-[A-Za-z\+\d]+)?\z/) + raise ArgumentError.new("Malformed version number string #{version}") if match.nil? + + Gem::Version.new( [ match[:major], match[:minor], match[:patch] ].join('.') ) - if actual_version < MIN_LIBMONGOCRYPT_VERSION + end + + # Validates if provided version of libmongocrypt is valid, i.e. equal or + # greater than minimum required version. Raises a LoadError if not. + # + # @param [ String ] lmc_version String representing libmongocrypt version. + # + # @raise [ LoadError ] if given version is lesser than minimum required version. + # + # @api private + def self.validate_version(lmc_version) + if (actual_version = parse_version(lmc_version)) < MIN_LIBMONGOCRYPT_VERSION raise LoadError, "libmongocrypt version #{MIN_LIBMONGOCRYPT_VERSION} or above is required, " + "but version #{actual_version} was found." end diff --git a/spec/mongo/crypt/binding/version_spec.rb b/spec/mongo/crypt/binding/version_spec.rb index fb55a2e09e..51fe649288 100644 --- a/spec/mongo/crypt/binding/version_spec.rb +++ b/spec/mongo/crypt/binding/version_spec.rb @@ -43,10 +43,24 @@ end context 'when in a non-parsable format' do - it 'does not raise ArgumentError' do - expect do - Mongo::Crypt::Binding.validate_version("1.5.3-dev+20220730git8f8675fa11") - end.not_to raise_error(ArgumentError, /Malformed version number string/) + let(:base_version) { Mongo::Crypt::Binding::MIN_LIBMONGOCRYPT_VERSION.to_s } + + shared_examples_for 'non-standard version format' do + it 'does not raise an exception' do + expect do + Mongo::Crypt::Binding.validate_version(version) + end.not_to raise_error + end + end + + context 'when the version is MAJOR.MINOR.PATH-dev+datecommit' do + let(:version) { "#{base_version}-dev+20220730git8f8675fa11" } + include_examples 'non-standard version format' + end + + context 'when the version is MAJOR.MINOR.PATH-date+commit' do + let(:version) { "#{base_version}-20230601+git9b07846bef" } + include_examples 'non-standard version format' end end end diff --git a/spec/shared b/spec/shared index 907504ce6b..ce1f8945bd 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit 907504ce6b61a56c38caeeb01fa3d21a86c19a5d +Subproject commit ce1f8945bd61e614ca4cc7a24d95d16071c46a59 diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-BypassQueryAnalysis.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-BypassQueryAnalysis.yml index 1ef8197c29..51e7a56758 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-BypassQueryAnalysis.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-BypassQueryAnalysis.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Compact.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Compact.yml index 8e88cc5203..2398a5cd44 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Compact.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Compact.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml index f55001a429..5cc6ead0f6 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml @@ -37,3 +37,25 @@ tests: collection: "encryptedCollection" result: errorContains: "Driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption." + # Assert no collections were created. + - name: assertCollectionNotExists + object: testRunner + arguments: + database: *database_name + collection: &esc_collection_name "enxcol_.encryptedCollection.esc" + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists + object: testRunner + arguments: + database: *database_name + collection: &ecc_collection_name "enxcol_.encryptedCollection.ecc" + - name: assertCollectionNotExists + object: testRunner + arguments: + database: *database_name + collection: &ecoc_collection_name "enxcol_.encryptedCollection.ecoc" + - name: assertCollectionNotExists + object: testRunner + arguments: + database: *database_name + collection: encryptedCollection diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml index ea33865a21..43dbccfc37 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] @@ -101,10 +100,6 @@ tests: command: create: *encrypted_collection_name encryptedFields: &encrypted_fields_expectation { - # Expect state collections are not included in the encryptedFields sent to the server. - "escCollection": null, - "ecocCollection": null, - "eccCollection": null, "fields": [ { "path": "firstName", @@ -939,4 +934,4 @@ tests: collection: *encrypted_collection_name result: # Expect error due to server constraints added in SERVER-74069 - errorContains: "Encrypted State Collection name should follow" \ No newline at end of file + errorContains: "Encrypted State Collection name should follow" diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml index 8ed6e1d89c..952551ba3a 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Delete.yml index c9a8aced59..4e1a59c93f 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Delete.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml index 13d1129c8b..8767132e62 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-jsonSchema.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-jsonSchema.yml index 3d6e5ce957..119da443fe 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-jsonSchema.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFields-vs-jsonSchema.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFieldsMap-defaults.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFieldsMap-defaults.yml index 4d85e304df..ec91ebf1ca 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFieldsMap-defaults.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-EncryptedFieldsMap-defaults.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-FindOneAndUpdate.yml index 1a2211130a..7849b6032a 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-FindOneAndUpdate.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Indexed.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Indexed.yml index 27520226be..2acd97585b 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Indexed.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Indexed.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Unindexed.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Unindexed.yml index aeff4a88fc..ddba6c7439 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Unindexed.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-InsertFind-Unindexed.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml index 0ff40dbe8c..2d9eb6e580 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml index 66fb5e2625..afb79d526b 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml index b03e4d9a25..93ad199511 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml index 6e5f80265c..d60b7b6201 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml @@ -4,7 +4,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml index e15d19d843..0b969fd48c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml index 5f25f9756b..76bfe7ea25 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml index d9f9a10d51..e978b9de59 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml index 097d54c184..fe7d050f1b 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml index 807a11376c..0926988d27 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. @@ -10,7 +9,7 @@ runOn: database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. Aggregate." @@ -21,13 +20,13 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - name: aggregate arguments: - pipeline: [{ $match: { "encryptedDecimal": { $gt: {$numberDecimal: "0" }} } }] + pipeline: [{ $match: { "encryptedDecimalNoPrecision": { $gt: {$numberDecimal: "0" }} } }] result: [*doc1] expectations: - command_started_event: @@ -62,7 +61,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -77,7 +76,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -94,7 +93,7 @@ tests: pipeline: [ { "$match": { - "encryptedDecimal": { + "encryptedDecimalNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -124,7 +123,7 @@ tests: "_id": { "$numberInt": "0" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { @@ -907,7 +906,7 @@ tests: "_id": { "$numberInt": "1" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml index 6307bea62a..1961ed3e5f 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml @@ -4,7 +4,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. @@ -12,7 +11,7 @@ runOn: database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" @@ -23,13 +22,13 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0.0" } } + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1.0" } } + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } - name: find arguments: - filter: { encryptedDecimal: { $gt: { $numberDecimal: "0.0" } }} + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" } }} result: [*doc1] - description: "Find with $gte" @@ -44,7 +43,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDecimal: { $gte: { $numberDecimal: "0.0" } }} + filter: { encryptedDecimalNoPrecision: { $gte: { $numberDecimal: "0.0" } }} # sort so results from range queries are ordered. sort: { _id: 1 } result: [*doc0, *doc1] @@ -61,7 +60,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDecimal: { $gt: { $numberDecimal: "1.0" } }} + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "1.0" } }} result: [] - description: "Find with $lt" @@ -76,7 +75,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDecimal: { $lt: { $numberDecimal: "1.0" } }} + filter: { encryptedDecimalNoPrecision: { $lt: { $numberDecimal: "1.0" } }} result: [*doc0] - description: "Find with $lte" @@ -91,7 +90,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDecimal: { $lte: { $numberDecimal: "1.0" } }} + filter: { encryptedDecimalNoPrecision: { $lte: { $numberDecimal: "1.0" } }} # sort so results from range queries are ordered. sort: { _id: 1 } result: [*doc0, *doc1] @@ -108,7 +107,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDecimal: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} result: [*doc1] - description: "Find with equality" @@ -123,11 +122,11 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDecimal: { $numberDecimal: "0.0" } } + filter: { encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } result: [*doc0] - name: find arguments: - filter: { encryptedDecimal: { $numberDecimal: "1.0" } } + filter: { encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } result: [*doc1] - description: "Find with $in" @@ -142,7 +141,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDecimal: { $in: [ {$numberDecimal: "0.0"} ] } } + filter: { encryptedDecimalNoPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } result: [*doc0] - description: "Aggregate with $gte" @@ -158,7 +157,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDecimal: { $gte: { $numberDecimal: "0.0" } }} } + - { $match: { encryptedDecimalNoPrecision: { $gte: { $numberDecimal: "0.0" } }} } # sort so results from range queries are ordered. - { $sort: { _id: 1 }} result: [*doc0, *doc1] @@ -176,7 +175,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDecimal: { $gt: { $numberDecimal: "1.0" } }} } + - { $match: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "1.0" } }} } result: [] - description: "Aggregate with $lt" @@ -192,7 +191,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDecimal: { $lt: { $numberDecimal: "1.0" } }} } + - { $match: { encryptedDecimalNoPrecision: { $lt: { $numberDecimal: "1.0" } }} } result: [*doc0] - description: "Aggregate with $lte" @@ -208,7 +207,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDecimal: { $lte: { $numberDecimal: "1.0" } }} } + - { $match: { encryptedDecimalNoPrecision: { $lte: { $numberDecimal: "1.0" } }} } # sort so results from range queries are ordered. - { $sort: { _id: 1 }} result: [*doc0, *doc1] @@ -226,7 +225,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDecimal: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} } + - { $match: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} } result: [*doc1] - description: "Aggregate with equality" @@ -242,12 +241,12 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDecimal: { $numberDecimal: "0.0" } } } + - { $match: { encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } } result: [*doc0] - name: aggregate arguments: pipeline: - - { $match: { encryptedDecimal: { $numberDecimal: "1.0" } } } + - { $match: { encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } } result: [*doc1] - description: "Aggregate with $in" @@ -263,7 +262,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDecimal: { $in: [ {$numberDecimal: "0.0"} ] } } } + - { $match: { encryptedDecimalNoPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } } result: [*doc0] - description: "Wrong type: Insert Int" @@ -273,7 +272,7 @@ tests: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} operations: - name: insertOne - arguments: { document: { _id: 0, encryptedDecimal: { $numberInt: "0" }} } + arguments: { document: { _id: 0, encryptedDecimalNoPrecision: { $numberInt: "0" }} } result: # Expect an error from mongocryptd. errorContains: "cannot encrypt element" @@ -286,7 +285,7 @@ tests: operations: - name: find arguments: - filter: { encryptedDecimal: { $gte: { $numberInt: "0" } }} + filter: { encryptedDecimalNoPrecision: { $gte: { $numberInt: "0" } }} # sort so results from range queries are ordered. sort: { _id: 1 } result: diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml index d017530cb0..bdb10e2d88 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. @@ -10,7 +9,7 @@ runOn: database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. Delete." @@ -21,13 +20,13 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - name: deleteOne arguments: - filter: { "encryptedDecimal": { $gt: {$numberDecimal: "0" }} } + filter: { "encryptedDecimalNoPrecision": { $gt: {$numberDecimal: "0" }} } result: deletedCount: 1 expectations: @@ -63,7 +62,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -78,7 +77,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -95,7 +94,7 @@ tests: deletes: [ { "q": { - "encryptedDecimal": { + "encryptedDecimalNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -126,7 +125,7 @@ tests: "_id": { "$numberInt": "0" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml index 32a35875a4..defccea0aa 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. @@ -10,7 +9,7 @@ runOn: database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. FindOneAndUpdate." @@ -21,14 +20,14 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - name: findOneAndUpdate arguments: - filter: { encryptedDecimal: { $gt: {$numberDecimal: "0"}} } - update: { "$set": { "encryptedDecimal": {$numberDecimal: "2"}}} + filter: { encryptedDecimalNoPrecision: { $gt: {$numberDecimal: "0"}} } + update: { "$set": { "encryptedDecimalNoPrecision": {$numberDecimal: "2"}}} returnDocument: Before result: *doc1 expectations: @@ -64,7 +63,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -79,7 +78,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -94,7 +93,7 @@ tests: command: findAndModify: *collection_name query: { - "encryptedDecimal": { + "encryptedDecimalNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -103,7 +102,7 @@ tests: } } } - update: { "$set": {"encryptedDecimal": { $$type: "binData" }} } + update: { "$set": {"encryptedDecimalNoPrecision": { $$type: "binData" }} } encryptionInformation: type: 1 schema: @@ -122,7 +121,7 @@ tests: "_id": { "$numberInt": "0" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { @@ -905,7 +904,7 @@ tests: "_id": { "$numberInt": "1" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml index d1bb287696..51abaa6423 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. @@ -10,7 +9,7 @@ runOn: database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. Insert and Find." @@ -21,13 +20,13 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - name: find arguments: - filter: { encryptedDecimal: { $gt: { $numberDecimal: "0" } } } + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0" } } } result: [*doc1] expectations: - command_started_event: @@ -62,7 +61,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -77,7 +76,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -92,7 +91,7 @@ tests: command: find: *collection_name filter: - "encryptedDecimal": { + "encryptedDecimalNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -118,7 +117,7 @@ tests: "_id": { "$numberInt": "0" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { @@ -901,7 +900,7 @@ tests: "_id": { "$numberInt": "1" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml index d4981ca375..e79fd082ec 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. @@ -10,7 +9,7 @@ runOn: database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimal', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Decimal. Update." @@ -21,14 +20,14 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDecimal: { $numberDecimal: "0" } } + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDecimal: { $numberDecimal: "1" } } + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - name: updateOne arguments: - filter: { encryptedDecimal: { $gt: { $numberDecimal: "0" } } } - update: { "$set": { "encryptedDecimal": { $numberDecimal: "2" } }} + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0" } } } + update: { "$set": { "encryptedDecimalNoPrecision": { $numberDecimal: "2" } }} result: matchedCount: 1 modifiedCount: 1 @@ -66,7 +65,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimal": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -81,7 +80,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimal": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -100,7 +99,7 @@ tests: "updates": [ { "q": { - "encryptedDecimal": { + "encryptedDecimalNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -111,7 +110,7 @@ tests: }, "u": { "$set": { - "encryptedDecimal": { $$type: "binData" } + "encryptedDecimalNoPrecision": { $$type: "binData" } } } } @@ -135,7 +134,7 @@ tests: "_id": { "$numberInt": "0" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { @@ -918,7 +917,7 @@ tests: "_id": { "$numberInt": "1" }, - "encryptedDecimal": { $$type: "binData" }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml index 98defcd9ed..08f4a380cf 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml index aa7ef7525d..18252b4bbe 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml @@ -4,7 +4,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml index 446745fdc8..ade385d202 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml index f90616a88a..7100d58886 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml index e8e76cc95f..32785d6589 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml index 87dfe70770..eedd076084 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml index 7cc857c3d9..4fb95343b0 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml @@ -2,14 +2,13 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. Aggregate." @@ -20,13 +19,13 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - name: aggregate arguments: - pipeline: [{ $match: { "encryptedDouble": { $gt: {$numberDouble: "0" }} } }] + pipeline: [{ $match: { "encryptedDoubleNoPrecision": { $gt: {$numberDouble: "0" }} } }] result: [*doc1] expectations: - command_started_event: @@ -61,7 +60,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -76,7 +75,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -93,7 +92,7 @@ tests: pipeline: [ { "$match": { - "encryptedDouble": { + "encryptedDoubleNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -121,7 +120,7 @@ tests: - { "_id": 0, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { @@ -518,7 +517,7 @@ tests: - { "_id": 1, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml index 38e870a053..7289bb24dc 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml @@ -4,14 +4,13 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "Find with $gt" @@ -22,13 +21,13 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0.0" } } + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1.0" } } + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } - name: find arguments: - filter: { encryptedDouble: { $gt: { $numberDouble: "0.0" } }} + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" } }} result: [*doc1] - description: "Find with $gte" @@ -43,7 +42,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDouble: { $gte: { $numberDouble: "0.0" } }} + filter: { encryptedDoubleNoPrecision: { $gte: { $numberDouble: "0.0" } }} # sort so results from range queries are ordered. sort: { _id: 1 } result: [*doc0, *doc1] @@ -60,7 +59,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDouble: { $gt: { $numberDouble: "1.0" } }} + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "1.0" } }} result: [] - description: "Find with $lt" @@ -75,7 +74,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDouble: { $lt: { $numberDouble: "1.0" } }} + filter: { encryptedDoubleNoPrecision: { $lt: { $numberDouble: "1.0" } }} result: [*doc0] - description: "Find with $lte" @@ -90,7 +89,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDouble: { $lte: { $numberDouble: "1.0" } }} + filter: { encryptedDoubleNoPrecision: { $lte: { $numberDouble: "1.0" } }} # sort so results from range queries are ordered. sort: { _id: 1 } result: [*doc0, *doc1] @@ -107,7 +106,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDouble: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} result: [*doc1] - description: "Find with equality" @@ -122,11 +121,11 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDouble: { $numberDouble: "0.0" } } + filter: { encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } result: [*doc0] - name: find arguments: - filter: { encryptedDouble: { $numberDouble: "1.0" } } + filter: { encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } result: [*doc1] - description: "Find with $in" @@ -141,7 +140,7 @@ tests: arguments: { document: *doc1 } - name: find arguments: - filter: { encryptedDouble: { $in: [ {$numberDouble: "0.0"} ] } } + filter: { encryptedDoubleNoPrecision: { $in: [ {$numberDouble: "0.0"} ] } } result: [*doc0] - description: "Aggregate with $gte" @@ -157,7 +156,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDouble: { $gte: { $numberDouble: "0.0" } }} } + - { $match: { encryptedDoubleNoPrecision: { $gte: { $numberDouble: "0.0" } }} } # sort so results from range queries are ordered. - { $sort: { _id: 1 }} result: [*doc0, *doc1] @@ -175,7 +174,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDouble: { $gt: { $numberDouble: "1.0" } }} } + - { $match: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "1.0" } }} } result: [] - description: "Aggregate with $lt" @@ -191,7 +190,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDouble: { $lt: { $numberDouble: "1.0" } }} } + - { $match: { encryptedDoubleNoPrecision: { $lt: { $numberDouble: "1.0" } }} } result: [*doc0] - description: "Aggregate with $lte" @@ -207,7 +206,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDouble: { $lte: { $numberDouble: "1.0" } }} } + - { $match: { encryptedDoubleNoPrecision: { $lte: { $numberDouble: "1.0" } }} } # sort so results from range queries are ordered. - { $sort: { _id: 1 }} result: [*doc0, *doc1] @@ -225,7 +224,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDouble: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} } + - { $match: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} } result: [*doc1] - description: "Aggregate with equality" @@ -241,12 +240,12 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDouble: { $numberDouble: "0.0" } } } + - { $match: { encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } } result: [*doc0] - name: aggregate arguments: pipeline: - - { $match: { encryptedDouble: { $numberDouble: "1.0" } } } + - { $match: { encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } } result: [*doc1] - description: "Aggregate with $in" @@ -262,7 +261,7 @@ tests: - name: aggregate arguments: pipeline: - - { $match: { encryptedDouble: { $in: [ {$numberDouble: "0.0"} ] } } } + - { $match: { encryptedDoubleNoPrecision: { $in: [ {$numberDouble: "0.0"} ] } } } result: [*doc0] - description: "Wrong type: Insert Int" @@ -272,7 +271,7 @@ tests: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} operations: - name: insertOne - arguments: { document: { _id: 0, encryptedDouble: { $numberInt: "0" }} } + arguments: { document: { _id: 0, encryptedDoubleNoPrecision: { $numberInt: "0" }} } result: # Expect an error from mongocryptd. errorContains: "cannot encrypt element" @@ -285,7 +284,7 @@ tests: operations: - name: find arguments: - filter: { encryptedDouble: { $gte: { $numberInt: "0" } }} + filter: { encryptedDoubleNoPrecision: { $gte: { $numberInt: "0" } }} # sort so results from range queries are ordered. sort: { _id: 1 } result: diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml index 3902b46c61..2f42c1da10 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml @@ -2,14 +2,13 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. Delete." @@ -20,13 +19,13 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - name: deleteOne arguments: - filter: { "encryptedDouble": { $gt: {$numberDouble: "0" }} } + filter: { "encryptedDoubleNoPrecision": { $gt: {$numberDouble: "0" }} } result: deletedCount: 1 expectations: @@ -62,7 +61,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -77,7 +76,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -94,7 +93,7 @@ tests: deletes: [ { "q": { - "encryptedDouble": { + "encryptedDoubleNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -123,7 +122,7 @@ tests: - { "_id": 0, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml index 59556ba71e..d6573ff862 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml @@ -2,14 +2,13 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. FindOneAndUpdate." @@ -20,14 +19,14 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - name: findOneAndUpdate arguments: - filter: { encryptedDouble: { $gt: {$numberDouble: "0"}} } - update: { "$set": { "encryptedDouble": {$numberDouble: "2"}}} + filter: { encryptedDoubleNoPrecision: { $gt: {$numberDouble: "0"}} } + update: { "$set": { "encryptedDoubleNoPrecision": {$numberDouble: "2"}}} returnDocument: Before result: *doc1 expectations: @@ -63,7 +62,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -78,7 +77,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -93,7 +92,7 @@ tests: command: findAndModify: *collection_name query: { - "encryptedDouble": { + "encryptedDoubleNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -102,7 +101,7 @@ tests: } } } - update: { "$set": {"encryptedDouble": { $$type: "binData" }} } + update: { "$set": {"encryptedDoubleNoPrecision": { $$type: "binData" }} } encryptionInformation: type: 1 schema: @@ -119,7 +118,7 @@ tests: - { "_id": 0, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { @@ -516,7 +515,7 @@ tests: - { "_id": 1, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml index 95b9fdc8f6..0122ba243c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml @@ -2,14 +2,13 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. Insert and Find." @@ -20,13 +19,13 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - name: find arguments: - filter: { encryptedDouble: { $gt: { $numberDouble: "0" } } } + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0" } } } result: [*doc1] expectations: - command_started_event: @@ -61,7 +60,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -76,7 +75,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -91,7 +90,7 @@ tests: command: find: *collection_name filter: - "encryptedDouble": { + "encryptedDoubleNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -115,7 +114,7 @@ tests: - { "_id": 0, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { @@ -512,7 +511,7 @@ tests: - { "_id": 1, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml index 7ab0a0cec2..176db3971e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml @@ -2,14 +2,13 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDouble', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] tests: - description: "FLE2 Range Double. Update." @@ -20,14 +19,14 @@ tests: operations: - name: insertOne arguments: - document: &doc0 { _id: 0, encryptedDouble: { $numberDouble: "0" } } + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - name: insertOne arguments: - document: &doc1 { _id: 1, encryptedDouble: { $numberDouble: "1" } } + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - name: updateOne arguments: - filter: { encryptedDouble: { $gt: { $numberDouble: "0" } } } - update: { "$set": { "encryptedDouble": { $numberDouble: "2" } }} + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0" } } } + update: { "$set": { "encryptedDoubleNoPrecision": { $numberDouble: "2" } }} result: matchedCount: 1 modifiedCount: 1 @@ -65,7 +64,7 @@ tests: command: insert: *collection_name documents: - - &doc0_encrypted { "_id": 0, "encryptedDouble": { $$type: "binData" } } + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -80,7 +79,7 @@ tests: command: insert: *collection_name documents: - - &doc1_encrypted { "_id": 1, "encryptedDouble": { $$type: "binData" } } + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } ordered: true encryptionInformation: type: 1 @@ -99,7 +98,7 @@ tests: "updates": [ { "q": { - "encryptedDouble": { + "encryptedDoubleNoPrecision": { "$gt": { "$binary": { "base64": "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", @@ -110,7 +109,7 @@ tests: }, "u": { "$set": { - "encryptedDouble": { $$type: "binData" } + "encryptedDoubleNoPrecision": { $$type: "binData" } } } } @@ -132,7 +131,7 @@ tests: - { "_id": 0, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { @@ -529,7 +528,7 @@ tests: - { "_id": 1, - "encryptedDouble": { $$type: "binData" }, + "encryptedDoubleNoPrecision": { $$type: "binData" }, "__safeContent__": [ { "$binary": { diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml index 2700de4ad4..134003bf9c 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml index 6508db7d19..a8fc4ec2a4 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml @@ -4,7 +4,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml index e5a35c8ee4..a6f83da786 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml index e74b6ecb73..5def8d287d 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml index bc15c9b88b..8900f79a86 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml index 3211b5047e..3e31f40181 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml index cb7765faae..13c350ea5f 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml index 77206aed6e..1e7d5d47fc 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml @@ -4,7 +4,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml index 59f58650e3..ab1e9d2e5e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml index f49f319e22..a33a5120a5 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml index d848bbdf7e..4ef8c8e520 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml index a18878f55e..cf5716dab0 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml index 9837cf6e41..cb5e42c158 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml index 4df64cb67a..a7a33e274e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml @@ -4,7 +4,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml index 1df1150c75..8dd1603f33 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml index 24f5d45c92..0641988b98 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml index 8e2f826f72..076670d49e 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml index c293a64093..0aad7c4416 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml @@ -2,7 +2,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml index ada65209e5..b44e8c5055 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml @@ -4,7 +4,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] @@ -41,4 +40,4 @@ tests: sort: { _id: 1 } result: # expect an error from libmongocrypt. - errorContains: "field type is not supported" \ No newline at end of file + errorContains: "field type is not supported" diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Update.yml index ce5a4ed670..3bed027181 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-Update.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Update.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml index fdeb49010c..3eb90ea935 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml @@ -2,7 +2,6 @@ runOn: # Require server version 6.0.0 to get behavior added in SERVER-64911. - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/spec/support/utils.rb b/spec/support/utils.rb index 7926d1d3f0..fa572adaa6 100644 --- a/spec/support/utils.rb +++ b/spec/support/utils.rb @@ -336,11 +336,6 @@ def match_with_type?(expected, actual) actual.is_a?(expected_class) || actual.key?(expected_key) elsif expected.is_a?(Hash) && actual.is_a?(Hash) - # some specs treat nil values as meaning that the field should be - # absent; we'll just squish nil values out for consistency. - expected = expected.compact - actual = actual.compact - has_all_keys = (expected.keys - actual.keys).empty? same_values = expected.keys.all? do |key| From 98da7832aaae5fb576cd2536d6353ea4597a2d3f Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Wed, 21 Jun 2023 19:53:35 +0200 Subject: [PATCH 139/198] Prepare release of 2.19.0 (#2736) Co-authored-by: Alex Bevilacqua --- docs/release-notes.txt | 15 ++++++++++++--- lib/mongo/version.rb | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/release-notes.txt b/docs/release-notes.txt index d3ebfba7ae..bb8739c977 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -23,14 +23,23 @@ its test suite. 2.19 ==== +This release of the Ruby driver supports MongoDB version 7.0. The Ruby driver +now supports Ruby 3.2. Ruby 2.5 and 2.6 are now deprecated. + This release includes the following new features: -- Added support for automatic AWS credentials retrieval when AWS KMS is used for - client side encryption. +- Added support for automatic AWS credentials retrieval and authentication + with temporary credentials when AWS KMS is used for client side encryption. - Added support for automatic GCP credentials retrieval when Google Cloud Key Management is used for client side encryption. +- Added support the Azure VM-assigned Managed Identity for Automatic KMS Credentials + when Azure Key Vault is used for client side encryption. +- `Queryable Encryption `_ support is extended. +- Added support for Queryable Encryption Range Indexes. +- A `crypt_shared `_ + library can be now used instead of ``mongocryptd``. - Added support for AWS IAM Roles for service accounts, EKS in particular. - +- AWS Credentials are now cached when possible. .. _release-notes-2.18: diff --git a/lib/mongo/version.rb b/lib/mongo/version.rb index 49641f39a3..80f916063a 100644 --- a/lib/mongo/version.rb +++ b/lib/mongo/version.rb @@ -20,5 +20,5 @@ module Mongo # The current version of the driver. # # @since 2.0.0 - VERSION = '2.18.1'.freeze + VERSION = '2.19.0'.freeze end From 5a6a8c0d35649b52d1c9d860169f04748d96d02b Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 23 Jun 2023 17:43:22 +0200 Subject: [PATCH 140/198] Update compatibility tables (#2737) * Update compatibility tables * Update record for kerberos --- docs/reference/driver-compatibility.txt | 30 ++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/reference/driver-compatibility.txt b/docs/reference/driver-compatibility.txt index bcb35561fb..0402c17f83 100644 --- a/docs/reference/driver-compatibility.txt +++ b/docs/reference/driver-compatibility.txt @@ -38,6 +38,7 @@ version. :class: compatibility-large no-padding * - Ruby Driver + - MongoDB 7.0 - MongoDB 6.0 - MongoDB 5.0 - MongoDB 4.4 @@ -49,7 +50,21 @@ version. - MongoDB 3.0 - MongoDB 2.6 + * - 2.19 + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - + - + - + - + * - 2.18 + - - |checkmark| - |checkmark| - |checkmark| @@ -62,6 +77,7 @@ version. - * - 2.17 + - - - |checkmark| - |checkmark| @@ -74,6 +90,7 @@ version. - * - 2.16 + - - - |checkmark| - |checkmark| @@ -86,6 +103,7 @@ version. - D * - 2.15 + - - - - |checkmark| @@ -98,6 +116,7 @@ version. - |checkmark| * - 2.14 + - - - - |checkmark| @@ -110,6 +129,7 @@ version. - |checkmark| * - 2.13 + - - - - |checkmark| [#ocsp]_ @@ -125,6 +145,7 @@ version. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -137,6 +158,7 @@ version. - - - + - - |checkmark| [#client-side-encryption]_ - |checkmark| - |checkmark| @@ -149,6 +171,7 @@ version. - - - + - - |checkmark| [#srv-polling]_ [#client-side-encryption]_ - |checkmark| - |checkmark| @@ -162,6 +185,7 @@ version. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -174,6 +198,7 @@ version. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -186,6 +211,7 @@ version. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -198,6 +224,7 @@ version. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -211,6 +238,7 @@ version. - - - + - - |checkmark| - |checkmark| - |checkmark| @@ -592,7 +620,7 @@ the driver. * - Ruby Driver - mongo_kerberos |nbsp| 2.1 - * - 2.7 - 2.18 + * - 2.7 - 2.19 - |checkmark| From eef4b2086c76c8905bae2b0c1d7af1e510c3037d Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 7 Jul 2023 16:46:03 +0200 Subject: [PATCH 141/198] RUBY-3205 Deprecate collStats (#2738) * RUBY-3205 Deprecate collStats * Consider createOptions in unified spec runner * Update lib/mongo/collection.rb Co-authored-by: Jamis Buck --------- Co-authored-by: Jamis Buck --- docs/reference/crud-operations.txt | 4 +- lib/mongo/collection.rb | 4 +- .../operation/shared/sessions_supported.rb | 1 - spec/integration/docs_examples_spec.rb | 2 +- spec/mongo/collection_ddl_spec.rb | 12 ++++-- spec/mongo/collection_spec.rb | 12 ++++-- spec/runners/unified/test.rb | 24 ++++++------ .../bypassedCommand.yml | 9 +++-- .../collectionData-createOptions.yml | 37 +++++++++++++++++++ 9 files changed, 76 insertions(+), 29 deletions(-) create mode 100644 spec/spec_tests/data/unified/valid-pass/collectionData-createOptions.yml diff --git a/docs/reference/crud-operations.txt b/docs/reference/crud-operations.txt index b8e5d5634b..bbae4c202a 100644 --- a/docs/reference/crud-operations.txt +++ b/docs/reference/crud-operations.txt @@ -458,7 +458,7 @@ part of the command: .. code-block:: ruby - client.database.command(collstats: 'test', readConcern: {level: :majority}) + client.database.command(dbStats: 1, readConcern: {level: :majority}) .. _read-preference: @@ -504,7 +504,7 @@ option when a command is run on a database: } ) # Set read preference for a given command - client.database.command( { collstats: 'test' }, read: { mode: secondary, + client.database.command( { dbStats: 1 }, read: { mode: secondary, tag_sets: [ { 'dc' => 'nyc' } ] } ) Read preference can also be set for specific operations on a collection diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index bb828ce491..5b8d5af128 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -314,7 +314,9 @@ def with(new_options) # # @since 2.0.0 def capped? - database.read_command(:collstats => name).documents[0][CAPPED] + database.list_collections(filter: { name: name }) + .first + &.dig('options', CAPPED) || false end # Force the collection to be created in the database. diff --git a/lib/mongo/operation/shared/sessions_supported.rb b/lib/mongo/operation/shared/sessions_supported.rb index e8d59c23d4..aed9bd23e6 100644 --- a/lib/mongo/operation/shared/sessions_supported.rb +++ b/lib/mongo/operation/shared/sessions_supported.rb @@ -30,7 +30,6 @@ module SessionsSupported READ_COMMANDS = [ :aggregate, - :collStats, :count, :dbStats, :distinct, diff --git a/spec/integration/docs_examples_spec.rb b/spec/integration/docs_examples_spec.rb index 01609bc353..bf1ae9e1b0 100644 --- a/spec/integration/docs_examples_spec.rb +++ b/spec/integration/docs_examples_spec.rb @@ -162,7 +162,7 @@ # Start runCommand Example 2 - client.database.command(collStats: 'restaurants') + client.database.command(dbStats: 1) # End runCommand Example 2 end diff --git a/spec/mongo/collection_ddl_spec.rb b/spec/mongo/collection_ddl_spec.rb index 4599133925..1c644cfabd 100644 --- a/spec/mongo/collection_ddl_spec.rb +++ b/spec/mongo/collection_ddl_spec.rb @@ -137,7 +137,11 @@ end let(:collstats) do - database.read_command(:collstats => :specs).documents.first + collection.aggregate([ {'$collStats' => { 'storageStats' => {} }} ]).first + end + + let(:storage_stats) do + collstats.fetch('storageStats', {}) end let(:options) do @@ -157,9 +161,9 @@ end it "applies the options" do - expect(collstats["capped"]).to be true - expect(collstats["max"]).to eq(512) - expect(collstats["maxSize"]).to eq(4096) + expect(storage_stats["capped"]).to be true + expect(storage_stats["max"]).to eq(512) + expect(storage_stats["maxSize"]).to eq(4096) end end end diff --git a/spec/mongo/collection_spec.rb b/spec/mongo/collection_spec.rb index bd050bb339..ef58e44c84 100644 --- a/spec/mongo/collection_spec.rb +++ b/spec/mongo/collection_spec.rb @@ -651,7 +651,11 @@ end let(:collstats) do - database.read_command(:collstats => :specs).documents.first + collection.aggregate([ {'$collStats' => { 'storageStats' => {} }} ]).first + end + + let(:storage_stats) do + collstats.fetch('storageStats', {}) end before do @@ -664,9 +668,9 @@ end it "applies the options" do - expect(collstats["capped"]).to be true - expect(collstats["max"]).to eq(512) - expect(collstats["maxSize"]).to eq(4096) + expect(storage_stats["capped"]).to be true + expect(storage_stats["max"]).to eq(512) + expect(storage_stats["maxSize"]).to eq(4096) end end diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index d1f21c5f60..bc48a01d65 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -320,22 +320,22 @@ def set_initial_data collection = root_authorized_client.with(write_concern: {w: :majority}). use(spec.use!('databaseName'))[spec.use!('collectionName')] collection.drop + create_options = spec.use('createOptions') || {} docs = spec.use!('documents') - if docs.any? - collection.insert_many(docs) - else - begin - collection.create - rescue Mongo::Error => e - if Mongo::Error::OperationFailure === e && ( + begin + collection.create(create_options) + rescue Mongo::Error => e + if Mongo::Error::OperationFailure === e && ( e.code == 48 || e.message =~ /collection already exists/ - ) - # Already exists - else - raise - end + ) + # Already exists + else + raise end end + if docs.any? + collection.insert_many(docs) + end unless spec.empty? raise NotImplementedError, "Unhandled spec keys: #{spec}" end diff --git a/spec/spec_tests/data/client_side_encryption/bypassedCommand.yml b/spec/spec_tests/data/client_side_encryption/bypassedCommand.yml index 7057abd592..98e360b6e3 100644 --- a/spec/spec_tests/data/client_side_encryption/bypassedCommand.yml +++ b/spec/spec_tests/data/client_side_encryption/bypassedCommand.yml @@ -26,7 +26,7 @@ tests: command: ping: 1 command_name: ping - - description: "current op is not bypassed" + - description: "kill op is not bypassed" clientOptions: autoEncryptOpts: kmsProviders: @@ -34,9 +34,10 @@ tests: operations: - name: runCommand object: database - command_name: currentOp + command_name: killOp arguments: command: - currentOp: 1 + killOp: 1 + op: 1234 result: - errorContains: "command not supported for auto encryption: currentOp" \ No newline at end of file + errorContains: "command not supported for auto encryption: killOp" \ No newline at end of file diff --git a/spec/spec_tests/data/unified/valid-pass/collectionData-createOptions.yml b/spec/spec_tests/data/unified/valid-pass/collectionData-createOptions.yml new file mode 100644 index 0000000000..c6afedcfa9 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/collectionData-createOptions.yml @@ -0,0 +1,37 @@ +description: collectionData-createOptions +schemaVersion: "1.9" +runOnRequirements: + - minServerVersion: "3.6" + # Capped collections cannot be created on serverless instances. + serverless: forbid +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + createOptions: + capped: true + # With MMAPv1, the size field cannot be less than 4096. + size: &cappedSize 4096 + documents: + - { _id: 1, x: 11 } +tests: + - description: collection is created with the correct options + operations: + - object: *collection0 + name: aggregate + arguments: + pipeline: + - $collStats: { storageStats: {} } + - $project: { capped: '$storageStats.capped', maxSize: '$storageStats.maxSize'} + expectResult: + - { capped: true, maxSize: *cappedSize } \ No newline at end of file From c928e60b21ade6b1ce1a88f0b15f7132eb363c06 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 12 Jul 2023 07:49:40 -0600 Subject: [PATCH 142/198] RUBY-1599 Test against BSON 5 (#2740) * let's not run 2.18-stable specs against MDB 7+ * Revert "let's not run 2.18-stable specs against MDB 7+" Meant to do this on a branch :/ This reverts commit 1f3f7c76655b10803087ba24274bacf7c3468eec. * remove dead comments, and permit bson5 for these specs * name the bson matrix appropriately * fix duplicate variant names --- .evergreen/config.yml | 4 +- .evergreen/config/standard.yml.erb | 4 +- spec/mongo/protocol/msg_spec.rb | 63 +----------------------------- 3 files changed, 5 insertions(+), 66 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index cc3dc63305..27a1ab0ed2 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1066,7 +1066,7 @@ buildvariants: mongodb-version: ["latest", "7.0", "6.0"] topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 - display_name: ${auth-and-ssl} ${ruby} ${topology} + display_name: ${auth-and-ssl} ${ruby} db-${mongodb-version} ${topology} tasks: - name: "test-mlaunch" @@ -1338,7 +1338,7 @@ buildvariants: topology: replica-set bson: "*" os: rhel8 - display_name: "AS ${mongodb-version} ${topology} ${ruby} ${bson}" + display_name: "bson-${bson} ${mongodb-version} ${topology} ${ruby}" tasks: - name: "test-mlaunch" diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index 8e56bcf887..67e2a92686 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -48,7 +48,7 @@ buildvariants: mongodb-version: <%= actual_and_upcoming_mdb %> topology: <%= topologies %> os: rhel8 - display_name: ${auth-and-ssl} ${ruby} ${topology} + display_name: ${auth-and-ssl} ${ruby} db-${mongodb-version} ${topology} tasks: - name: "test-mlaunch" @@ -320,7 +320,7 @@ buildvariants: topology: replica-set bson: "*" os: rhel8 - display_name: "AS ${mongodb-version} ${topology} ${ruby} ${bson}" + display_name: "bson-${bson} ${mongodb-version} ${topology} ${ruby}" tasks: - name: "test-mlaunch" diff --git a/spec/mongo/protocol/msg_spec.rb b/spec/mongo/protocol/msg_spec.rb index 9c28da0196..23a4f41d1b 100644 --- a/spec/mongo/protocol/msg_spec.rb +++ b/spec/mongo/protocol/msg_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # rubocop:todo all -require 'lite_spec_helper' +require 'spec_helper' require 'support/shared/protocol' describe Mongo::Protocol::Msg do @@ -237,44 +237,6 @@ end end -=begin no longer supported - context 'when a 0 payload type is specified' do - - let(:section) do - { type: 0, payload: { ismaster: 1 } } - end - - let(:section_payload_type) { bytes.to_s[36] } - let(:section_bytes) { bytes.to_s[37..-1] } - - it 'sets the payload type' do - expect(section_payload_type).to eq(0.chr) - end - - it 'serializes the section' do - expect(section_bytes).to be_bson(section[:payload]) - end - end - - context 'when a no payload type is specified' do - - let(:section) do - { payload: { ismaster: 1 } } - end - - let(:section_payload_type) { bytes.to_s[36] } - let(:section_bytes) { bytes.to_s[37..-1] } - - it 'sets the payload type as 0' do - expect(section_payload_type).to eq(0.chr) - end - - it 'serializes the section' do - expect(section_bytes).to be_bson(section[:payload]) - end - end -=end - context 'when a payload of type 1 is specified' do let(:section) do @@ -366,32 +328,9 @@ end end end - -=begin no longer supported - context 'when the sections are mixed types and payload type 1 comes before type 0' do - - let(:section1) do - Mongo::Protocol::Msg::Section1.new('documents', [ { a: 1 } ]) - end - - let(:section2) do - { type: 0, payload: { 'b' => 2 } } - end - - let(:sections) do - [ section1, section2 ] - end - - it 'serializes all sections' do - expect(deserialized.documents).to eq([ BSON::Document.new(main_document), { 'a' => 1 }, { 'b' => 2 }]) - end - end -=end end context 'when the validating_keys option is true with payload 1' do - max_bson_version '4.99.99' - let(:sequences) do [ section ] end From 7f338a12657e91774d3ed5ea9e1285aa7b6e019b Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 13 Jul 2023 16:13:48 +0200 Subject: [PATCH 143/198] RUBY-3284 Enable max_connecting client option (#2739) * RUBY-3284 Enable max_connecting client option * Add validation and error * Add test for uri option * More tests * Docu; bump the version * Fix connection_requests counter handling * Fix code review remarks * Increase timeout * Update documentation * Housekeeping * Update release-notes.txt * Fix code review remarks * Revert some changes in spec tests * Skip flaky jruby test * Use select (we still support 2.5) --------- Co-authored-by: Alex Bevilacqua --- docs/reference/create-client.txt | 26 +++- docs/release-notes.txt | 6 + lib/mongo/client.rb | 25 +++ lib/mongo/error.rb | 1 + lib/mongo/error/invalid_max_connecting.rb | 28 ++++ lib/mongo/server/connection_pool.rb | 146 ++++++++++++------ lib/mongo/uri/options_mapper.rb | 1 + lib/mongo/version.rb | 2 +- spec/lite_spec_helper.rb | 6 +- spec/mongo/client_construction_spec.rb | 48 ++++++ spec/mongo/uri/srv_protocol_spec.rb | 12 ++ ...t-minPoolSize-connection-maxConnecting.yml | 6 +- 12 files changed, 254 insertions(+), 53 deletions(-) create mode 100644 lib/mongo/error/invalid_max_connecting.rb diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index b71a866910..f54805651a 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -575,6 +575,11 @@ Ruby Options - ``Object`` - ``Logger`` + * - ``:max_connecting`` + - The maximum number of connections that the connection pool will try to establish in parallel. + - ``Integer`` + - 2 + * - ``:max_idle_time`` - The maximum time, in seconds, that a connection can be idle before it is closed by the connection pool. @@ -1018,6 +1023,9 @@ URI options are explained in detail in the :manual:`Connection URI reference * - localThresholdMS=Integer - ``:local_threshold => Float`` + * - maxConnecting=Integer + - ``:max_connecting => Integer`` + * - maxIdleTimeMS=Integer - ``:max_idle_time => Float`` @@ -1551,6 +1559,14 @@ maximum size, the thread waits for a connection to be returned to the pool by another thread. If ``max_pool_size`` is set to zero, there is no limit for the maximum number of connections in the pool. +Each pool has a limit on the number of connections that can be concurrently +connecting to a server. This limit is called ``max_connecting`` and defaults to +2. If the number of connections that are currently connecting to a server +reaches this limit, the pool will wait for a connection attempt to succeed or +fail before attempting to create a new connection. If your application +has a large number of threads, you may want to increase ``max_connecting`` to avoid +having threads wait for a connection to be established. + The number of seconds the thread will wait for a connection to become available is configurable. This setting, called ``wait_queue_timeout``, is defined in seconds. If this timeout is reached, a ``Timeout::Error`` is raised. The @@ -1595,6 +1611,14 @@ process, increase ``max_pool_size``: client = Mongo::Client.new(["localhost:27017"], max_pool_size: 200) +To support extremely high numbers of threads that share the same client +within one process, increase ``max_connecting``: + +.. code-block:: ruby + + client = Mongo::Client.new(["localhost:27017"], max_pool_size: 200, max_connecting: 10) + + Any number of threads are allowed to wait for connections to become available, and they can wait the default (1 second) or the ``wait_queue_timeout`` setting: @@ -1627,7 +1651,7 @@ such as Unicorn, Puma or Passenger, or when the application otherwise forks, each process should generally each have their own ``Mongo::Client`` instances. This is because: -1. The background threads remain in the parent process and are not transfered +1. The background threads remain in the parent process and are not transferred to the child process. 2. File descriptors like network sockets are shared between parent and child processes. diff --git a/docs/release-notes.txt b/docs/release-notes.txt index bb8739c977..f8eebf104a 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -28,6 +28,12 @@ now supports Ruby 3.2. Ruby 2.5 and 2.6 are now deprecated. This release includes the following new features: +- The driver now limits the number of connections established by a connection + pool simultaneously. By default the limit is 2. The limit can be configured + with the ``:max_connecting`` option of the ``Mongo::Client`` constructor. + The default should be sufficient for most applications. However, if your + application is using a large number of threads, you may need to increase + the limit. - Added support for automatic AWS credentials retrieval and authentication with temporary credentials when AWS KMS is used for client side encryption. - Added support for automatic GCP credentials retrieval when Google Cloud Key diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index 42de028ca5..e06231d4ad 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -71,6 +71,7 @@ class Client :local_threshold, :logger, :log_prefix, + :max_connecting, :max_idle_time, :max_pool_size, :max_read_retries, @@ -266,6 +267,12 @@ def hash # @option options [ String ] :log_prefix A custom log prefix to use when # logging. This option is experimental and subject to change in a future # version of the driver. + # @option options [ Integer ] :max_connecting The maximum number of + # connections that can be connecting simultaneously. The default is 2. + # This option should be increased if there are many threads that share + # the same client and the application is experiencing timeouts + # while waiting for connections to be established. + # selecting a server for an operation. The default is 2. # @option options [ Integer ] :max_idle_time The maximum seconds a socket can remain idle # since it has been checked in to the pool. # @option options [ Integer ] :max_pool_size The maximum size of the @@ -1318,6 +1325,7 @@ def validate_new_options!(opts) key = k.to_sym if VALID_OPTIONS.include?(key) validate_max_min_pool_size!(key, opts) + validate_max_connecting!(key, opts) validate_read!(key, opts) if key == :compressors compressors = valid_compressors(v) @@ -1580,6 +1588,23 @@ def validate_max_min_pool_size!(option, opts) true end + # Validates whether the max_connecting option is valid. + # + # @param [ Symbol ] option The option to validate. + # @param [ Hash ] opts The client options. + # + # @return [ true ] If the option is valid. + # @raise [ Error::InvalidMaxConnecting ] If the option is invalid. + def validate_max_connecting!(option, opts) + if option == :max_connecting && opts.key?(:max_connecting) + max_connecting = opts[:max_connecting] || Server::ConnectionPool::DEFAULT_MAX_CONNECTING + if max_connecting <= 0 + raise Error::InvalidMaxConnecting.new(opts[:max_connecting]) + end + end + true + end + def validate_read!(option, opts) if option == :read && opts.has_key?(:read) read = opts[:read] diff --git a/lib/mongo/error.rb b/lib/mongo/error.rb index 883fbee9a5..36296735d7 100644 --- a/lib/mongo/error.rb +++ b/lib/mongo/error.rb @@ -159,6 +159,7 @@ def write_concern_error_labels require 'mongo/error/invalid_document' require 'mongo/error/invalid_file' require 'mongo/error/invalid_file_revision' +require 'mongo/error/invalid_max_connecting' require 'mongo/error/invalid_min_pool_size' require 'mongo/error/invalid_read_option' require 'mongo/error/invalid_application_name' diff --git a/lib/mongo/error/invalid_max_connecting.rb b/lib/mongo/error/invalid_max_connecting.rb new file mode 100644 index 0000000000..759d4c1732 --- /dev/null +++ b/lib/mongo/error/invalid_max_connecting.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Copyright (C) 2014-present MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Error + # Exception that is raised when trying to create a client with an invalid + # max_connecting option. + class InvalidMaxConnecting < Error + # Instantiate the new exception. + def initialize(max_connecting) + super("Invalid max_connecting: #{max_connecting}. Please ensure that it is greater than zero. ") + end + end + end +end diff --git a/lib/mongo/server/connection_pool.rb b/lib/mongo/server/connection_pool.rb index 543ac6abde..dfbc718522 100644 --- a/lib/mongo/server/connection_pool.rb +++ b/lib/mongo/server/connection_pool.rb @@ -29,12 +29,16 @@ class ConnectionPool # The default max size for the connection pool. # # @since 2.9.0 - DEFAULT_MAX_SIZE = 20.freeze + DEFAULT_MAX_SIZE = 20 # The default min size for the connection pool. # # @since 2.9.0 - DEFAULT_MIN_SIZE = 0.freeze + DEFAULT_MIN_SIZE = 0 + + # The default maximum number of connections that can be connecting at + # any given time. + DEFAULT_MAX_CONNECTING = 2 # The default timeout, in seconds, to wait for a connection. # @@ -61,6 +65,11 @@ class ConnectionPool # # @option options [ Integer ] :max_size The maximum pool size. Setting # this option to zero creates an unlimited connection pool. + # @option options [ Integer ] :max_connecting The maximum number of + # connections that can be connecting simultaneously. The default is 2. + # This option should be increased if there are many threads that share + # same connection pool and the application is experiencing timeouts + # while waiting for connections to be established. # @option options [ Integer ] :max_pool_size Deprecated. # The maximum pool size. If max_size is also given, max_size and # max_pool_size must be identical. @@ -89,6 +98,7 @@ class ConnectionPool # any connections created by the pool. # # @since 2.0.0, API changed in 2.9.0 + def initialize(server, options = {}) unless server.is_a?(Server) raise ArgumentError, 'First argument must be a Server instance' @@ -146,7 +156,7 @@ def initialize(server, options = {}) # Condition variable to enforce the first check in check_out: max_pool_size. # This condition variable should be signaled when the number of # unavailable connections decreases (pending + pending_connections + - # available_connections). + # checked_out_connections). @size_cv = Mongo::ConditionVariable.new(@lock) # This represents the number of threads that have made it past the size_cv # gate but have not acquired a connection to add to the pending_connections @@ -157,7 +167,7 @@ def initialize(server, options = {}) # Thei condition variable should be signaled when the number of pending # connections decreases. @max_connecting_cv = Mongo::ConditionVariable.new(@lock) - @max_connecting = options.fetch(:max_connecting, 2) + @max_connecting = options.fetch(:max_connecting, DEFAULT_MAX_CONNECTING) ObjectSpace.define_finalizer(self, self.class.finalize(@available_connections, @pending_connections, @populator)) @@ -1074,7 +1084,8 @@ def raise_check_out_timeout!(connection_global_id) "from pool for #{@server.address}#{connection_global_id_msg} after #{wait_timeout} sec. " + "Connections in pool: #{@available_connections.length} available, " + "#{@checked_out_connections.length} checked out, " + - "#{@pending_connections.length + @connection_requests} pending " + + "#{@pending_connections.length} pending, " + + "#{@connection_requests} connections requests " + "(max size: #{max_size})" raise Error::ConnectionCheckOutTimeout.new(msg, address: @server.address) end @@ -1170,7 +1181,6 @@ def valid_available_connection?(connection, pid, connection_global_id) # one. If no connection exists and the pool is at max size, wait until # a connection is checked back into the pool. # - # @param [ Float ] deadline The deadline to get the connection. # @param [ Integer ] pid The current process id. # @param [ Integer ] connection_global_id The global id for the # connection to check out. @@ -1180,17 +1190,22 @@ def valid_available_connection?(connection, pid, connection_global_id) # @raise [ Error::PoolClosedError ] If the pool has been closed. # @raise [ Timeout::Error ] If the connection pool is at maximum size # and remains so for longer than the wait timeout. - def get_connection(deadline, pid, connection_global_id) + def get_connection(pid, connection_global_id) if connection = next_available_connection(connection_global_id) unless valid_available_connection?(connection, pid, connection_global_id) return nil end + # We've got a connection, so we decrement the number of connection + # requests. + # We do not need to signal condition variable here, because + # because the execution will continue, and we signal later. + @connection_requests -= 1 + # If the connection is connected, it's not considered a # "pending connection". The pending_connections list represents # the set of connections that are awaiting connection. unless connection.connected? - @connection_requests -= 1 @pending_connections << connection end return connection @@ -1207,6 +1222,9 @@ def get_connection(deadline, pid, connection_global_id) Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR ), ) + # We're going to raise, so we need to decrement the number of + # connection requests. + decrement_connection_requests_and_signal raise Error::MissingConnection.new end end @@ -1237,7 +1255,6 @@ def retrieve_and_connect_connection(connection_global_id) connection = nil @lock.synchronize do - # The first gate to checking out a connection. Make sure the number of # unavailable connections is less than the max pool size. until max_size == 0 || unavailable_connections < max_size @@ -1247,60 +1264,97 @@ def retrieve_and_connect_connection(connection_global_id) raise_if_not_ready! end @connection_requests += 1 + connection = wait_for_connection(connection_global_id, deadline) + end - while connection.nil? - # The second gate to checking out a connection. Make sure 1) there - # exists an available connection and 2) we are under max_connecting. - until @available_connections.any? || @pending_connections.length < @max_connecting - wait = deadline - Utils.monotonic_time - raise_check_out_timeout!(connection_global_id) if wait <= 0 - @max_connecting_cv.wait(wait) - raise_if_not_ready! - end + connect_or_raise(connection) unless connection.connected? - connection = get_connection(deadline, Process.pid, connection_global_id) - wait = deadline - Utils.monotonic_time - raise_check_out_timeout!(connection_global_id) if connection.nil? && wait <= 0 + @lock.synchronize do + @checked_out_connections << connection + if @pending_connections.include?(connection) + @pending_connections.delete(connection) end + @max_connecting_cv.signal + # no need to signal size_cv here since the number of unavailable + # connections is unchanged. end - begin - connect_connection(connection) - rescue Exception - # Handshake or authentication failed - @lock.synchronize do - if @pending_connections.include?(connection) - @pending_connections.delete(connection) - else - @connection_requests -= 1 + connection + end + + # Waits for a connection to become available, or raises is no connection + # becomes available before the timeout. + # @param [ Integer ] connection_global_id The global id for the + # connection to check out. + # @param [ Float ] deadline The time at which to stop waiting. + # + # @return [ Mongo::Server::Connection ] The checked out connection. + def wait_for_connection(connection_global_id, deadline) + connection = nil + while connection.nil? + # The second gate to checking out a connection. Make sure 1) there + # exists an available connection and 2) we are under max_connecting. + until @available_connections.any? || @pending_connections.length < @max_connecting + wait = deadline - Utils.monotonic_time + if wait <= 0 + # We are going to raise a timeout error, so the connection + # request is not going to be fulfilled. Decrement the counter + # here. + decrement_connection_requests_and_signal + raise_check_out_timeout!(connection_global_id) end - @max_connecting_cv.signal - @size_cv.signal + @max_connecting_cv.wait(wait) + # We do not need to decrement the connection_requests counter + # or signal here because the pool is not ready yet. + raise_if_not_ready! end - @populate_semaphore.signal - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( - @server.address, - Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR - ), - ) - raise + connection = get_connection(Process.pid, connection_global_id) + wait = deadline - Utils.monotonic_time + if connection.nil? && wait <= 0 + # connection is nil here, it means that get_connection method + # did not create a new connection; therefore, it did not decrease + # the connection_requests counter. We need to do it here. + decrement_connection_requests_and_signal + raise_check_out_timeout!(connection_global_id) + end end + connection + end + + # Connects a connection and raises an exception if the connection + # cannot be connected. + # This method also publish corresponding event and ensures that counters + # and condition variables are updated. + def connect_or_raise(connection) + connect_connection(connection) + rescue Exception + # Handshake or authentication failed @lock.synchronize do - @checked_out_connections << connection if @pending_connections.include?(connection) @pending_connections.delete(connection) - else - @connection_requests -= 1 end @max_connecting_cv.signal - # no need to signal size_cv here since the number of unavailable - # connections is unchanged. + @size_cv.signal end + @populate_semaphore.signal + publish_cmap_event( + Monitoring::Event::Cmap::ConnectionCheckOutFailed.new( + @server.address, + Monitoring::Event::Cmap::ConnectionCheckOutFailed::CONNECTION_ERROR + ), + ) + raise + end - connection + + # Decrement connection requests counter and signal the condition + # variables that the number of unavailable connections has decreased. + def decrement_connection_requests_and_signal + @connection_requests -= 1 + @max_connecting_cv.signal + @size_cv.signal end end end diff --git a/lib/mongo/uri/options_mapper.rb b/lib/mongo/uri/options_mapper.rb index b32149f84b..bc7e8f16dc 100644 --- a/lib/mongo/uri/options_mapper.rb +++ b/lib/mongo/uri/options_mapper.rb @@ -285,6 +285,7 @@ def self.uri_option(uri_key, name, **extra) uri_option 'maxStalenessSeconds', :max_staleness, group: :read, type: :max_staleness # Pool options + uri_option 'maxConnecting', :max_connecting, type: :integer uri_option 'minPoolSize', :min_pool_size, type: :integer uri_option 'maxPoolSize', :max_pool_size, type: :integer uri_option 'waitQueueTimeoutMS', :wait_queue_timeout, type: :ms diff --git a/lib/mongo/version.rb b/lib/mongo/version.rb index 80f916063a..eb0246b5af 100644 --- a/lib/mongo/version.rb +++ b/lib/mongo/version.rb @@ -20,5 +20,5 @@ module Mongo # The current version of the driver. # # @since 2.0.0 - VERSION = '2.19.0'.freeze + VERSION = '2.19.1'.freeze end diff --git a/spec/lite_spec_helper.rb b/spec/lite_spec_helper.rb index 0394b97f3b..7bd6675a70 100644 --- a/spec/lite_spec_helper.rb +++ b/spec/lite_spec_helper.rb @@ -16,7 +16,11 @@ TRANSACTIONS_TESTS = Dir.glob("#{CURRENT_PATH}/spec_tests/data/transactions/*.yml").sort TRANSACTIONS_API_TESTS = Dir.glob("#{CURRENT_PATH}/spec_tests/data/transactions_api/*.yml").sort CHANGE_STREAMS_TESTS = Dir.glob("#{CURRENT_PATH}/spec_tests/data/change_streams/*.yml").sort -CMAP_TESTS = Dir.glob("#{CURRENT_PATH}/spec_tests/data/cmap/*.yml").sort +CMAP_TESTS = Dir.glob("#{CURRENT_PATH}/spec_tests/data/cmap/*.yml").sort.select do |f| + # Skip tests that are flaky on JRuby. + # https://jira.mongodb.org/browse/RUBY-3292 + !defined?(JRUBY_VERSION) || !f.include?('pool-checkout-minPoolSize-connection-maxConnecting.yml') +end AUTH_TESTS = Dir.glob("#{CURRENT_PATH}/spec_tests/data/auth/*.yml").sort CLIENT_SIDE_ENCRYPTION_TESTS = Dir.glob("#{CURRENT_PATH}/spec_tests/data/client_side_encryption/*.yml").sort diff --git a/spec/mongo/client_construction_spec.rb b/spec/mongo/client_construction_spec.rb index 0ea5715e8c..442c0480b3 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -797,6 +797,32 @@ end end + context 'when max_connecting is provided' do + let(:client) do + new_local_client_nmio(SINGLE_CLIENT, options) + end + + context 'when max_connecting is a positive integer' do + let(:options) do + { max_connecting: 5 } + end + + it 'sets the max connecting' do + expect(client.options[:max_connecting]).to eq(options[:max_connecting]) + end + end + + context 'when max_connecting is a negative integer' do + let(:options) do + { max_connecting: -5 } + end + + it 'raises an exception' do + expect { client }.to raise_error(Mongo::Error::InvalidMaxConnecting) + end + end + end + context 'when min_pool_size is provided' do let(:client) { new_local_client_nmio(SINGLE_CLIENT, options) } @@ -998,6 +1024,28 @@ expect(client.options).to eq(expected_options) end + context 'when max_connecting is provided' do + context 'when max_connecting is a positive integer' do + let(:uri) do + 'mongodb://127.0.0.1:27017/?maxConnecting=10' + end + + it 'sets the max connecting' do + expect(client.options[:max_connecting]).to eq(10) + end + end + + context 'when max_connecting is a negative integer' do + let(:uri) do + 'mongodb://127.0.0.1:27017/?maxConnecting=0' + end + + it 'raises an exception' do + expect { client }.to raise_error(Mongo::Error::InvalidMaxConnecting) + end + end + end + context 'when min_pool_size is provided' do context 'when max_pool_size is provided' do context 'when the min_pool_size is greater than the max_pool_size' do diff --git a/spec/mongo/uri/srv_protocol_spec.rb b/spec/mongo/uri/srv_protocol_spec.rb index af21a43161..3efc41f910 100644 --- a/spec/mongo/uri/srv_protocol_spec.rb +++ b/spec/mongo/uri/srv_protocol_spec.rb @@ -1018,6 +1018,18 @@ include_examples "roundtrips string" end + context 'when providing maxConnecting' do + + let(:max_connecting) { 10 } + let(:options) { "maxConnecting=#{max_connecting}" } + + it 'sets the max connecting option' do + expect(uri.uri_options[:max_connecting]).to eq(max_connecting) + end + + include_examples "roundtrips string" + end + context 'when providing maxPoolSize' do let(:max_pool_size) { 10 } diff --git a/spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml b/spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml index 9c7c4cd43b..11acf17e76 100644 --- a/spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml +++ b/spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml @@ -1,7 +1,6 @@ version: 1 style: integration description: threads blocked by maxConnecting check out minPoolSize connections -# Remove the topology runOn requirement when cmap specs are adjusted for lbs runOn: - # required for blockConnection in fail point @@ -14,7 +13,7 @@ failPoint: failCommands: ["isMaster","hello"] closeConnection: false blockConnection: true - blockTimeMS: 500 + blockTimeMS: 1000 poolOptions: # allows both thread1 and the background thread to start opening connections concurrently minPoolSize: 2 @@ -35,9 +34,8 @@ operations: # - it is long enough to make sure that the background thread opens a connection before thread1 releases its permit; # - it is short enough to allow thread2 to become blocked acquiring a permit to open a connection, and then grab the connection # opened by the background thread, before the background thread releases its permit. - # This wait is not necessary in ruby since the background thread runs immediately. - name: wait - ms: 0 + ms: 200 - name: checkOut thread: thread1 - name: waitForEvent From ea09dbe32f242a6d6787676656ea1a4e8b8c9dbe Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 18 Jul 2023 08:21:40 -0600 Subject: [PATCH 144/198] RUBY-3288 Serverless tests fail when building greenlet on python 3.12 (#2741) * let's not run 2.18-stable specs against MDB 7+ * Revert "let's not run 2.18-stable specs against MDB 7+" Meant to do this on a branch :/ This reverts commit 1f3f7c76655b10803087ba24274bacf7c3468eec. * bump drivers-evergreen-tools --- .mod/drivers-evergreen-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index 9186b53aeb..6b328a119f 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit 9186b53aebe04390d68e348946a4cefc33c6964d +Subproject commit 6b328a119fa0ffdcd13e62b2c1c2259873a9076d From 9c907027992accc2f0e1bd9d6fff2d5256271bd7 Mon Sep 17 00:00:00 2001 From: Chris Cho Date: Fri, 21 Jul 2023 16:06:11 -0400 Subject: [PATCH 145/198] DOCSP-31496: fix API link (#2743) --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index e126a4d50a..79358a0ac5 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -62,7 +62,7 @@ For tutorials on Mongoid, see the `Mongoid Manual + API release-notes reference/additional-resources contribute From 98da223e4f1270c97d594e168a5799f98f904412 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 7 Aug 2023 10:46:51 +0200 Subject: [PATCH 146/198] RUBY-3209 Generate api docs (#2768) --- .gitignore | 1 + upload-api-docs | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100755 upload-api-docs diff --git a/.gitignore b/.gitignore index b127af9a1c..a60fb2cf76 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ data/* gemfiles/*.gemfile.lock .env.private* .env +build diff --git a/upload-api-docs b/upload-api-docs new file mode 100755 index 0000000000..1915efabb0 --- /dev/null +++ b/upload-api-docs @@ -0,0 +1,121 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'bundler/inline' + +gemfile true do + source 'https://rubygems.org' + gem 'nokogiri' + gem 'aws-sdk-s3' + gem 'yard' +end + +require 'aws-sdk-s3' +require 'optparse' +require 'yard' + +# This class contains logic for uploading API docs to S3. +class FileUploader + def initialize(options) + Aws.config.update({ + region: options[:region], + credentials: Aws::Credentials.new(options[:access_key], options[:secret_key]) + }) + Aws.use_bundled_cert! + @s3 = Aws::S3::Client.new + @bucket = options[:bucket] + @prefix = options[:prefix] + @docs_path = options[:docs_path] + end + + def upload_docs + Dir.glob("#{@docs_path}/**/*").each do |file| + next if File.directory?(file) + + upload_file(file, key(file)) + print '.' + $stdout.flush + end + puts "\nDone!" + end + + private + + def key(file) + File.join(@prefix, file.gsub("#{@docs_path}/", '')) + end + + def upload_file(file, key) + mime_type = mime_type(file) + @s3.put_object(bucket: @bucket, key: key, body: File.read(file), content_type: mime_type) + end + + def mime_type(file) + { + '.html' => 'text/html', + '.css' => 'text/css', + '.js' => 'application/javascript', + }.fetch(File.extname(file)) + end +end + +# This class contains logic for parsing CLI and ENV options. +class Options + def initialize + @options = {} + parse_cli_options! + parse_env_options! + @options[:prefix] = 'docs/ruby-driver/current/api' + @options[:docs_path] = 'build/public/current/api' + end + + def [](key) + @options[key] + end + + private + + def parse_cli_options! + OptionParser.new do |opts| + opts.banner = 'Usage: upload-api-docs [options]' + + opts.on('-b BUCKET', '--bucket=BUCKET', 'S3 Bucket to upload') do |b| + @options[:bucket] = b + end + opts.on('-r REGION', '--region=REGION', 'AWS region') do |r| + @options[:region] = r + end + end.parse! + %i[bucket region].each do |opt| + raise OptionParser::MissingArgument, "Option --#{opt} is required" unless @options[opt] + end + end + + def parse_env_options! + @options[:access_key] = ENV.fetch('DOCS_AWS_ACCESS_KEY_ID') do + raise ArgumentError, 'Please provide aws access key via DOCS_AWS_ACCESS_KEY_ID env variable' + end + @options[:secret_key] = ENV.fetch('DOCS_AWS_SECRET_ACCESS_KEY') do + raise ArgumentError, 'Please provide aws secret key via DOCS_AWS_SECRET_ACCESS_KEY env variable' + end + end +end + +def generate_docs(options) + YARD::CLI::Yardoc.run( + '.', + '--exclude', './.evergreen', + '--exclude', './.mod', + '--exclude', './examples', + '--exclude', './profile', + '--exclude', './release', + '--exclude', './spec', + '--readme', './README.md', + '-o', options[:docs_path] + ) +end + +options = Options.new +generate_docs(options) +FileUploader.new(options).upload_docs +return From bed8c0997e53fd2575461c5e5214c33ae4f48f19 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 14 Aug 2023 08:23:05 -0600 Subject: [PATCH 147/198] RUBY-3308 Fixing serverless test failures (#2770) * making these changes from a clean slate * propagate CRYPT_SHARED_LIB_PATH from the create-instance.sh script to run-serverless-tests.sh --- .evergreen/config.yml | 2 +- .evergreen/config/common.yml.erb | 2 +- .evergreen/run-tests-serverless.sh | 19 ++++++++++++------- .mod/drivers-evergreen-tools | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 27a1ab0ed2..499c5fa7d1 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -453,7 +453,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh + CRYPT_SHARED_LIB_PATH="${CRYPT_SHARED_LIB_PATH}" SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh pre: - func: "fetch source" diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 4febb453fa..836a1849f0 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -450,7 +450,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh + CRYPT_SHARED_LIB_PATH="${CRYPT_SHARED_LIB_PATH}" SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh pre: - func: "fetch source" diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index 4bcd983ade..32bd9ea110 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -16,14 +16,19 @@ export MONGODB_URI=`echo ${SERVERLESS_URI} | sed -r 's/mongodb\+srv:\/\//mongodb export TOPOLOGY="load-balanced" -python3 -u .evergreen/mongodl.py --component crypt_shared -V ${SERVERLESS_MONGODB_VERSION} --out `pwd`/csfle_lib --target `host_distro` || true -if test -f `pwd`/csfle_lib/lib/mongo_crypt_v1.so -then - echo Usinn crypt shared library version ${SERVERLESS_MONGODB_VERSION} - export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=`pwd`/csfle_lib/lib/mongo_crypt_v1.so +if [ -n "${CRYPT_SHARED_LIB_PATH}" ]; then + echo crypt_shared already present at ${CRYPT_SHARED_LIB_PATH} -- using this version + export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=$CRYPT_SHARED_LIB_PATH else - echo Failed to download crypt shared library - exit -1 + python3 -u .evergreen/mongodl.py --component crypt_shared -V ${SERVERLESS_MONGODB_VERSION} --out `pwd`/csfle_lib --target `host_distro` || true + if test -f `pwd`/csfle_lib/lib/mongo_crypt_v1.so + then + echo Usinn crypt shared library version ${SERVERLESS_MONGODB_VERSION} + export MONGO_RUBY_DRIVER_CRYPT_SHARED_LIB_PATH=`pwd`/csfle_lib/lib/mongo_crypt_v1.so + else + echo Failed to download crypt shared library + exit -1 + fi fi if ! ( test -f /etc/os-release & grep -q ^ID.*rhel /etc/os-release & grep -q ^VERSION_ID.*8.0 /etc/os-release ); then diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index 6b328a119f..0361b3fd3a 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit 6b328a119fa0ffdcd13e62b2c1c2259873a9076d +Subproject commit 0361b3fd3a9f1641518aa2f37d872d3346e8a450 From 4bde723e7e55bd5d31ad2b0f940ac4a68fd23e84 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 17 Aug 2023 08:08:30 -0600 Subject: [PATCH 148/198] RUBY-3314 Implement variable iterations for benchmarks (#2771) --- .gitignore | 1 + profile/benchmarking.rb | 97 +++++++++------------------------- profile/benchmarking/helper.rb | 51 +++++++++++++++--- profile/benchmarking/micro.rb | 35 ++++-------- 4 files changed, 79 insertions(+), 105 deletions(-) diff --git a/.gitignore b/.gitignore index a60fb2cf76..7af488f10d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ gemfiles/*.gemfile.lock .env.private* .env build +profile/benchmarking/data diff --git a/profile/benchmarking.rb b/profile/benchmarking.rb index a054b3906b..43797982b2 100644 --- a/profile/benchmarking.rb +++ b/profile/benchmarking.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # @@ -23,107 +22,59 @@ require_relative 'benchmarking/parallel' module Mongo - # Module with all functionality for running driver benchmark tests. # # @since 2.2.3 module Benchmarking - extend self - # The current path. - # - # @return [ String ] The current path. - # - # @since 2.2.3 - CURRENT_PATH = File.expand_path(File.dirname(__FILE__)).freeze - - # The path to data files used in Benchmarking tests. - # # @return [ String ] Path to Benchmarking test files. - # - # @since 2.2.3 - DATA_PATH = [CURRENT_PATH, 'benchmarking', 'data'].join('/').freeze + DATA_PATH = [ __dir__, 'benchmarking', 'data' ].join('/').freeze - # The file containing the single tweet document. - # - # @return [ String ] The file containing the tweet document. - # - # @since 2.2.3 - TWEET_DOCUMENT_FILE = [DATA_PATH, 'TWEET.json'].join('/').freeze + # @return [ String ] The file containing the single tweet document. + TWEET_DOCUMENT_FILE = [ DATA_PATH, 'TWEET.json' ].join('/').freeze - # The file containing the single small document. - # - # @return [ String ] The file containing the small document. - # - # @since 2.2.3 - SMALL_DOCUMENT_FILE = [DATA_PATH, 'SMALL_DOC.json'].join('/').freeze + # @return [ String ] The file containing the single small document. + SMALL_DOCUMENT_FILE = [ DATA_PATH, 'SMALL_DOC.json' ].join('/').freeze - # The file containing the single large document. - # - # @return [ String ] The file containing the large document. - # - # @since 2.2.3 - LARGE_DOCUMENT_FILE = [DATA_PATH, 'LARGE_DOC.json'].join('/').freeze + # @return [ String ] The file containing the single large document. + LARGE_DOCUMENT_FILE = [ DATA_PATH, 'LARGE_DOC.json' ].join('/').freeze - # The file to upload when testing GridFS. - # - # @return [ String ] The file containing the GridFS test data. - # - # @since 2.2.3 - GRIDFS_FILE = [DATA_PATH, 'GRIDFS_LARGE'].join('/').freeze + # @return [ String ] The file to upload when testing GridFS. + GRIDFS_FILE = [ DATA_PATH, 'GRIDFS_LARGE' ].join('/').freeze - # The file path and base name for the LDJSON files. - # # @return [ String ] The file path and base name for the LDJSON files. - # - # @since 2.2.3 - LDJSON_FILE_BASE = [DATA_PATH, 'LDJSON_MULTI', 'LDJSON'].join('/').freeze + LDJSON_FILE_BASE = [ DATA_PATH, 'LDJSON_MULTI', 'LDJSON' ].join('/').freeze - # The file path and base name for the outputted LDJSON files. - # - # @return [ String ] The file path and base name for the outputted LDJSON files. - # - # @since 2.2.3 - LDJSON_FILE_OUTPUT_BASE = [DATA_PATH, 'LDJSON_MULTI', 'output', 'LDJSON'].join('/').freeze + # @return [ String ] The file path and base name for the emitted LDJSON files. + LDJSON_FILE_OUTPUT_BASE = [ DATA_PATH, 'LDJSON_MULTI', 'output', 'LDJSON' ].join('/').freeze - # The file path and base name for the GRIDFS files to upload. - # # @return [ String ] The file path and base name for the GRIDFS files to upload. - # - # @since 2.2.3 - GRIDFS_MULTI_BASE = [DATA_PATH, 'GRIDFS_MULTI', 'file'].join('/').freeze + GRIDFS_MULTI_BASE = [ DATA_PATH, 'GRIDFS_MULTI', 'file' ].join('/').freeze - # The file path and base name for the outputted GRIDFS downloaded files. - # - # @return [ String ] The file path and base name for the outputted GRIDFS downloaded files. - # - # @since 2.2.3 - GRIDFS_MULTI_OUTPUT_BASE = [DATA_PATH, 'GRIDFS_MULTI', 'output', 'file-output'].join('/').freeze + # @return [ String ] The file path and base name for the emitted GRIDFS downloaded files. + GRIDFS_MULTI_OUTPUT_BASE = [ DATA_PATH, 'GRIDFS_MULTI', 'output', 'file-output' ].join('/').freeze - # The default number of test repetitions. - # # @return [ Integer ] The number of test repetitions. - # - # @since 2.2.3 - TEST_REPETITIONS = 100.freeze + TEST_REPETITIONS = 100 - # The number of default warmup repetitions of the test to do before - # recording times. - # - # @return [ Integer ] The default number of warmup repetitions. + # Convenience helper for loading the single tweet document. # - # @since 2.2.3 - WARMUP_REPETITIONS = 10.freeze - + # @return [ Hash ] a single parsed JSON document def tweet_document Benchmarking.load_file(TWEET_DOCUMENT_FILE).first end + # Convenience helper for loading the single small document. + # + # @return [ Hash ] a single parsed JSON document def small_document Benchmarking.load_file(SMALL_DOCUMENT_FILE).first end + # Convenience helper for loading the single large document. + # + # @return [ Hash ] a single parsed JSON document def large_document Benchmarking.load_file(LARGE_DOCUMENT_FILE).first end diff --git a/profile/benchmarking/helper.rb b/profile/benchmarking/helper.rb index 11d9f670ab..654eddbfd9 100644 --- a/profile/benchmarking/helper.rb +++ b/profile/benchmarking/helper.rb @@ -1,11 +1,8 @@ # frozen_string_literal: true -# rubocop:todo all module Mongo - # Helper functions used by benchmarking tasks module Benchmarking - extend self # Load a json file and represent each document as a Hash. @@ -19,7 +16,7 @@ module Benchmarking # # @since 2.2.3 def load_file(file_name) - File.open(file_name, "r") do |f| + File.open(file_name, 'r') do |f| f.each_line.collect do |line| parse_json(line) end @@ -39,8 +36,47 @@ def load_file(file_name) # @since 2.2.3 def parse_json(document) JSON.parse(document).tap do |doc| - if doc['_id'] && doc['_id']['$oid'] - doc['_id'] = BSON::ObjectId.from_string(doc['_id']['$oid']) + doc['_id'] = BSON::ObjectId.from_string(doc['_id']['$oid']) if doc['_id'] && doc['_id']['$oid'] + end + end + + # The spec requires that most benchmarks use a variable number of + # iterations, defined as follows: + # + # * iterations should loop for at least 1 minute cumulative execution + # time + # * iterations should stop after 100 iterations or 5 minutes cumulative + # execution time, whichever is shorter + # + # This method will yield once for each iteration. + # + # @param [ Integer ] max_iterations the maximum number of iterations to + # attempt (default: 100) + # @param [ Integer ] min_time the minimum number of seconds to spend + # iterating + # @param [ Integer ] max_time the maximum number of seconds to spend + # iterating. + # + # @return [ Array ] the timings for each iteration + def benchmark(max_iterations: Benchmarking::TEST_REPETITIONS, min_time: 60, max_time: 5 * 60, &block) + [].tap do |results| + iteration_count = 0 + cumulative_time = 0 + + loop do + timing = Benchmark.realtime(&block) + + iteration_count += 1 + cumulative_time += timing + results.push timing + + # always stop after the maximum time has elapsed, regardless of + # iteration count. + break if cumulative_time > max_time + + # otherwise, break if the minimum time has elapsed, and the maximum + # number of iterations have been reached. + break if cumulative_time >= min_time && iteration_count >= max_iterations end end end @@ -56,7 +92,8 @@ def parse_json(document) # # @since 2.2.3 def median(values) - values.sort![values.size/2-1] + i = (values.size / 2) - 1 + values.sort[i] end end end diff --git a/profile/benchmarking/micro.rb b/profile/benchmarking/micro.rb index 2f560bb310..99edf2ae10 100644 --- a/profile/benchmarking/micro.rb +++ b/profile/benchmarking/micro.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# rubocop:todo all # Copyright (C) 2015-2020 MongoDB Inc. # @@ -17,13 +16,11 @@ module Mongo module Benchmarking - # These tests focus on BSON encoding and decoding; they are client-side only and # do not involve any transmission of data to or from the server. # # @since 2.2.3 module Micro - extend self # Run a micro benchmark test. @@ -38,10 +35,11 @@ module Micro # # @since 2.2.3 def run(type, action, repetitions = Benchmarking::TEST_REPETITIONS) - file_name = type.to_s << "_bson.json" + file_name = type.to_s << '_bson.json' GC.disable - file_path = [Benchmarking::DATA_PATH, file_name].join('/') + file_path = [ Benchmarking::DATA_PATH, file_name ].join('/') puts "#{action} : #{send(action, file_path, repetitions)}" + GC.enable end # Run an encoding micro benchmark test. @@ -59,16 +57,8 @@ def encode(file_name, repetitions) data = Benchmarking.load_file(file_name) document = BSON::Document.new(data.first) - # WARMUP_REPETITIONS.times do - # doc.to_bson - # end - - results = repetitions.times.collect do - Benchmark.realtime do - 10_000.times do - document.to_bson - end - end + results = Benchmarking.benchmark(max_iterations: repetitions) do + 10_000.times { document.to_bson } end Benchmarking.median(results) end @@ -88,18 +78,13 @@ def decode(file_name, repetitions) data = Benchmarking.load_file(file_name) buffer = BSON::Document.new(data.first).to_bson - # WARMUP_REPETITIONS.times do - # BSON::Document.from_bson(buffers.shift) - # end - - results = repetitions.times.collect do - Benchmark.realtime do - 10_000.times do - BSON::Document.from_bson(buffer) - buffer.rewind! - end + results = Benchmarking.benchmark(max_iterations: repetitions) do + 10_000.times do + BSON::Document.from_bson(buffer) + buffer.rewind! end end + Benchmarking.median(results) end end From 0f866fd548c53810bb2f06197921d6d9a6a24cfd Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 21 Aug 2023 08:49:35 -0600 Subject: [PATCH 149/198] RUBY-3313 Record benchmark percentiles (#2772) * RUBY-3314 Implement variable iterations for benchmarks * report percentiles along with the median * rename Benchmarking::Micro to Benchmarking::BSON * refactoring to appease rubocop --- .rubocop.yml | 3 + Rakefile | 40 ++++++----- profile/benchmarking.rb | 2 +- profile/benchmarking/{micro.rb => bson.rb} | 78 ++++++++++++---------- profile/benchmarking/helper.rb | 72 ++++++++++++++++++-- 5 files changed, 139 insertions(+), 56 deletions(-) rename profile/benchmarking/{micro.rb => bson.rb} (50%) diff --git a/.rubocop.yml b/.rubocop.yml index 2b7a7f90f7..6cb04cd597 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -83,6 +83,9 @@ Style/Documentation: Exclude: - 'spec/**/*' +Style/FormatStringToken: + Enabled: false + Style/ModuleFunction: EnforcedStyle: extend_self diff --git a/Rakefile b/Rakefile index f64a790c12..49733ed4af 100644 --- a/Rakefile +++ b/Rakefile @@ -135,24 +135,32 @@ require_relative "profile/benchmarking" # Some require data files, available from the drivers team. See the comments above each task for details." namespace :benchmark do - desc "Run the driver benchmark tests." - - namespace :micro do - desc "Run the common driver micro benchmarking tests" + desc "Run the bson benchmarking tests" + task :bson do + puts "BSON BENCHMARK" + Mongo::Benchmarking.report({ + bson: Mongo::Benchmarking::BSON.run_all({ + flat: %i[ encode decode ], + deep: %i[ encode decode ], + full: %i[ encode decode ], + }) + }) + end + namespace :bson do namespace :flat do desc "Benchmarking for flat bson documents." # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called flat_bson.json. task :encode do - puts "MICRO BENCHMARK:: FLAT:: ENCODE" - Mongo::Benchmarking::Micro.run(:flat, :encode) + puts "BSON BENCHMARK :: FLAT :: ENCODE" + Mongo::Benchmarking.report({ bson: { flat: { encode: Mongo::Benchmarking::BSON.run(:flat, :encode) } } }) end # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called flat_bson.json. task :decode do - puts "MICRO BENCHMARK:: FLAT:: DECODE" - Mongo::Benchmarking::Micro.run(:flat, :decode) + puts "BSON BENCHMARK :: FLAT :: DECODE" + Mongo::Benchmarking.report({ bson: { flat: { decode: Mongo::Benchmarking::BSON.run(:flat, :decode) } } }) end end @@ -161,14 +169,14 @@ namespace :benchmark do # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called deep_bson.json. task :encode do - puts "MICRO BENCHMARK:: DEEP:: ENCODE" - Mongo::Benchmarking::Micro.run(:deep, :encode) + puts "BSON BENCHMARK :: DEEP :: ENCODE" + Mongo::Benchmarking.report({ bson: { deep: { encode: Mongo::Benchmarking::BSON.run(:deep, :encode) } } }) end # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called deep_bson.json. task :decode do - puts "MICRO BENCHMARK:: DEEP:: DECODE" - Mongo::Benchmarking::Micro.run(:deep, :decode) + puts "BSON BENCHMARK :: DEEP :: DECODE" + Mongo::Benchmarking.report({ bson: { deep: { decode: Mongo::Benchmarking::BSON.run(:deep, :decode) } } }) end end @@ -177,14 +185,14 @@ namespace :benchmark do # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called full_bson.json. task :encode do - puts "MICRO BENCHMARK:: FULL:: ENCODE" - Mongo::Benchmarking::Micro.run(:full, :encode) + puts "BSON BENCHMARK :: FULL :: ENCODE" + Mongo::Benchmarking.report({ bson: { full: { encode: Mongo::Benchmarking::BSON.run(:full, :encode) } } }) end # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called full_bson.json. task :decode do - puts "MICRO BENCHMARK:: FULL:: DECODE" - Mongo::Benchmarking::Micro.run(:full, :decode) + puts "BSON BENCHMARK :: FULL :: DECODE" + Mongo::Benchmarking.report({ bson: { full: { decode: Mongo::Benchmarking::BSON.run(:full, :decode) } } }) end end end diff --git a/profile/benchmarking.rb b/profile/benchmarking.rb index 43797982b2..08f6b20423 100644 --- a/profile/benchmarking.rb +++ b/profile/benchmarking.rb @@ -16,7 +16,7 @@ require 'benchmark' require_relative 'benchmarking/helper' -require_relative 'benchmarking/micro' +require_relative 'benchmarking/bson' require_relative 'benchmarking/single_doc' require_relative 'benchmarking/multi_doc' require_relative 'benchmarking/parallel' diff --git a/profile/benchmarking/micro.rb b/profile/benchmarking/bson.rb similarity index 50% rename from profile/benchmarking/micro.rb rename to profile/benchmarking/bson.rb index 99edf2ae10..0a3667b261 100644 --- a/profile/benchmarking/micro.rb +++ b/profile/benchmarking/bson.rb @@ -18,74 +18,82 @@ module Mongo module Benchmarking # These tests focus on BSON encoding and decoding; they are client-side only and # do not involve any transmission of data to or from the server. - # - # @since 2.2.3 - module Micro + module BSON extend self - # Run a micro benchmark test. + # Runs all of the benchmarks specified by the given mapping. + # + # @example Run a collection of benchmarks. + # Benchmarking::BSON.run_all( + # flat: %i[ encode decode ], + # deep: %i[ encode decode ], + # full: %i[ encode decode ] + # ) + # + # @return [ Hash ] a hash of the results for each benchmark + def run_all(map) + {}.tap do |results| + map.each do |type, actions| + results[type] = {} + + actions.each do |action| + results[type][action] = run(type, action) + end + end + end + end + + # Run a BSON benchmark test. # # @example Run a test. - # Benchmarking::Micro.run(:flat) + # Benchmarking::BSON.run(:flat) # # @param [ Symbol ] type The type of test to run. - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The test results. + # @param [ :encode | :decode ] action The action to perform. # - # @since 2.2.3 - def run(type, action, repetitions = Benchmarking::TEST_REPETITIONS) - file_name = type.to_s << '_bson.json' - GC.disable - file_path = [ Benchmarking::DATA_PATH, file_name ].join('/') - puts "#{action} : #{send(action, file_path, repetitions)}" - GC.enable + # @return [ Array ] The test results for each iteration + def run(type, action) + file_path = File.join(Benchmarking::DATA_PATH, "#{type}_bson.json") + Benchmarking.without_gc { send(action, file_path) } end - # Run an encoding micro benchmark test. + # Run an encoding BSON benchmark test. # # @example Run an encoding test. - # Benchmarking::Micro.encode(file_name) + # Benchmarking::BSON.encode(file_name) # # @param [ String ] file_name The name of the file with data for the test. # @param [ Integer ] repetitions The number of test repetitions. # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def encode(file_name, repetitions) + # @return [ Array ] The list of the results for each iteration + def encode(file_name) data = Benchmarking.load_file(file_name) - document = BSON::Document.new(data.first) + document = ::BSON::Document.new(data.first) - results = Benchmarking.benchmark(max_iterations: repetitions) do + Benchmarking.benchmark do 10_000.times { document.to_bson } end - Benchmarking.median(results) end - # Run a decoding micro benchmark test. + # Run a decoding BSON benchmark test. # # @example Run an decoding test. - # Benchmarking::Micro.decode(file_name) + # Benchmarking::BSON.decode(file_name) # # @param [ String ] file_name The name of the file with data for the test. # @param [ Integer ] repetitions The number of test repetitions. # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def decode(file_name, repetitions) + # @return [ Array ] The list of the results for each iteration + def decode(file_name) data = Benchmarking.load_file(file_name) - buffer = BSON::Document.new(data.first).to_bson + buffer = ::BSON::Document.new(data.first).to_bson - results = Benchmarking.benchmark(max_iterations: repetitions) do + Benchmarking.benchmark do 10_000.times do - BSON::Document.from_bson(buffer) + ::BSON::Document.from_bson(buffer) buffer.rewind! end end - - Benchmarking.median(results) end end end diff --git a/profile/benchmarking/helper.rb b/profile/benchmarking/helper.rb index 654eddbfd9..309932964c 100644 --- a/profile/benchmarking/helper.rb +++ b/profile/benchmarking/helper.rb @@ -36,7 +36,7 @@ def load_file(file_name) # @since 2.2.3 def parse_json(document) JSON.parse(document).tap do |doc| - doc['_id'] = BSON::ObjectId.from_string(doc['_id']['$oid']) if doc['_id'] && doc['_id']['$oid'] + doc['_id'] = ::BSON::ObjectId.from_string(doc['_id']['$oid']) if doc['_id'] && doc['_id']['$oid'] end end @@ -81,19 +81,83 @@ def benchmark(max_iterations: Benchmarking::TEST_REPETITIONS, min_time: 60, max_ end end + # Formats and displays a report of the given results. + # + # @param [ Hash ] results the results of a benchmarking run. + # @param [ Integer ] indent how much the report should be indented. + # @param [ Array ] percentiles the percentile values to report + def report(results, indent: 0, percentiles: [ 10, 25, 50, 75, 90, 95, 98, 99 ]) + results.each do |key, value| + puts format('%*s%s:', indent, '', key) + if value.is_a?(Hash) + report(value, indent: indent + 2, percentiles: percentiles) + else + report_result(value, indent, percentiles) + end + end + end + + # A utility class for returning the list item at a given percentile + # value. + class Percentiles + # @return [ Array ] the sorted list of numbers to consider + attr_reader :list + + # Create a new Percentiles object that encapsulates the given list of + # numbers. + # + # @param [ Array ] list the list of numbers to considier + def initialize(list) + @list = list.sort + end + + # Finds and returns the element in the list that represents the given + # percentile value. + # + # @param [ Number ] percentile a number in the range [1,100] + # + # @return [ Number ] the element of the list for the given percentile. + def [](percentile) + i = (list.size * percentile / 100.0).ceil - 1 + list[i] + end + end + # Get the median of values in a list. # # @example Get the median. # Benchmarking.median(values) # - # @param [ Array ] The values to get the median of. + # @param [ Array ] values The values to get the median of. # # @return [ Numeric ] The median of the list. - # - # @since 2.2.3 def median(values) i = (values.size / 2) - 1 values.sort[i] end + + # Runs a given block with GC disabled. + def without_gc + GC.disable + yield + ensure + GC.enable + end + + private + + # Formats and displays the results of a single benchmark run. + # + # @param [ Array ] results the results to report + # @param [ Integer ] indent how much the report should be indented + # @param [ Array ] percentiles the percentiles to report + def report_result(results, indent, percentiles) + ps = Percentiles.new(results) + puts format('%*smedian: %g', indent + 2, '', ps[50]) + puts format('%*spercentiles:', indent + 2, '') + percentiles.each do |pct| + puts format('%*s%g: %g', indent + 4, '', pct, ps[pct]) + end + end end end From 532033695460a356df2e39b463ff6f8c28f32c1f Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 22 Aug 2023 08:19:50 -0600 Subject: [PATCH 150/198] RUBY-3315 BSON benchmark scoring (#2773) * RUBY-3314 Implement variable iterations for benchmarks * report percentiles along with the median * rename Benchmarking::Micro to Benchmarking::BSON * refactoring to appease rubocop * RUBY-3315 benchmark scoring * fix merge artifact --- profile/benchmarking/bson.rb | 57 +++++++++++++++++++++++++++-- profile/benchmarking/helper.rb | 23 ++---------- profile/benchmarking/percentiles.rb | 31 ++++++++++++++++ profile/benchmarking/summary.rb | 56 ++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 profile/benchmarking/percentiles.rb create mode 100644 profile/benchmarking/summary.rb diff --git a/profile/benchmarking/bson.rb b/profile/benchmarking/bson.rb index 0a3667b261..88cb979ab2 100644 --- a/profile/benchmarking/bson.rb +++ b/profile/benchmarking/bson.rb @@ -14,6 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +require_relative 'percentiles' +require_relative 'summary' + module Mongo module Benchmarking # These tests focus on BSON encoding and decoding; they are client-side only and @@ -43,6 +46,20 @@ def run_all(map) end end + # As defined by the spec, the score for a given benchmark is the + # size of the task (in MB) divided by the median wall clock time. + # + # @param [ Symbol ] type the type of the task + # @param [ Mongo::Benchmarking::Percentiles ] percentiles the Percentiles + # object to query for the median time. + # @param [ Numeric ] scale the number of times the operation is performed + # per iteration, used to scale the task size. + # + # @return [ Numeric ] the score for the given task. + def score_for(type, percentiles, scale: 10_000) + task_size(type, scale) / percentiles[50] + end + # Run a BSON benchmark test. # # @example Run a test. @@ -51,10 +68,14 @@ def run_all(map) # @param [ Symbol ] type The type of test to run. # @param [ :encode | :decode ] action The action to perform. # - # @return [ Array ] The test results for each iteration + # @return [ Hash<:timings,:percentiles,:score> ] The test results for + # the requested benchmark. def run(type, action) - file_path = File.join(Benchmarking::DATA_PATH, "#{type}_bson.json") - Benchmarking.without_gc { send(action, file_path) } + timings = Benchmarking.without_gc { send(action, file_for(type)) } + percentiles = Percentiles.new(timings) + score = score_for(type, percentiles) + + Summary.new(timings, percentiles, score) end # Run an encoding BSON benchmark test. @@ -95,6 +116,36 @@ def decode(file_name) end end end + + private + + # The path to the source file for the given task type. + # + # @param [ Symbol ] type the task type + # + # @return [ String ] the path to the source file. + def file_for(type) + File.join(Benchmarking::DATA_PATH, "#{type}_bson.json") + end + + # As defined by the spec, the size of a BSON task is the size of the + # file, multipled by the scale (the number of times the file is processed + # per iteration), divided by a million. + # + # "the dataset size for a task is the size of the single-document source + # file...times 10,000 operations" + # + # "Each task will have defined for it an associated size in + # megabytes (MB)" + # + # @param [ Symbol ] type the type of the task + # @param [ Numeric ] scale the number of times the operation is performed + # per iteration (e.g. 10,000) + # + # @return [ Numeric ] the score for the task, reported in MB + def task_size(type, scale) + File.size(file_for(type)) * scale / 1_000_000.0 + end end end end diff --git a/profile/benchmarking/helper.rb b/profile/benchmarking/helper.rb index 309932964c..856cb7659b 100644 --- a/profile/benchmarking/helper.rb +++ b/profile/benchmarking/helper.rb @@ -89,10 +89,11 @@ def benchmark(max_iterations: Benchmarking::TEST_REPETITIONS, min_time: 60, max_ def report(results, indent: 0, percentiles: [ 10, 25, 50, 75, 90, 95, 98, 99 ]) results.each do |key, value| puts format('%*s%s:', indent, '', key) - if value.is_a?(Hash) - report(value, indent: indent + 2, percentiles: percentiles) + + if value.respond_to?(:summary) + puts value.summary(indent + 2, percentiles) else - report_result(value, indent, percentiles) + report(value, indent: indent + 2, percentiles: percentiles) end end end @@ -143,21 +144,5 @@ def without_gc ensure GC.enable end - - private - - # Formats and displays the results of a single benchmark run. - # - # @param [ Array ] results the results to report - # @param [ Integer ] indent how much the report should be indented - # @param [ Array ] percentiles the percentiles to report - def report_result(results, indent, percentiles) - ps = Percentiles.new(results) - puts format('%*smedian: %g', indent + 2, '', ps[50]) - puts format('%*spercentiles:', indent + 2, '') - percentiles.each do |pct| - puts format('%*s%g: %g', indent + 4, '', pct, ps[pct]) - end - end end end diff --git a/profile/benchmarking/percentiles.rb b/profile/benchmarking/percentiles.rb new file mode 100644 index 0000000000..aeebe9d1d9 --- /dev/null +++ b/profile/benchmarking/percentiles.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Mongo + module Benchmarking + # A utility class for returning the list item at a given percentile + # value. + class Percentiles + # @return [ Array ] the sorted list of numbers to consider + attr_reader :list + + # Create a new Percentiles object that encapsulates the given list of + # numbers. + # + # @param [ Array ] list the list of numbers to considier + def initialize(list) + @list = list.sort + end + + # Finds and returns the element in the list that represents the given + # percentile value. + # + # @param [ Number ] percentile a number in the range [1,100] + # + # @return [ Number ] the element of the list for the given percentile. + def [](percentile) + i = (list.size * percentile / 100.0).ceil - 1 + list[i] + end + end + end +end diff --git a/profile/benchmarking/summary.rb b/profile/benchmarking/summary.rb new file mode 100644 index 0000000000..93fddf5435 --- /dev/null +++ b/profile/benchmarking/summary.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Mongo + module Benchmarking + # A utility class for encapsulating the summary information for a + # benchmark, including behaviors for reporting on the summary. + class Summary + # @return [ Array ] the timings of each iteration in the + # benchmark + attr_reader :timings + + # @return [ Percentiles ] the percentiles object for querying the + # timing at a given percentile value. + attr_reader :percentiles + + # @return [ Numeric ] the composite score for the benchmark + attr_reader :score + + # Construct a new Summary object with the given timings, percentiles, + # and score. + # + # @param [ Array ] timings the timings of each iteration in the + # benchmark + # @param [ Percentiles ] percentiles the percentiles object for querying + # the timing at a given percentile value + # @param [ Numeric ] score the composite score for the benchmark + def initialize(timings, percentiles, score) + @timings = timings + @percentiles = percentiles + @score = score + end + + # @return [ Numeric ] the median timing for the benchmark. + def median + percentiles[50] + end + + # Formats and displays the results of a single benchmark run. + # + # @param [ Integer ] indent how much the report should be indented + # @param [ Array ] points the percentile points to report + # + # @return [ String ] a YAML-formatted summary + def summary(indent, points) + [].tap do |lines| + lines << format('%*sscore: %g', indent, '', score) + lines << format('%*smedian: %g', indent, '', median) + lines << format('%*spercentiles:', indent, '') + points.each do |pct| + lines << format('%*s%g: %g', indent + 2, '', pct, percentiles[pct]) + end + end.join("\n") + end + end + end +end From d149d6afeab61f6069f3ab6efb24198ddeb6ffac Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 25 Aug 2023 21:27:39 +0300 Subject: [PATCH 151/198] RUBY-3242 Add spec tests for runCommand (#2776) * RUBY-3242 Add spec tests for runCommand * Fix runner * Add a bit of a comment --- spec/runners/unified/assertions.rb | 19 +- .../data/run_command_unified/runCommand.yml | 319 ++++++++++++++++++ spec/spec_tests/run_command_unified_spec.rb | 13 + 3 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 spec/spec_tests/data/run_command_unified/runCommand.yml create mode 100644 spec/spec_tests/run_command_unified_spec.rb diff --git a/spec/runners/unified/assertions.rb b/spec/runners/unified/assertions.rb index 722b11ad83..908fe80fbb 100644 --- a/spec/runners/unified/assertions.rb +++ b/spec/runners/unified/assertions.rb @@ -251,11 +251,11 @@ def assert_matches(actual, expected, msg) end when Hash if expected.keys == %w($$unsetOrMatches) && expected.values.first.keys == %w(insertedId) - actual_v = actual.inserted_id + actual_v = get_actual_value(actual, 'inserted_id') expected_v = expected.values.first.values.first assert_value_matches(actual_v, expected_v, 'inserted_id') elsif expected.keys == %w(insertedId) - actual_v = actual.inserted_id + actual_v = get_actual_value(actual, 'inserted_id') expected_v = expected.values.first assert_value_matches(actual_v, expected_v, 'inserted_id') else @@ -270,7 +270,7 @@ def assert_matches(actual, expected, msg) if k.start_with?('$$') assert_value_matches(actual, expected, k) else - actual_v = actual[k] + actual_v = get_actual_value(actual, k) if Hash === expected_v && expected_v.length == 1 && expected_v.keys.first.start_with?('$$') assert_value_matches(actual_v, expected_v, k) else @@ -290,6 +290,19 @@ def assert_matches(actual, expected, msg) end end + # The actual value may be of different types depending on the operation. + # In order to avoid having to write a lot of code to handle the different + # types, we use this method to get the actual value. + def get_actual_value(actual, key) + if Hash === actual + actual[key] + elsif Mongo::Operation::Result === actual && !actual.respond_to?(key.to_sym) + actual.documents.first[key] + else + actual.send(key) + end + end + def assert_type(object, type) ok = [*type].reduce(false) { |acc, x| acc || type_matches?(object, x) } diff --git a/spec/spec_tests/data/run_command_unified/runCommand.yml b/spec/spec_tests/data/run_command_unified/runCommand.yml new file mode 100644 index 0000000000..9b0bf1ad63 --- /dev/null +++ b/spec/spec_tests/data/run_command_unified/runCommand.yml @@ -0,0 +1,319 @@ +description: runCommand + +schemaVersion: "1.3" + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: [commandStartedEvent] + - database: + id: &db db + client: *client + databaseName: *db + - collection: + id: &collection collection + database: *db + collectionName: *collection + - database: + id: &dbWithRC dbWithRC + client: *client + databaseName: *dbWithRC + databaseOptions: + readConcern: { level: 'local' } + - database: + id: &dbWithWC dbWithWC + client: *client + databaseName: *dbWithWC + databaseOptions: + writeConcern: { w: 0 } + - session: + id: &session session + client: *client + # Stable API test + - client: + id: &clientWithStableApi clientWithStableApi + observeEvents: [commandStartedEvent] + serverApi: + version: "1" + strict: true + - database: + id: &dbWithStableApi dbWithStableApi + client: *clientWithStableApi + databaseName: *dbWithStableApi + +initialData: +- collectionName: *collection + databaseName: *db + documents: [] + +tests: + - description: always attaches $db and implicit lsid to given command and omits default readPreference + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $db: *db + lsid: { $$exists: true } + $readPreference: { $$exists: false } + commandName: ping + + - description: always gossips the $clusterTime on the sent command + runOnRequirements: + # Only replicasets and sharded clusters have a $clusterTime + - topologies: [ replicaset, sharded ] + operations: + # We have to run one command to obtain a clusterTime to gossip + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectResult: { ok: 1 } + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + commandName: ping + # Only check the shape of the second ping which should have the $clusterTime received from the first operation + - commandStartedEvent: + command: + ping: 1 + $clusterTime: { $$exists: true } + commandName: ping + + - description: attaches the provided session lsid to given command + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + session: *session + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + lsid: { $$sessionLsid: *session } + $db: *db + commandName: ping + + - description: attaches the provided $readPreference to given command + runOnRequirements: + # Exclude single topology, which is most likely a standalone server + - topologies: [ replicaset, load-balanced, sharded ] + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + readPreference: &readPreference { mode: 'nearest' } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $readPreference: *readPreference + $db: *db + commandName: ping + + - description: does not attach $readPreference to given command on standalone + runOnRequirements: + # This test assumes that the single topology contains a standalone server; + # however, it is possible for a single topology to contain a direct + # connection to another server type. + # See: https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst#topology-type-single + - topologies: [ single ] + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + readPreference: { mode: 'nearest' } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $readPreference: { $$exists: false } + $db: *db + commandName: ping + + - description: does not attach primary $readPreference to given command + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + readPreference: { mode: 'primary' } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $readPreference: { $$exists: false } + $db: *db + commandName: ping + + - description: does not inherit readConcern specified at the db level + operations: + - name: runCommand + object: *dbWithRC + # Test with a command that supports a readConcern option. + # expectResult is intentionally omitted because some drivers + # may automatically convert command responses into cursors. + arguments: + commandName: aggregate + command: { aggregate: *collection, pipeline: [], cursor: {} } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collection + readConcern: { $$exists: false } + $db: *dbWithRC + commandName: aggregate + + - description: does not inherit writeConcern specified at the db level + operations: + - name: runCommand + object: *dbWithWC + arguments: + commandName: insert + command: + insert: *collection + documents: [ { foo: 'bar' } ] + ordered: true + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collection + writeConcern: { $$exists: false } + $db: *dbWithWC + commandName: insert + + - description: does not retry retryable errors on given command + runOnRequirements: + - minServerVersion: "4.2" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ping] + closeConnection: true + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectError: + isClientError: true + + - description: attaches transaction fields to given command + runOnRequirements: + - minServerVersion: "4.0" + topologies: [ replicaset ] + - minServerVersion: "4.2" + topologies: [ sharded, load-balanced ] + operations: + - name: withTransaction + object: *session + arguments: + callback: + - name: runCommand + object: *db + arguments: + session: *session + commandName: insert + command: + insert: *collection + documents: [ { foo: 'transaction' } ] + ordered: true + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collection + documents: [ { foo: 'transaction' } ] + ordered: true + lsid: { $$sessionLsid: *session } + txnNumber: 1 + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *db + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session } + txnNumber: 1 + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + readConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + + - description: attaches apiVersion fields to given command when stableApi is configured on the client + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: runCommand + object: *dbWithStableApi + arguments: + commandName: ping + command: + ping: 1 + expectResult: { ok: 1 } + expectEvents: + - client: *clientWithStableApi + events: + - commandStartedEvent: + command: + ping: 1 + $db: *dbWithStableApi + apiVersion: "1" + apiStrict: true + apiDeprecationErrors: { $$unsetOrMatches: false } + commandName: ping diff --git a/spec/spec_tests/run_command_unified_spec.rb b/spec/spec_tests/run_command_unified_spec.rb new file mode 100644 index 0000000000..50878ca4fe --- /dev/null +++ b/spec/spec_tests/run_command_unified_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +# rubocop:todo all + +require 'spec_helper' + +require 'runners/unified' + +base = "#{CURRENT_PATH}/spec_tests/data/run_command_unified" +RUN_COMMAND_UNIFIED_TESTS = Dir.glob("#{base}/**/*.yml").sort + +describe 'runCommand unified spec tests' do + define_unified_spec_tests(base, RUN_COMMAND_UNIFIED_TESTS) +end From b03052a02e2c0fc1e5ba45be25f0b95c292f9566 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 25 Aug 2023 21:29:33 +0300 Subject: [PATCH 152/198] RUBY-2748 Retry reads/writes on another mongos (#2717) * First implementation attempt * Adjust spec requirement * Improve specs * Add write spec * Add documenting comments * Use proper error codes in prose tests --- lib/mongo/retryable.rb | 4 +- lib/mongo/retryable/read_worker.rb | 13 +- lib/mongo/retryable/write_worker.rb | 8 +- lib/mongo/server_selector/base.rb | 38 +++- .../retryable_reads_errors_spec.rb | 169 +++++++++++++++++- .../retryable_writes_errors_spec.rb | 156 ++++++++++++++++ 6 files changed, 363 insertions(+), 25 deletions(-) diff --git a/lib/mongo/retryable.rb b/lib/mongo/retryable.rb index c6330bf486..2508e13efd 100644 --- a/lib/mongo/retryable.rb +++ b/lib/mongo/retryable.rb @@ -46,8 +46,8 @@ module Retryable # @api private # # @return [ Mongo::Server ] A server matching the server preference. - def select_server(cluster, server_selector, session) - server_selector.select_server(cluster, nil, session) + def select_server(cluster, server_selector, session, failed_server = nil) + server_selector.select_server(cluster, nil, session, deprioritized: [failed_server].compact) end # Returns the read worker for handling retryable reads. diff --git a/lib/mongo/retryable/read_worker.rb b/lib/mongo/retryable/read_worker.rb index 763fa0f75b..a82e67a051 100644 --- a/lib/mongo/retryable/read_worker.rb +++ b/lib/mongo/retryable/read_worker.rb @@ -190,12 +190,13 @@ def deprecated_legacy_read_with_retry(&block) # # @return [ Result ] The result of the operation. def modern_read_with_retry(session, server_selector, &block) - yield select_server(cluster, server_selector, session) + server = select_server(cluster, server_selector, session) + yield server rescue *retryable_exceptions, Error::OperationFailure, Auth::Unauthorized, Error::PoolError => e e.add_notes('modern retry', 'attempt 1') raise e if session.in_transaction? raise e if !is_retryable_exception?(e) && !e.write_retryable? - retry_read(e, session, server_selector, &block) + retry_read(e, session, server_selector, failed_server: server, &block) end # Attempts to do a "legacy" read with retry. The operation will be @@ -257,12 +258,14 @@ def read_without_retry(session, server_selector, &block) # being run on. # @param [ Mongo::ServerSelector::Selectable ] server_selector Server # selector for the operation. + # @param [ Mongo::Server ] failed_server The server on which the original + # operation failed. # @param [ Proc ] block The block to execute. # # @return [ Result ] The result of the operation. - def retry_read(original_error, session, server_selector, &block) + def retry_read(original_error, session, server_selector, failed_server: nil, &block) begin - server = select_server(cluster, server_selector, session) + server = select_server(cluster, server_selector, session, failed_server) rescue Error, Error::AuthError => e original_error.add_note("later retry failed: #{e.class}: #{e}") raise original_error @@ -289,8 +292,6 @@ def retry_read(original_error, session, server_selector, &block) raise original_error end end - end - end end diff --git a/lib/mongo/retryable/write_worker.rb b/lib/mongo/retryable/write_worker.rb index c21265ed56..303ff3af06 100644 --- a/lib/mongo/retryable/write_worker.rb +++ b/lib/mongo/retryable/write_worker.rb @@ -240,7 +240,7 @@ def modern_write_with_retry(session, server, context, &block) # Context#with creates a new context, which is not necessary here # but the API is less prone to misuse this way. - retry_write(e, txn_num, context: context.with(is_retry: true), &block) + retry_write(e, txn_num, context: context.with(is_retry: true), failed_server: server, &block) end # Called after a failed write, this will retry the write no more than @@ -250,9 +250,11 @@ def modern_write_with_retry(session, server, context, &block) # retry. # @param [ Number ] txn_num The transaction number. # @param [ Operation::Context ] context The context for the operation. + # @param [ Mongo::Server ] failed_server The server on which the original + # operation failed. # # @return [ Result ] The result of the operation. - def retry_write(original_error, txn_num, context:, &block) + def retry_write(original_error, txn_num, context:, failed_server: nil, &block) session = context.session # We do not request a scan of the cluster here, because error handling @@ -260,7 +262,7 @@ def retry_write(original_error, txn_num, context:, &block) # server description and/or topology as necessary (specifically, # a socket error or a not master error should have marked the respective # server unknown). Here we just need to wait for server selection. - server = select_server(cluster, ServerSelector.primary, session) + server = select_server(cluster, ServerSelector.primary, session, failed_server) unless server.retry_writes? # Do not need to add "modern retry" here, it should already be on diff --git a/lib/mongo/server_selector/base.rb b/lib/mongo/server_selector/base.rb index b883785129..10eb478449 100644 --- a/lib/mongo/server_selector/base.rb +++ b/lib/mongo/server_selector/base.rb @@ -164,6 +164,10 @@ def ==(other) # for mongos pinning. Added in version 2.10.0. # @param [ true | false ] write_aggregation Whether we need a server that # supports writing aggregations (e.g. with $merge/$out) on secondaries. + # @param [ Array ] deprioritized A list of servers that should + # be selected from only if no other servers are available. This is + # used to avoid selecting the same server twice in a row when + # retrying a command. # # @return [ Mongo::Server ] A server matching the server preference. # @@ -174,8 +178,8 @@ def ==(other) # lint mode is enabled. # # @since 2.0.0 - def select_server(cluster, ping = nil, session = nil, write_aggregation: false) - select_server_impl(cluster, ping, session, write_aggregation).tap do |server| + def select_server(cluster, ping = nil, session = nil, write_aggregation: false, deprioritized: []) + select_server_impl(cluster, ping, session, write_aggregation, deprioritized).tap do |server| if Lint.enabled? && !server.pool.ready? raise Error::LintError, 'Server selector returning a server with a pool which is not ready' end @@ -183,7 +187,7 @@ def select_server(cluster, ping = nil, session = nil, write_aggregation: false) end # Parameters and return values are the same as for select_server. - private def select_server_impl(cluster, ping, session, write_aggregation) + private def select_server_impl(cluster, ping, session, write_aggregation, deprioritized) if cluster.topology.is_a?(Cluster::Topology::LoadBalanced) return cluster.servers.first end @@ -266,7 +270,7 @@ def select_server(cluster, ping = nil, session = nil, write_aggregation: false) end end - server = try_select_server(cluster, write_aggregation: write_aggregation) + server = try_select_server(cluster, write_aggregation: write_aggregation, deprioritized: deprioritized) if server unless cluster.topology.compatible? @@ -321,11 +325,15 @@ def select_server(cluster, ping = nil, session = nil, write_aggregation: false) # an eligible server. # @param [ true | false ] write_aggregation Whether we need a server that # supports writing aggregations (e.g. with $merge/$out) on secondaries. + # @param [ Array ] deprioritized A list of servers that should + # be selected from only if no other servers are available. This is + # used to avoid selecting the same server twice in a row when + # retrying a command. # # @return [ Server | nil ] A suitable server, if one exists. # # @api private - def try_select_server(cluster, write_aggregation: false) + def try_select_server(cluster, write_aggregation: false, deprioritized: []) servers = if write_aggregation && cluster.replica_set? # 1. Check if ALL servers in cluster support secondary writes. is_write_supported = cluster.servers.reduce(true) do |res, server| @@ -347,7 +355,7 @@ def try_select_server(cluster, write_aggregation: false) # by the selector (e.g. for secondary preferred, the first # server may be a secondary and the second server may be primary) # and we should take the first server here respecting the order - server = servers.first + server = suitable_server(servers, deprioritized) if server if Lint.enabled? @@ -418,6 +426,24 @@ def suitable_servers(cluster) private + # Returns a server from the list of servers that is suitable for + # executing the operation. + # + # @param [ Array ] servers The candidate servers. + # @param [ Array ] deprioritized A list of servers that should + # be selected from only if no other servers are available. + # + # @return [ Server | nil ] The suitable server or nil if no suitable + # server is available. + def suitable_server(servers, deprioritized) + preferred = servers - deprioritized + if preferred.empty? + servers.first + else + preferred.first + end + end + # Convert this server preference definition into a format appropriate # for sending to a MongoDB server (i.e., as a command field). # diff --git a/spec/integration/retryable_reads_errors_spec.rb b/spec/integration/retryable_reads_errors_spec.rb index 4d662c4bf5..81402c8f4d 100644 --- a/spec/integration/retryable_reads_errors_spec.rb +++ b/spec/integration/retryable_reads_errors_spec.rb @@ -20,14 +20,14 @@ let(:failpoint) do { - configureFailPoint: "failCommand", - mode: { times: 1 }, - data: { - failCommands: [ "find" ], - errorCode: 91, - blockConnection: true, - blockTimeMS: 1000 - } + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: [ "find" ], + errorCode: 91, + blockConnection: true, + blockTimeMS: 1000 + } } end @@ -107,4 +107,157 @@ }) end end + + context 'Retries in a sharded cluster' do + require_topology :sharded + min_server_version '4.2' + require_no_auth + + let(:subscriber) { Mrss::EventSubscriber.new } + + let(:find_started_events) do + subscriber.started_events.select { |e| e.command_name == "find" } + end + + let(:find_failed_events) do + subscriber.failed_events.select { |e| e.command_name == "find" } + end + + let(:find_succeeded_events) do + subscriber.succeeded_events.select { |e| e.command_name == "find" } + end + + context 'when another mongos is available' do + + let(:first_mongos) do + Mongo::Client.new( + [SpecConfig.instance.addresses.first], + direct_connection: true, + database: 'admin' + ) + end + + let(:second_mongos) do + Mongo::Client.new( + [SpecConfig.instance.addresses.last], + direct_connection: false, + database: 'admin' + ) + end + + let(:client) do + new_local_client( + [ + SpecConfig.instance.addresses.first, + SpecConfig.instance.addresses.last, + ], + SpecConfig.instance.test_options.merge(retry_reads: true) + ) + end + + let(:expected_servers) do + [ + SpecConfig.instance.addresses.first.to_s, + SpecConfig.instance.addresses.last.to_s + ].sort + end + + before do + skip 'This test requires at least two mongos' if SpecConfig.instance.addresses.length < 2 + + first_mongos.database.command( + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: %w(find), + closeConnection: false, + errorCode: 6 + } + ) + + second_mongos.database.command( + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: %w(find), + closeConnection: false, + errorCode: 6 + } + ) + end + + after do + [first_mongos, second_mongos].each do |admin_client| + admin_client.database.command( + configureFailPoint: 'failCommand', + mode: 'off' + ) + admin_client.close + end + client.close + end + + it 'retries on different mongos' do + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + expect { collection.find.first }.to raise_error(Mongo::Error::OperationFailure) + expect(find_started_events.map { |e| e.address.to_s }.sort).to eq(expected_servers) + expect(find_failed_events.map { |e| e.address.to_s }.sort).to eq(expected_servers) + end + end + + context 'when no other mongos is available' do + let(:mongos) do + Mongo::Client.new( + [SpecConfig.instance.addresses.first], + direct_connection: true, + database: 'admin' + ) + end + + let(:client) do + new_local_client( + [ + SpecConfig.instance.addresses.first + ], + SpecConfig.instance.test_options.merge(retry_reads: true) + ) + end + + before do + mongos.database.command( + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: %w(find), + closeConnection: false, + errorCode: 6 + } + ) + end + + after do + mongos.database.command( + configureFailPoint: 'failCommand', + mode: 'off' + ) + mongos.close + client.close + end + + it 'retries on the same mongos' do + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + expect { collection.find.first }.not_to raise_error + expect(find_started_events.map { |e| e.address.to_s }.sort).to eq([ + SpecConfig.instance.addresses.first.to_s, + SpecConfig.instance.addresses.first.to_s + ]) + expect(find_failed_events.map { |e| e.address.to_s }.sort).to eq([ + SpecConfig.instance.addresses.first.to_s + ]) + expect(find_succeeded_events.map { |e| e.address.to_s }.sort).to eq([ + SpecConfig.instance.addresses.first.to_s + ]) + end + end + end end diff --git a/spec/integration/retryable_writes_errors_spec.rb b/spec/integration/retryable_writes_errors_spec.rb index 2769089b11..25b5cba8aa 100644 --- a/spec/integration/retryable_writes_errors_spec.rb +++ b/spec/integration/retryable_writes_errors_spec.rb @@ -189,4 +189,160 @@ }) end end + + context 'Retries in a sharded cluster' do + require_topology :sharded + min_server_version '4.2' + require_no_auth + + let(:subscriber) { Mrss::EventSubscriber.new } + + let(:insert_started_events) do + subscriber.started_events.select { |e| e.command_name == "insert" } + end + + let(:insert_failed_events) do + subscriber.failed_events.select { |e| e.command_name == "insert" } + end + + let(:insert_succeeded_events) do + subscriber.succeeded_events.select { |e| e.command_name == "insert" } + end + + context 'when another mongos is available' do + + let(:first_mongos) do + Mongo::Client.new( + [SpecConfig.instance.addresses.first], + direct_connection: true, + database: 'admin' + ) + end + + let(:second_mongos) do + Mongo::Client.new( + [SpecConfig.instance.addresses.last], + direct_connection: false, + database: 'admin' + ) + end + + let(:client) do + new_local_client( + [ + SpecConfig.instance.addresses.first, + SpecConfig.instance.addresses.last, + ], + SpecConfig.instance.test_options.merge(retry_writes: true) + ) + end + + let(:expected_servers) do + [ + SpecConfig.instance.addresses.first.to_s, + SpecConfig.instance.addresses.last.to_s + ].sort + end + + before do + skip 'This test requires at least two mongos' if SpecConfig.instance.addresses.length < 2 + + first_mongos.database.command( + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: %w(insert), + closeConnection: false, + errorCode: 6, + errorLabels: ['RetryableWriteError'] + } + ) + + second_mongos.database.command( + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: %w(insert), + closeConnection: false, + errorCode: 6, + errorLabels: ['RetryableWriteError'] + } + ) + end + + after do + [first_mongos, second_mongos].each do |admin_client| + admin_client.database.command( + configureFailPoint: 'failCommand', + mode: 'off' + ) + admin_client.close + end + client.close + end + + it 'retries on different mongos' do + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + expect { collection.insert_one(x: 1) }.to raise_error(Mongo::Error::OperationFailure) + expect(insert_started_events.map { |e| e.address.to_s }.sort).to eq(expected_servers) + expect(insert_failed_events.map { |e| e.address.to_s }.sort).to eq(expected_servers) + end + end + + context 'when no other mongos is available' do + let(:mongos) do + Mongo::Client.new( + [SpecConfig.instance.addresses.first], + direct_connection: true, + database: 'admin' + ) + end + + let(:client) do + new_local_client( + [ + SpecConfig.instance.addresses.first + ], + SpecConfig.instance.test_options.merge(retry_writes: true) + ) + end + + before do + mongos.database.command( + configureFailPoint: 'failCommand', + mode: { times: 1 }, + data: { + failCommands: %w(insert), + closeConnection: false, + errorCode: 6, + errorLabels: ['RetryableWriteError'] + } + ) + end + + after do + mongos.database.command( + configureFailPoint: 'failCommand', + mode: 'off' + ) + mongos.close + client.close + end + + it 'retries on the same mongos' do + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + expect { collection.insert_one(x: 1) }.not_to raise_error + expect(insert_started_events.map { |e| e.address.to_s }.sort).to eq([ + SpecConfig.instance.addresses.first.to_s, + SpecConfig.instance.addresses.first.to_s + ]) + expect(insert_failed_events.map { |e| e.address.to_s }.sort).to eq([ + SpecConfig.instance.addresses.first.to_s + ]) + expect(insert_succeeded_events.map { |e| e.address.to_s }.sort).to eq([ + SpecConfig.instance.addresses.first.to_s + ]) + end + end + end end From 12f789ea2a6e258b8aa6d723bf9108bba27b2e4d Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 28 Aug 2023 14:03:27 -0600 Subject: [PATCH 153/198] RUBY-3316 Quality of life updates for BSON benchmarks (#2774) * RUBY-3314 Implement variable iterations for benchmarks * report percentiles along with the median * rename Benchmarking::Micro to Benchmarking::BSON * refactoring to appease rubocop * RUBY-3315 benchmark scoring * clean up the Rakefile * progress indicator while benchmarks are running * rubocop --- Rakefile | 163 +--------------------- profile/benchmarking/bson.rb | 2 +- profile/benchmarking/helper.rb | 72 ++++++---- profile/benchmarking/rake/bson.rake | 122 ++++++++++++++++ profile/benchmarking/rake/multi_doc.rake | 34 +++++ profile/benchmarking/rake/parallel.rake | 36 +++++ profile/benchmarking/rake/single_doc.rake | 27 ++++ profile/benchmarking/rake/tasks.rake | 11 ++ 8 files changed, 276 insertions(+), 191 deletions(-) create mode 100644 profile/benchmarking/rake/bson.rake create mode 100644 profile/benchmarking/rake/multi_doc.rake create mode 100644 profile/benchmarking/rake/parallel.rake create mode 100644 profile/benchmarking/rake/single_doc.rake create mode 100644 profile/benchmarking/rake/tasks.rake diff --git a/Rakefile b/Rakefile index 49733ed4af..4a4458070d 100644 --- a/Rakefile +++ b/Rakefile @@ -131,165 +131,4 @@ namespace :docs do end end -require_relative "profile/benchmarking" - -# Some require data files, available from the drivers team. See the comments above each task for details." -namespace :benchmark do - desc "Run the bson benchmarking tests" - task :bson do - puts "BSON BENCHMARK" - Mongo::Benchmarking.report({ - bson: Mongo::Benchmarking::BSON.run_all({ - flat: %i[ encode decode ], - deep: %i[ encode decode ], - full: %i[ encode decode ], - }) - }) - end - - namespace :bson do - namespace :flat do - desc "Benchmarking for flat bson documents." - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called flat_bson.json. - task :encode do - puts "BSON BENCHMARK :: FLAT :: ENCODE" - Mongo::Benchmarking.report({ bson: { flat: { encode: Mongo::Benchmarking::BSON.run(:flat, :encode) } } }) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called flat_bson.json. - task :decode do - puts "BSON BENCHMARK :: FLAT :: DECODE" - Mongo::Benchmarking.report({ bson: { flat: { decode: Mongo::Benchmarking::BSON.run(:flat, :decode) } } }) - end - end - - namespace :deep do - desc "Benchmarking for deep bson documents." - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called deep_bson.json. - task :encode do - puts "BSON BENCHMARK :: DEEP :: ENCODE" - Mongo::Benchmarking.report({ bson: { deep: { encode: Mongo::Benchmarking::BSON.run(:deep, :encode) } } }) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called deep_bson.json. - task :decode do - puts "BSON BENCHMARK :: DEEP :: DECODE" - Mongo::Benchmarking.report({ bson: { deep: { decode: Mongo::Benchmarking::BSON.run(:deep, :decode) } } }) - end - end - - namespace :full do - desc "Benchmarking for full bson documents." - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called full_bson.json. - task :encode do - puts "BSON BENCHMARK :: FULL :: ENCODE" - Mongo::Benchmarking.report({ bson: { full: { encode: Mongo::Benchmarking::BSON.run(:full, :encode) } } }) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called full_bson.json. - task :decode do - puts "BSON BENCHMARK :: FULL :: DECODE" - Mongo::Benchmarking.report({ bson: { full: { decode: Mongo::Benchmarking::BSON.run(:full, :decode) } } }) - end - end - end - - namespace :single_doc do - desc "Run the common driver single-document benchmarking tests" - task :command do - puts "SINGLE DOC BENCHMARK:: COMMAND" - Mongo::Benchmarking::SingleDoc.run(:command) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called TWEET.json. - task :find_one do - puts "SINGLE DOC BENCHMARK:: FIND ONE BY ID" - Mongo::Benchmarking::SingleDoc.run(:find_one) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called SMALL_DOC.json. - task :insert_one_small do - puts "SINGLE DOC BENCHMARK:: INSERT ONE SMALL DOCUMENT" - Mongo::Benchmarking::SingleDoc.run(:insert_one_small) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called LARGE_DOC.json. - task :insert_one_large do - puts "SINGLE DOC BENCHMARK:: INSERT ONE LARGE DOCUMENT" - Mongo::Benchmarking::SingleDoc.run(:insert_one_large) - end - end - - namespace :multi_doc do - desc "Run the common driver multi-document benchmarking tests" - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called TWEET.json. - task :find_many do - puts "MULTI DOCUMENT BENCHMARK:: FIND MANY" - Mongo::Benchmarking::MultiDoc.run(:find_many) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called SMALL_DOC.json. - task :bulk_insert_small do - puts "MULTI DOCUMENT BENCHMARK:: BULK INSERT SMALL" - Mongo::Benchmarking::MultiDoc.run(:bulk_insert_small) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called LARGE_DOC.json. - task :bulk_insert_large do - puts "MULTI DOCUMENT BENCHMARK:: BULK INSERT LARGE" - Mongo::Benchmarking::MultiDoc.run(:bulk_insert_large) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called GRIDFS_LARGE. - task :gridfs_upload do - puts "MULTI DOCUMENT BENCHMARK:: GRIDFS UPLOAD" - Mongo::Benchmarking::MultiDoc.run(:gridfs_upload) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called GRIDFS_LARGE. - task :gridfs_download do - puts "MULTI DOCUMENT BENCHMARK:: GRIDFS DOWNLOAD" - Mongo::Benchmarking::MultiDoc.run(:gridfs_download) - end - end - - namespace :parallel do - desc "Run the common driver paralell ETL benchmarking tests" - - # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called LDJSON_MULTI, - # with the files used in this task. - task :import do - puts "PARALLEL ETL BENCHMARK:: IMPORT" - Mongo::Benchmarking::Parallel.run(:import) - end - - # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called LDJSON_MULTI, - # with the files used in this task. - # Requirement: Another directory in "#{Mongo::Benchmarking::DATA_PATH}/LDJSON_MULTI" - # called 'output'. - task :export do - puts "PARALLEL ETL BENCHMARK:: EXPORT" - Mongo::Benchmarking::Parallel.run(:export) - end - - # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called GRIDFS_MULTI, - # with the files used in this task. - task :gridfs_upload do - puts "PARALLEL ETL BENCHMARK:: GRIDFS UPLOAD" - Mongo::Benchmarking::Parallel.run(:gridfs_upload) - end - - # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called GRIDFS_MULTI, - # with the files used in this task. - # Requirement: Another directory in "#{Mongo::Benchmarking::DATA_PATH}/GRIDFS_MULTI" - # called 'output'. - task :gridfs_download do - puts "PARALLEL ETL BENCHMARK:: GRIDFS DOWNLOAD" - Mongo::Benchmarking::Parallel.run(:gridfs_download) - end - end -end +load 'profile/benchmarking/rake/tasks.rake' diff --git a/profile/benchmarking/bson.rb b/profile/benchmarking/bson.rb index 88cb979ab2..415e58114e 100644 --- a/profile/benchmarking/bson.rb +++ b/profile/benchmarking/bson.rb @@ -71,7 +71,7 @@ def score_for(type, percentiles, scale: 10_000) # @return [ Hash<:timings,:percentiles,:score> ] The test results for # the requested benchmark. def run(type, action) - timings = Benchmarking.without_gc { send(action, file_for(type)) } + timings = send(action, file_for(type)) percentiles = Percentiles.new(timings) score = score_for(type, percentiles) diff --git a/profile/benchmarking/helper.rb b/profile/benchmarking/helper.rb index 856cb7659b..70d4e34312 100644 --- a/profile/benchmarking/helper.rb +++ b/profile/benchmarking/helper.rb @@ -58,13 +58,21 @@ def parse_json(document) # iterating. # # @return [ Array ] the timings for each iteration - def benchmark(max_iterations: Benchmarking::TEST_REPETITIONS, min_time: 60, max_time: 5 * 60, &block) + def benchmark(max_iterations: Benchmarking::TEST_REPETITIONS, + min_time: 60, + max_time: 5 * 60, + progress: default_progress_callback, + &block) + progress ||= ->(state) {} # fallback to a no-op callback + progress[:start] + [].tap do |results| iteration_count = 0 cumulative_time = 0 loop do - timing = Benchmark.realtime(&block) + timing = without_gc { Benchmark.realtime(&block) } + progress[:step] iteration_count += 1 cumulative_time += timing @@ -78,6 +86,8 @@ def benchmark(max_iterations: Benchmarking::TEST_REPETITIONS, min_time: 60, max_ # number of iterations have been reached. break if cumulative_time >= min_time && iteration_count >= max_iterations end + + progress[:end] end end @@ -98,32 +108,6 @@ def report(results, indent: 0, percentiles: [ 10, 25, 50, 75, 90, 95, 98, 99 ]) end end - # A utility class for returning the list item at a given percentile - # value. - class Percentiles - # @return [ Array ] the sorted list of numbers to consider - attr_reader :list - - # Create a new Percentiles object that encapsulates the given list of - # numbers. - # - # @param [ Array ] list the list of numbers to considier - def initialize(list) - @list = list.sort - end - - # Finds and returns the element in the list that represents the given - # percentile value. - # - # @param [ Number ] percentile a number in the range [1,100] - # - # @return [ Number ] the element of the list for the given percentile. - def [](percentile) - i = (list.size * percentile / 100.0).ceil - 1 - list[i] - end - end - # Get the median of values in a list. # # @example Get the median. @@ -144,5 +128,37 @@ def without_gc ensure GC.enable end + + private + + # Returns the proc object (or nil) corresponding to the "PROGRESS" + # environment variable. + # + # @return [ Proc | nil ] the callback proc to use (or nil if none should + # be used) + def default_progress_callback + case ENV['PROGRESS'] + when '0', 'false', 'none' + nil + when nil, '1', 'true', 'minimal' + method(:minimal_progress_callback).to_proc + else + raise ArgumentError, "unsupported progress callback #{ENV['PROGRESS'].inspect}" + end + end + + # A minimal progress callback implementation, printing '|' when a benchmark + # starts and '.' for each iteration. + # + # @param [ :start | :step | :end ] state the current progress state + def minimal_progress_callback(state) + case state + when :start then print '|' + when :step then print '.' + when :end then puts + end + + $stdout.flush + end end end diff --git a/profile/benchmarking/rake/bson.rake b/profile/benchmarking/rake/bson.rake new file mode 100644 index 0000000000..4d5bdb1c04 --- /dev/null +++ b/profile/benchmarking/rake/bson.rake @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +# rubocop:disable Layout/FirstHashElementIndentation + +desc 'Run the full BSON benchmarking suite' +task :bson do + puts 'BSON BENCHMARK SUITE' + Mongo::Benchmarking.report({ + bson: Mongo::Benchmarking::BSON.run_all( + flat: %i[ encode decode ], + deep: %i[ encode decode ], + full: %i[ encode decode ] + ) + }) +end + +namespace :bson do # rubocop:disable Metrics/BlockLength + # a convenience task for running all of the bson benchmark tasks; this is + # only useful for testing that they all work. + task test: %w[ + bson + bson:flat bson:flat:encode bson:flat:decode + bson:deep bson:deep:encode bson:deep:decode + bson:full bson:full:encode bson:full:decode + ] + + desc 'Learn how to run the BSON benchmarks' + task :help do + puts <<~HELP + The BSON micro benchmarks require a set of data files that are stored in + the specifications repository, here: + + https://github.com/mongodb/specifications/tree/master/source/benchmarking/data + + Download the `extended_bson.tgz` file and extract its contents. It should + contain a single folder (`extended_bson`) with several files in it. Move + those files to: + + #{Mongo::Benchmarking::DATA_PATH} + + Once there, you may run any of the BSON benchmarking tasks: + + $ rake benchmark:bson:flat:encode + + Tasks may be run in aggregate, as well, by specifying the namespace + directly: + + $ rake benchmark:bson:flat # runs all flat BSON benchmarks + $ rake benchmark:bson:deep # runs all deep BSON benchmarks + $ rake benchmark:bson:full # runs all full BSON benchmarks + # rake benchmark:bson # runs all BSON benchmarks + HELP + end + + desc 'Run the `flat` BSON benchmarking suite' + task :flat do + puts 'BSON BENCHMARK :: FLAT' + Mongo::Benchmarking.report({ + bson: Mongo::Benchmarking::BSON.run_all(flat: %i[ encode decode ]) + }) + end + + namespace :flat do + desc 'Run the `flat` encoding BSON benchmark' + task :encode do + puts 'BSON BENCHMARK :: FLAT :: ENCODE' + Mongo::Benchmarking.report({ bson: { flat: { encode: Mongo::Benchmarking::BSON.run(:flat, :encode) } } }) + end + + desc 'Run the `flat` decoding BSON benchmark' + task :decode do + puts 'BSON BENCHMARK :: FLAT :: DECODE' + Mongo::Benchmarking.report({ bson: { flat: { decode: Mongo::Benchmarking::BSON.run(:flat, :decode) } } }) + end + end + + desc 'Run the `deep` BSON benchmarking suite' + task :deep do + puts 'BSON BENCHMARK :: DEEP' + Mongo::Benchmarking.report({ + bson: Mongo::Benchmarking::BSON.run_all(deep: %i[ encode decode ]) + }) + end + + namespace :deep do + desc 'Run the `deep` encoding BSON benchmark' + task :encode do + puts 'BSON BENCHMARK :: DEEP :: ENCODE' + Mongo::Benchmarking.report({ bson: { deep: { encode: Mongo::Benchmarking::BSON.run(:deep, :encode) } } }) + end + + desc 'Run the `deep` decoding BSON benchmark' + task :decode do + puts 'BSON BENCHMARK :: DEEP :: DECODE' + Mongo::Benchmarking.report({ bson: { deep: { decode: Mongo::Benchmarking::BSON.run(:deep, :decode) } } }) + end + end + + desc 'Run the `full` BSON benchmarking suite' + task :full do + puts 'BSON BENCHMARK :: FULL' + Mongo::Benchmarking.report({ + bson: Mongo::Benchmarking::BSON.run_all({ full: %i[ encode decode ] }) + }) + end + + namespace :full do + desc 'Run the `full` encoding BSON benchmark' + task :encode do + puts 'BSON BENCHMARK :: FULL :: ENCODE' + Mongo::Benchmarking.report({ bson: { full: { encode: Mongo::Benchmarking::BSON.run(:full, :encode) } } }) + end + + desc 'Run the `full` decoding BSON benchmark' + task :decode do + puts 'BSON BENCHMARK :: FULL :: DECODE' + Mongo::Benchmarking.report({ bson: { full: { decode: Mongo::Benchmarking::BSON.run(:full, :decode) } } }) + end + end +end + +# rubocop:enable Layout/FirstHashElementIndentation diff --git a/profile/benchmarking/rake/multi_doc.rake b/profile/benchmarking/rake/multi_doc.rake new file mode 100644 index 0000000000..86c190ef1f --- /dev/null +++ b/profile/benchmarking/rake/multi_doc.rake @@ -0,0 +1,34 @@ +# frozen_string_literal: true +# rubocop:todo all + +namespace :multi_doc do + # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called TWEET.json. + task :find_many do + puts 'MULTI DOCUMENT BENCHMARK :: FIND MANY' + Mongo::Benchmarking::MultiDoc.run(:find_many) + end + + # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called SMALL_DOC.json. + task :bulk_insert_small do + puts 'MULTI DOCUMENT BENCHMARK :: BULK INSERT SMALL' + Mongo::Benchmarking::MultiDoc.run(:bulk_insert_small) + end + + # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called LARGE_DOC.json. + task :bulk_insert_large do + puts 'MULTI DOCUMENT BENCHMARK :: BULK INSERT LARGE' + Mongo::Benchmarking::MultiDoc.run(:bulk_insert_large) + end + + # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called GRIDFS_LARGE. + task :gridfs_upload do + puts 'MULTI DOCUMENT BENCHMARK :: GRIDFS UPLOAD' + Mongo::Benchmarking::MultiDoc.run(:gridfs_upload) + end + + # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called GRIDFS_LARGE. + task :gridfs_download do + puts 'MULTI DOCUMENT BENCHMARK :: GRIDFS DOWNLOAD' + Mongo::Benchmarking::MultiDoc.run(:gridfs_download) + end +end diff --git a/profile/benchmarking/rake/parallel.rake b/profile/benchmarking/rake/parallel.rake new file mode 100644 index 0000000000..98752e231e --- /dev/null +++ b/profile/benchmarking/rake/parallel.rake @@ -0,0 +1,36 @@ +# frozen_string_literal: true +# rubocop:todo all + +namespace :parallel do + # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called LDJSON_MULTI, + # with the files used in this task. + task :import do + puts 'PARALLEL ETL BENCHMARK :: IMPORT' + Mongo::Benchmarking::Parallel.run(:import) + end + + # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called LDJSON_MULTI, + # with the files used in this task. + # Requirement: Another directory in '#{Mongo::Benchmarking::DATA_PATH}/LDJSON_MULTI' + # called 'output'. + task :export do + puts 'PARALLEL ETL BENCHMARK :: EXPORT' + Mongo::Benchmarking::Parallel.run(:export) + end + + # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called GRIDFS_MULTI, + # with the files used in this task. + task :gridfs_upload do + puts 'PARALLEL ETL BENCHMARK :: GRIDFS UPLOAD' + Mongo::Benchmarking::Parallel.run(:gridfs_upload) + end + + # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called GRIDFS_MULTI, + # with the files used in this task. + # Requirement: Another directory in '#{Mongo::Benchmarking::DATA_PATH}/GRIDFS_MULTI' + # called 'output'. + task :gridfs_download do + puts 'PARALLEL ETL BENCHMARK :: GRIDFS DOWNLOAD' + Mongo::Benchmarking::Parallel.run(:gridfs_download) + end +end diff --git a/profile/benchmarking/rake/single_doc.rake b/profile/benchmarking/rake/single_doc.rake new file mode 100644 index 0000000000..803e28d593 --- /dev/null +++ b/profile/benchmarking/rake/single_doc.rake @@ -0,0 +1,27 @@ +# frozen_string_literal: true +# rubocop:todo all + +namespace :single_doc do + task :command do + puts 'SINGLE DOC BENCHMARK :: COMMAND' + Mongo::Benchmarking::SingleDoc.run(:command) + end + + # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called TWEET.json. + task :find_one do + puts 'SINGLE DOC BENCHMARK :: FIND ONE BY ID' + Mongo::Benchmarking::SingleDoc.run(:find_one) + end + + # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called SMALL_DOC.json. + task :insert_one_small do + puts 'SINGLE DOC BENCHMARK :: INSERT ONE SMALL DOCUMENT' + Mongo::Benchmarking::SingleDoc.run(:insert_one_small) + end + + # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called LARGE_DOC.json. + task :insert_one_large do + puts 'SINGLE DOC BENCHMARK :: INSERT ONE LARGE DOCUMENT' + Mongo::Benchmarking::SingleDoc.run(:insert_one_large) + end +end diff --git a/profile/benchmarking/rake/tasks.rake b/profile/benchmarking/rake/tasks.rake new file mode 100644 index 0000000000..7feae8d2ab --- /dev/null +++ b/profile/benchmarking/rake/tasks.rake @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative '../../benchmarking' + +# Some require data files, available from the drivers team. +# See the comments above each task for details. +namespace :benchmark do + %w[ bson single_doc multi_doc parallel ].each do |group| + load File.join(__dir__, "#{group}.rake") + end +end From c2c7ad0c524f4ab7c7c491cd3c79a3c607a71e7f Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 7 Sep 2023 15:21:25 -0600 Subject: [PATCH 154/198] RUBY-3268 search index management helpers (#2777) * RUBY-3268 Index View API for Search Indexes * documentation, and align method names with the spec * return the correct value here * suppress NamespaceNotFound errors for the drop operation * prose tests for search indexes * rubocop * prose tests pass * first stab at evergreen config for index management specs * rubocop * gah, executable permissions * don't use FLE for full atlas tests * make sure MONGODB_URI is set * set the timeout higher for the search index specs * pass all aggregation options through to the list indexes command * use the correct implementation for #empty? * remove unnecessary validation --- .evergreen/config.yml | 58 +++++ .evergreen/config/common.yml.erb | 50 ++++ .evergreen/config/standard.yml.erb | 8 + .evergreen/run-tests-atlas-full.sh | 24 ++ .gitignore | 3 + .rubocop.yml | 3 + lib/mongo.rb | 1 + lib/mongo/collection.rb | 24 +- lib/mongo/operation.rb | 3 + lib/mongo/operation/create_search_indexes.rb | 15 ++ .../operation/create_search_indexes/op_msg.rb | 31 +++ lib/mongo/operation/drop_search_index.rb | 15 ++ .../operation/drop_search_index/op_msg.rb | 33 +++ lib/mongo/operation/shared/specifiable.rb | 7 + lib/mongo/operation/update_search_index.rb | 15 ++ .../operation/update_search_index/op_msg.rb | 34 +++ lib/mongo/search_index/view.rb | 232 ++++++++++++++++++ spec/atlas/atlas_connectivity_spec.rb | 6 +- spec/atlas/operations_spec.rb | 6 +- spec/integration/search_indexes_prose_spec.rb | 168 +++++++++++++ spec/lite_spec_helper.rb | 42 +++- .../unified/search_index_operations.rb | 63 +++++ spec/runners/unified/test.rb | 4 +- spec/spec_helper.rb | 2 +- .../index_management/createSearchIndex.yml | 62 +++++ .../index_management/createSearchIndexes.yml | 83 +++++++ .../data/index_management/dropSearchIndex.yml | 42 ++++ .../index_management/listSearchIndexes.yml | 85 +++++++ .../index_management/updateSearchIndex.yml | 45 ++++ .../index_management_unified_spec.rb | 13 + spec/support/spec_config.rb | 5 + 31 files changed, 1159 insertions(+), 23 deletions(-) create mode 100755 .evergreen/run-tests-atlas-full.sh create mode 100644 lib/mongo/operation/create_search_indexes.rb create mode 100644 lib/mongo/operation/create_search_indexes/op_msg.rb create mode 100644 lib/mongo/operation/drop_search_index.rb create mode 100644 lib/mongo/operation/drop_search_index/op_msg.rb create mode 100644 lib/mongo/operation/update_search_index.rb create mode 100644 lib/mongo/operation/update_search_index/op_msg.rb create mode 100644 lib/mongo/search_index/view.rb create mode 100644 spec/integration/search_indexes_prose_spec.rb create mode 100644 spec/runners/unified/search_index_operations.rb create mode 100644 spec/spec_tests/data/index_management/createSearchIndex.yml create mode 100644 spec/spec_tests/data/index_management/createSearchIndexes.yml create mode 100644 spec/spec_tests/data/index_management/dropSearchIndex.yml create mode 100644 spec/spec_tests/data/index_management/listSearchIndexes.yml create mode 100644 spec/spec_tests/data/index_management/updateSearchIndex.yml create mode 100644 spec/spec_tests/index_management_unified_spec.rb diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 499c5fa7d1..e43a13b7b3 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -469,6 +469,46 @@ post: - func: "delete serverless instance" task_groups: + - name: testatlas_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: create expansions + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ + DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ + DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ + DRIVERS_ATLAS_LAMBDA_USER="${DRIVERS_ATLAS_LAMBDA_USER}" \ + DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" \ + LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + MONGODB_VERSION="7.0" \ + $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh + - command: expansions.update + params: + file: src/atlas-expansion.yml + teardown_group: + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ + DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ + DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ + LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh + tasks: + - test-full-atlas-task + - name: testgcpkms_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 # 30 minutes @@ -586,6 +626,16 @@ tasks: commands: - func: "export AWS auth credentials" - func: "run AWS auth tests" + - name: "test-full-atlas-task" + commands: + - command: shell.exec + type: test + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + MONGODB_URI="${MONGODB_URI}" .evergreen/run-tests-atlas-full.sh - name: "testgcpkms-task" commands: - command: shell.exec @@ -1612,6 +1662,14 @@ buildvariants: - name: testazurekms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - matrix_name: atlas-full + matrix_spec: + ruby: "ruby-3.2" + os: rhel8 + display_name: "Atlas (Full)" + tasks: + - name: testatlas_task_group + - matrix_name: "atlas" matrix_spec: ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 836a1849f0..306adc1b7a 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -466,6 +466,46 @@ post: - func: "delete serverless instance" task_groups: + - name: testatlas_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: create expansions + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ + DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ + DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ + DRIVERS_ATLAS_LAMBDA_USER="${DRIVERS_ATLAS_LAMBDA_USER}" \ + DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" \ + LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + MONGODB_VERSION="7.0" \ + $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh + - command: expansions.update + params: + file: src/atlas-expansion.yml + teardown_group: + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ + DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ + DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ + LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh + tasks: + - test-full-atlas-task + - name: testgcpkms_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 # 30 minutes @@ -583,6 +623,16 @@ tasks: commands: - func: "export AWS auth credentials" - func: "run AWS auth tests" + - name: "test-full-atlas-task" + commands: + - command: shell.exec + type: test + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + MONGODB_URI="${MONGODB_URI}" .evergreen/run-tests-atlas-full.sh - name: "testgcpkms-task" commands: - command: shell.exec diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index 67e2a92686..3bcd1b9acb 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -497,6 +497,14 @@ buildvariants: - name: testazurekms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README + - matrix_name: atlas-full + matrix_spec: + ruby: <%= latest_ruby %> + os: rhel8 + display_name: "Atlas (Full)" + tasks: + - name: testatlas_task_group + - matrix_name: "atlas" matrix_spec: ruby: <%= supported_rubies %> diff --git a/.evergreen/run-tests-atlas-full.sh b/.evergreen/run-tests-atlas-full.sh new file mode 100755 index 0000000000..2fb15001c1 --- /dev/null +++ b/.evergreen/run-tests-atlas-full.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -ex + +. `dirname "$0"`/../spec/shared/shlib/distro.sh +. `dirname "$0"`/../spec/shared/shlib/set_env.sh +. `dirname "$0"`/functions.sh + +set_env_vars +set_env_python +set_env_ruby + +bundle_install + +ATLAS_URI=$MONGODB_URI \ + SERVERLESS=1 \ + EXAMPLE_TIMEOUT=600 \ + bundle exec rspec -fd spec/integration/search_indexes_prose_spec.rb + +test_status=$? + +kill_jruby + +exit ${test_status} diff --git a/.gitignore b/.gitignore index 7af488f10d..24a54bc470 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ gemfiles/*.gemfile.lock .env build profile/benchmarking/data +secrets-export.sh +secrets-expansion.yml +atlas-expansion.yml diff --git a/.rubocop.yml b/.rubocop.yml index 6cb04cd597..788bd56daf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -56,6 +56,9 @@ Metrics/ModuleLength: Metrics/MethodLength: Max: 20 +Naming/MethodParameterName: + AllowedNames: [ id, op ] + RSpec/BeforeAfterAll: Enabled: false diff --git a/lib/mongo.rb b/lib/mongo.rb index 0f24833f06..b90cd4a011 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -64,6 +64,7 @@ require 'mongo/dbref' require 'mongo/grid' require 'mongo/index' +require 'mongo/search_index/view' require 'mongo/lint' require 'mongo/query_cache' require 'mongo/server' diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 5b8d5af128..90514f203f 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -725,13 +725,35 @@ def distinct(field_name, filter = nil, options = {}) # # @option options [ Session ] :session The session to use. # - # @return [ View::Index ] The index view. + # @return [ Index::View ] The index view. # # @since 2.0.0 def indexes(options = {}) Index::View.new(self, options) end + # Get a view of all search indexes for this collection. Can be iterated or + # operated on directly. If id or name are given, the iterator will return + # only the indicated index. For all other operations, id and name are + # ignored. + # + # @note Only one of id or name may be given; it is an error to specify both, + # although both may be omitted safely. + # + # @param [ Hash ] options The options to use to configure the view. + # + # @option options [ String ] :id The id of the specific index to query (optional) + # @option options [ String ] :name The name of the specific index to query (optional) + # @option options [ Hash ] :aggregate The options hash to pass to the + # aggregate command (optional) + # + # @return [ SearchIndex::View ] The search index view. + # + # @since 2.0.0 + def search_indexes(options = {}) + SearchIndex::View.new(self, options) + end + # Get a pretty printed string inspection for the collection. # # @example Inspect the collection. diff --git a/lib/mongo/operation.rb b/lib/mongo/operation.rb index b9b24968ab..8def25dbe3 100644 --- a/lib/mongo/operation.rb +++ b/lib/mongo/operation.rb @@ -51,6 +51,9 @@ require 'mongo/operation/remove_user' require 'mongo/operation/create_index' require 'mongo/operation/drop_index' +require 'mongo/operation/create_search_indexes' +require 'mongo/operation/drop_search_index' +require 'mongo/operation/update_search_index' module Mongo diff --git a/lib/mongo/operation/create_search_indexes.rb b/lib/mongo/operation/create_search_indexes.rb new file mode 100644 index 0000000000..1b07ac4241 --- /dev/null +++ b/lib/mongo/operation/create_search_indexes.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'mongo/operation/create_search_indexes/op_msg' + +module Mongo + module Operation + # A MongoDB createSearchIndexes command operation. + # + # @api private + class CreateSearchIndexes + include Specifiable + include OpMsgExecutable + end + end +end diff --git a/lib/mongo/operation/create_search_indexes/op_msg.rb b/lib/mongo/operation/create_search_indexes/op_msg.rb new file mode 100644 index 0000000000..444d35721b --- /dev/null +++ b/lib/mongo/operation/create_search_indexes/op_msg.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Mongo + module Operation + class CreateSearchIndexes + # A MongoDB createSearchIndexes operation sent as an op message. + # + # @api private + class OpMsg < OpMsgBase + include ExecutableTransactionLabel + + private + + # Returns the command to send to the database, describing the + # desired createSearchIndexes operation. + # + # @param [ Mongo::Server ] _server the server that will receive the + # command + # + # @return [ Hash ] the selector + def selector(_server) + { + createSearchIndexes: coll_name, + :$db => db_name, + indexes: indexes, + } + end + end + end + end +end diff --git a/lib/mongo/operation/drop_search_index.rb b/lib/mongo/operation/drop_search_index.rb new file mode 100644 index 0000000000..8a12a06fc2 --- /dev/null +++ b/lib/mongo/operation/drop_search_index.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'mongo/operation/drop_search_index/op_msg' + +module Mongo + module Operation + # A MongoDB dropSearchIndex command operation. + # + # @api private + class DropSearchIndex + include Specifiable + include OpMsgExecutable + end + end +end diff --git a/lib/mongo/operation/drop_search_index/op_msg.rb b/lib/mongo/operation/drop_search_index/op_msg.rb new file mode 100644 index 0000000000..8f4d323c55 --- /dev/null +++ b/lib/mongo/operation/drop_search_index/op_msg.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Mongo + module Operation + class DropSearchIndex + # A MongoDB createSearchIndexes operation sent as an op message. + # + # @api private + class OpMsg < OpMsgBase + include ExecutableTransactionLabel + + private + + # Returns the command to send to the database, describing the + # desired dropSearchIndex operation. + # + # @param [ Mongo::Server ] _server the server that will receive the + # command + # + # @return [ Hash ] the selector + def selector(_server) + { + dropSearchIndex: coll_name, + :$db => db_name, + }.tap do |sel| + sel[:id] = index_id if index_id + sel[:name] = index_name if index_name + end + end + end + end + end +end diff --git a/lib/mongo/operation/shared/specifiable.rb b/lib/mongo/operation/shared/specifiable.rb index 0a19dba1f6..afc799f46e 100644 --- a/lib/mongo/operation/shared/specifiable.rb +++ b/lib/mongo/operation/shared/specifiable.rb @@ -260,6 +260,13 @@ def index spec[INDEX] end + # Get the index id from the spec. + # + # @return [ String ] The index id. + def index_id + spec[:index_id] + end + # Get the index name from the spec. # # @example Get the index name. diff --git a/lib/mongo/operation/update_search_index.rb b/lib/mongo/operation/update_search_index.rb new file mode 100644 index 0000000000..05d8155bb1 --- /dev/null +++ b/lib/mongo/operation/update_search_index.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'mongo/operation/update_search_index/op_msg' + +module Mongo + module Operation + # A MongoDB updateSearchIndex command operation. + # + # @api private + class UpdateSearchIndex + include Specifiable + include OpMsgExecutable + end + end +end diff --git a/lib/mongo/operation/update_search_index/op_msg.rb b/lib/mongo/operation/update_search_index/op_msg.rb new file mode 100644 index 0000000000..c6d21aaf0d --- /dev/null +++ b/lib/mongo/operation/update_search_index/op_msg.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Mongo + module Operation + class UpdateSearchIndex + # A MongoDB updateSearchIndex operation sent as an op message. + # + # @api private + class OpMsg < OpMsgBase + include ExecutableTransactionLabel + + private + + # Returns the command to send to the database, describing the + # desired updateSearchIndex operation. + # + # @param [ Mongo::Server ] _server the server that will receive the + # command + # + # @return [ Hash ] the selector + def selector(_server) + { + updateSearchIndex: coll_name, + :$db => db_name, + definition: index, + }.tap do |sel| + sel[:id] = index_id if index_id + sel[:name] = index_name if index_name + end + end + end + end + end +end diff --git a/lib/mongo/search_index/view.rb b/lib/mongo/search_index/view.rb new file mode 100644 index 0000000000..8c59771eab --- /dev/null +++ b/lib/mongo/search_index/view.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +module Mongo + module SearchIndex + # A class representing a view of search indexes. + class View + include Enumerable + include Retryable + include Collection::Helpers + + # @return [ Mongo::Collection ] the collection this view belongs to + attr_reader :collection + + # @return [ nil | String ] the index id to query + attr_reader :requested_index_id + + # @return [ nil | String ] the index name to query + attr_reader :requested_index_name + + # @return [ Hash ] the options hash to use for the aggregate command + # when querying the available indexes. + attr_reader :aggregate_options + + # Create the new search index view. + # + # @param [ Collection ] collection The collection. + # @param [ Hash ] options The options that configure the behavior of the view. + # + # @option options [ String ] :id The specific index id to query (optional) + # @option options [ String ] :name The name of the specific index to query (optional) + # @option options [ Hash ] :aggregate The options hash to send to the + # aggregate command when querying the available indexes. + def initialize(collection, options = {}) + @collection = collection + @requested_index_id = options[:id] + @requested_index_name = options[:name] + @aggregate_options = options[:aggregate] || {} + + return if @aggregate_options.is_a?(Hash) + + raise ArgumentError, "The :aggregate option must be a Hash (got a #{@aggregate_options.class})" + end + + # Create a single search index with the given definition. If the name is + # provided, the new index will be given that name. + # + # @param [ Hash ] definition The definition of the search index. + # @param [ nil | String ] name The name to give the new search index. + # + # @return [ String ] the name of the new search index. + def create_one(definition, name: nil) + create_many([ { name: name, definition: definition } ]).first + end + + # Create multiple search indexes with a single command. + # + # @param [ Array ] indexes The description of the indexes to + # create. Each element of the list must be a hash with a definition + # key, and an optional name key. + # + # @return [ Array ] the names of the new search indexes. + def create_many(indexes) + spec = spec_with(indexes: indexes.map { |v| validate_search_index!(v) }) + result = Operation::CreateSearchIndexes.new(spec).execute(next_primary, context: execution_context) + result.first['indexesCreated'].map { |idx| idx['name'] } + end + + # Drop the search index with the given id, or name. One or the other must + # be specified, but not both. + # + # @param [ String ] id the id of the index to drop + # @param [ String ] name the name of the index to drop + # + # @return [ Mongo::Operation::Result | false ] the result of the + # operation, or false if the given index does not exist. + def drop_one(id: nil, name: nil) + validate_id_or_name!(id, name) + + spec = spec_with(index_id: id, index_name: name) + op = Operation::DropSearchIndex.new(spec) + + # per the spec: + # Drivers MUST suppress NamespaceNotFound errors for the + # ``dropSearchIndex`` helper. Drop operations should be idempotent. + do_drop(op, nil, execution_context) + end + + # Iterate over the search indexes. + # + # @param [ Proc ] block if given, each search index will be yieleded to + # the block. + # + # @return [ self | Enumerator ] if a block is given, self is returned. + # Otherwise, an enumerator will be returned. + def each(&block) + @result ||= begin + spec = {}.tap do |s| + s[:id] = requested_index_id if requested_index_id + s[:name] = requested_index_name if requested_index_name + end + + collection.aggregate( + [ { '$listSearchIndexes' => spec } ], + aggregate_options + ) + end + + return @result.to_enum unless block + + @result.each(&block) + self + end + + # Update the search index with the given id or name. One or the other + # must be provided, but not both. + # + # @param [ Hash ] definition the definition to replace the given search + # index with. + # @param [ nil | String ] id the id of the search index to update + # @param [ nil | String ] name the name of the search index to update + # + # @return [ Mongo::Operation::Result ] the result of the operation + def update_one(definition, id: nil, name: nil) + validate_id_or_name!(id, name) + + spec = spec_with(index_id: id, index_name: name, index: definition) + Operation::UpdateSearchIndex.new(spec).execute(next_primary, context: execution_context) + end + + # The following methods are to make the view act more like an array, + # without having to explicitly make it an array... + + # Queries whether the search index enumerable is empty. + # + # @return [ true | false ] whether the enumerable is empty or not. + def empty? + count.zero? + end + + private + + # A helper method for building the specification document with certain + # values pre-populated. + # + # @param [ Hash ] extras the values to put into the specification + # + # @return [ Hash ] the specification document + def spec_with(extras) + { + coll_name: collection.name, + db_name: collection.database.name, + }.merge(extras) + end + + # A helper method for retrieving the primary server from the cluster. + # + # @return [ Mongo::Server ] the server to use + def next_primary(ping = nil, session = nil) + collection.cluster.next_primary(ping, session) + end + + # A helper method for constructing a new operation context for executing + # an operation. + # + # @return [ Mongo::Operation::Context ] the operation context + def execution_context + Operation::Context.new(client: collection.client) + end + + # Validates the given id and name, ensuring that exactly one of them + # is non-nil. + # + # @param [ nil | String ] id the id to validate + # @param [ nil | String ] name the name to validate + # + # @raise [ ArgumentError ] if neither or both arguments are nil + def validate_id_or_name!(id, name) + return unless (id.nil? && name.nil?) || (!id.nil? && !name.nil?) + + raise ArgumentError, 'exactly one of id or name must be specified' + end + + # Validates the given search index document, ensuring that it has no + # extra keys, and that the name and definition are valid. + # + # @param [ Hash ] doc the document to validate + # + # @raise [ ArgumentError ] if the document is invalid. + def validate_search_index!(doc) + validate_search_index_keys!(doc.keys) + validate_search_index_name!(doc[:name] || doc['name']) + validate_search_index_definition!(doc[:definition] || doc['definition']) + doc + end + + # Validates the keys of a search index document, ensuring that + # they are all valid. + # + # @param [ Array ] keys the keys of a search index document + # + # @raise [ ArgumentError ] if the list contains any invalid keys + def validate_search_index_keys!(keys) + extras = keys - [ 'name', 'definition', :name, :definition ] + + raise ArgumentError, "invalid keys in search index creation: #{extras.inspect}" if extras.any? + end + + # Validates the name of a search index, ensuring that it is either a + # String or nil. + # + # @param [ nil | String ] name the name of a search index + # + # @raise [ ArgumentError ] if the name is not valid + def validate_search_index_name!(name) + return if name.nil? || name.is_a?(String) + + raise ArgumentError, "search index name must be nil or a string (got #{name.inspect})" + end + + # Validates the definition of a search index. + # + # @param [ Hash ] definition the definition of a search index + # + # @raise [ ArgumentError ] if the definition is not valid + def validate_search_index_definition!(definition) + return if definition.is_a?(Hash) + + raise ArgumentError, "search index definition must be a Hash (got #{definition.inspect})" + end + end + end +end diff --git a/spec/atlas/atlas_connectivity_spec.rb b/spec/atlas/atlas_connectivity_spec.rb index 9c519dacda..0412d1bd84 100644 --- a/spec/atlas/atlas_connectivity_spec.rb +++ b/spec/atlas/atlas_connectivity_spec.rb @@ -7,11 +7,7 @@ let(:uri) { ENV['ATLAS_URI'] } let(:client) { Mongo::Client.new(uri) } - before do - if uri.nil? - skip "ATLAS_URI not set in environment" - end - end + require_atlas describe 'connection to Atlas' do it 'runs ismaster successfully' do diff --git a/spec/atlas/operations_spec.rb b/spec/atlas/operations_spec.rb index 8ac9495e67..8a46ab3702 100644 --- a/spec/atlas/operations_spec.rb +++ b/spec/atlas/operations_spec.rb @@ -7,11 +7,7 @@ let(:uri) { ENV['ATLAS_URI'] } let(:client) { Mongo::Client.new(uri) } - before do - if uri.nil? - skip "ATLAS_URI not set in environment" - end - end + require_atlas describe 'ping' do it 'works' do diff --git a/spec/integration/search_indexes_prose_spec.rb b/spec/integration/search_indexes_prose_spec.rb new file mode 100644 index 0000000000..0a17accaf2 --- /dev/null +++ b/spec/integration/search_indexes_prose_spec.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require 'spec_helper' + +class SearchIndexHelper + attr_reader :client, :collection_name + + def initialize(client) + @client = client + + # https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#id4 + # "...each test uses a randomly generated collection name. Drivers may + # generate this collection name however they like, but a suggested + # implementation is a hex representation of an ObjectId..." + @collection_name = BSON::ObjectId.new.to_s + end + + # `soft_create` means to create the collection object without forcing it to + # be created in the database. + def collection(soft_create: false) + @collection ||= client.database[collection_name].tap do |collection| + collection.create unless soft_create + end + end + + # Wait for all of the indexes with the given names to be ready; then return + # the list of index definitions corresponding to those names. + def wait_for(*names, &condition) + timeboxed_wait do + result = collection.search_indexes + return filter_results(result, names) if names.all? { |name| ready?(result, name, &condition) } + end + end + + # Wait until all of the indexes with the given names are absent from the + # search index list. + def wait_for_absense_of(*names) + names.each do |name| + timeboxed_wait do + break if collection.search_indexes(name: name).empty? + end + end + end + + private + + def timeboxed_wait(step: 5, max: 300) + start = Mongo::Utils.monotonic_time + + loop do + yield + + sleep step + raise Timeout::Error, 'wait took too long' if Mongo::Utils.monotonic_time - start > max + end + end + + # Returns true if the list of search indexes includes one with the given name, + # which is ready to be queried. + def ready?(list, name, &condition) + condition ||= ->(index) { index['queryable'] } + list.any? { |index| index['name'] == name && condition[index] } + end + + def filter_results(result, names) + result.select { |index| names.include?(index['name']) } + end +end + +describe 'Mongo::Collection#search_indexes prose tests' do + # https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#id5 + # "These tests must run against an Atlas cluster with a 7.0+ server." + require_atlas + + let(:client) do + Mongo::Client.new( + ENV['ATLAS_URI'], + database: SpecConfig.instance.test_db, + ssl: true, + ssl_verify: true + ) + end + + let(:helper) { SearchIndexHelper.new(client) } + + let(:name) { 'test-search-index' } + let(:definition) { { 'mappings' => { 'dynamic' => false } } } + let(:create_index) { helper.collection.search_indexes.create_one(definition, name: name) } + + # Case 1: Driver can successfully create and list search indexes + context 'when creating and listing search indexes' do + let(:index) { helper.wait_for(name).first } + + it 'succeeds' do + expect(create_index).to be == name + expect(index['latestDefinition']).to be == definition + end + end + + # Case 2: Driver can successfully create multiple indexes in batch + context 'when creating multiple indexes in batch' do + let(:specs) do + [ + { 'name' => 'test-search-index-1', 'definition' => definition }, + { 'name' => 'test-search-index-2', 'definition' => definition } + ] + end + + let(:names) { specs.map { |spec| spec['name'] } } + let(:create_indexes) { helper.collection.search_indexes.create_many(specs) } + + let(:indexes) { helper.wait_for(*names) } + + let(:index1) { indexes[0] } + let(:index2) { indexes[1] } + + it 'succeeds' do + expect(create_indexes).to be == names + expect(index1['latestDefinition']).to be == specs[0]['definition'] + expect(index2['latestDefinition']).to be == specs[1]['definition'] + end + end + + # Case 3: Driver can successfully drop search indexes + context 'when dropping search indexes' do + it 'succeeds' do + expect(create_index).to be == name + helper.wait_for(name) + + helper.collection.search_indexes.drop_one(name: name) + + expect { helper.wait_for_absense_of(name) }.not_to raise_error + end + end + + # Case 4: Driver can update a search index + context 'when updating search indexes' do + let(:new_definition) { { 'mappings' => { 'dynamic' => true } } } + + let(:index) do + helper + .wait_for(name) { |idx| idx['queryable'] && idx['status'] == 'READY' } + .first + end + + # rubocop:disable RSpec/ExampleLength + it 'succeeds' do + expect(create_index).to be == name + helper.wait_for(name) + + expect do + helper.collection.search_indexes.update_one(new_definition, name: name) + end.not_to raise_error + + expect(index['latestDefinition']).to be == new_definition + end + # rubocop:enable RSpec/ExampleLength + end + + # Case 5: dropSearchIndex suppresses namespace not found errors + context 'when dropping a non-existent search index' do + it 'ignores `namespace not found` errors' do + collection = helper.collection(soft_create: true) + expect { collection.search_indexes.drop_one(name: name) } + .not_to raise_error + end + end +end diff --git a/spec/lite_spec_helper.rb b/spec/lite_spec_helper.rb index 7bd6675a70..f7db224690 100644 --- a/spec/lite_spec_helper.rb +++ b/spec/lite_spec_helper.rb @@ -106,6 +106,31 @@ module Mrss class ExampleTimeout < StandardError; end +STANDARD_TIMEOUTS = { + stress: 210, + jruby: 90, + default: 45, +}.freeze + +def timeout_type + if ENV['EXAMPLE_TIMEOUT'].to_i > 0 + :custom + elsif %w(1 true yes).include?(ENV['STRESS']&.downcase) + :stress + elsif BSON::Environment.jruby? + :jruby + else + :default + end +end + +def example_timeout_seconds + STANDARD_TIMEOUTS.fetch( + timeout_type, + (ENV['EXAMPLE_TIMEOUT'] || STANDARD_TIMEOUTS[:default]).to_i + ) +end + RSpec.configure do |config| config.extend(CommonShortcuts::ClassMethods) config.include(CommonShortcuts::InstanceMethods) @@ -123,6 +148,12 @@ def require_solo end end + def require_atlas + before do + skip 'Set ATLAS_URI in environment to run atlas tests' if ENV['ATLAS_URI'].nil? + end + end + if SpecConfig.instance.ci? SdamFormatterIntegration.subscribe config.add_formatter(JsonExtFormatter, File.join(File.dirname(__FILE__), '../tmp/rspec.json')) @@ -141,16 +172,7 @@ def require_solo # Tests should take under 10 seconds ideally but it seems # we have some that run for more than 10 seconds in CI. config.around(:each) do |example| - timeout = if %w(1 true yes).include?(ENV['STRESS']&.downcase) - 210 - else - if BSON::Environment.jruby? - 90 - else - 45 - end - end - TimeoutInterrupt.timeout(timeout, ExampleTimeout) do + TimeoutInterrupt.timeout(example_timeout_seconds, ExampleTimeout) do example.run end end diff --git a/spec/runners/unified/search_index_operations.rb b/spec/runners/unified/search_index_operations.rb new file mode 100644 index 0000000000..d74ab57776 --- /dev/null +++ b/spec/runners/unified/search_index_operations.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Unified + # The definitions of available search index operations, as used by the + # unified tests. + module SearchIndexOperations + def create_search_index(op) + collection = entities.get(:collection, op.use!('object')) + + use_arguments(op) do |args| + model = args.use('model') + name = model.use('name') + definition = model.use('definition') + collection.search_indexes.create_one(definition, name: name) + end + end + + def create_search_indexes(op) + collection = entities.get(:collection, op.use!('object')) + + use_arguments(op) do |args| + models = args.use('models') + collection.search_indexes.create_many(models) + end + end + + def drop_search_index(op) + collection = entities.get(:collection, op.use!('object')) + + use_arguments(op) do |args| + collection.search_indexes.drop_one( + id: args.use('id'), + name: args.use('name') + ) + end + end + + def list_search_indexes(op) + collection = entities.get(:collection, op.use!('object')) + + use_arguments(op) do |args| + agg_opts = args.use('aggregationOptions') || {} + collection.search_indexes( + id: args.use('id'), + name: args.use('name'), + aggregate: ::Utils.underscore_hash(agg_opts) + ).to_a + end + end + + def update_search_index(op) + collection = entities.get(:collection, op.use!('object')) + + use_arguments(op) do |args| + collection.search_indexes.update_one( + args.use('definition'), + id: args.use('id'), + name: args.use('name') + ) + end + end + end +end diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index bc48a01d65..fb9083e6e9 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -9,6 +9,7 @@ require 'runners/unified/change_stream_operations' require 'runners/unified/support_operations' require 'runners/unified/thread_operations' +require 'runners/unified/search_index_operations' require 'runners/unified/assertions' require 'support/utils' require 'support/crypt' @@ -23,6 +24,7 @@ class Test include ChangeStreamOperations include SupportOperations include ThreadOperations + include SearchIndexOperations include Assertions include RSpec::Core::Pending @@ -120,7 +122,7 @@ def generate_entities(es) # the other set members, in standalone deployments because # there is only one server, but changes behavior in # sharded clusters compared to how the test suite is configured. - opts[:single_address] = true + options[:single_address] = true end if store_events = spec.use('storeEventsAsEntities') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3684894c54..f3ffc785e2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,7 +20,7 @@ config.extend(Constraints) config.before(:all) do - if ClusterConfig.instance.fcv_ish >= '3.6' && !SpecConfig.instance.serverless? # Serverless instances do not support killAllSessions command. + if SpecConfig.instance.kill_all_server_sessions? kill_all_server_sessions end end diff --git a/spec/spec_tests/data/index_management/createSearchIndex.yml b/spec/spec_tests/data/index_management/createSearchIndex.yml new file mode 100644 index 0000000000..6aa56f3bc4 --- /dev/null +++ b/spec/spec_tests/data/index_management/createSearchIndex.yml @@ -0,0 +1,62 @@ +description: "createSearchIndex" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "no name provided for an index definition" + operations: + - name: createSearchIndex + object: *collection0 + arguments: + model: { definition: &definition { mappings: { dynamic: true } } } + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition } ] + $db: *database0 + + - description: "name provided for an index definition" + operations: + - name: createSearchIndex + object: *collection0 + arguments: + model: { definition: &definition { mappings: { dynamic: true } } , name: 'test index' } + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index' } ] + $db: *database0 \ No newline at end of file diff --git a/spec/spec_tests/data/index_management/createSearchIndexes.yml b/spec/spec_tests/data/index_management/createSearchIndexes.yml new file mode 100644 index 0000000000..54a6e84ccb --- /dev/null +++ b/spec/spec_tests/data/index_management/createSearchIndexes.yml @@ -0,0 +1,83 @@ +description: "createSearchIndexes" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "empty index definition array" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [] + $db: *database0 + + + - description: "no name provided for an index definition" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [ { definition: &definition { mappings: { dynamic: true } } } ] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition } ] + $db: *database0 + + - description: "name provided for an index definition" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [ { definition: &definition { mappings: { dynamic: true } } , name: 'test index' } ] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index' } ] + $db: *database0 \ No newline at end of file diff --git a/spec/spec_tests/data/index_management/dropSearchIndex.yml b/spec/spec_tests/data/index_management/dropSearchIndex.yml new file mode 100644 index 0000000000..e384cf26c5 --- /dev/null +++ b/spec/spec_tests/data/index_management/dropSearchIndex.yml @@ -0,0 +1,42 @@ +description: "dropSearchIndex" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "sends the correct command" + operations: + - name: dropSearchIndex + object: *collection0 + arguments: + name: &indexName 'test index' + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + dropSearchIndex: *collection0 + name: *indexName + $db: *database0 diff --git a/spec/spec_tests/data/index_management/listSearchIndexes.yml b/spec/spec_tests/data/index_management/listSearchIndexes.yml new file mode 100644 index 0000000000..a50becdf1d --- /dev/null +++ b/spec/spec_tests/data/index_management/listSearchIndexes.yml @@ -0,0 +1,85 @@ +description: "listSearchIndexes" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "when no name is provided, it does not populate the filter" + operations: + - name: listSearchIndexes + object: *collection0 + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + pipeline: + - $listSearchIndexes: {} + + - description: "when a name is provided, it is present in the filter" + operations: + - name: listSearchIndexes + object: *collection0 + arguments: + name: &indexName "test index" + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + pipeline: + - $listSearchIndexes: { name: *indexName } + $db: *database0 + + - description: aggregation cursor options are supported + operations: + - name: listSearchIndexes + object: *collection0 + arguments: + name: &indexName "test index" + aggregationOptions: + batchSize: 10 + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + cursor: { batchSize: 10 } + pipeline: + - $listSearchIndexes: { name: *indexName } + $db: *database0 \ No newline at end of file diff --git a/spec/spec_tests/data/index_management/updateSearchIndex.yml b/spec/spec_tests/data/index_management/updateSearchIndex.yml new file mode 100644 index 0000000000..bb18ab512e --- /dev/null +++ b/spec/spec_tests/data/index_management/updateSearchIndex.yml @@ -0,0 +1,45 @@ +description: "updateSearchIndex" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "sends the correct command" + operations: + - name: updateSearchIndex + object: *collection0 + arguments: + name: &indexName 'test index' + definition: &definition {} + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + isError: true + errorContains: Search index commands are only supported with Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + updateSearchIndex: *collection0 + name: *indexName + definition: *definition + $db: *database0 + diff --git a/spec/spec_tests/index_management_unified_spec.rb b/spec/spec_tests/index_management_unified_spec.rb new file mode 100644 index 0000000000..e93d30cfb5 --- /dev/null +++ b/spec/spec_tests/index_management_unified_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'runners/unified' + +base = "#{CURRENT_PATH}/spec_tests/data/index_management" +INDEX_MANAGEMENT_UNIFIED_TESTS = Dir.glob("#{base}/**/*.yml").sort + +# rubocop:disable RSpec/EmptyExampleGroup +describe 'index management unified spec tests' do + define_unified_spec_tests(base, INDEX_MANAGEMENT_UNIFIED_TESTS) +end +# rubocop:enable RSpec/EmptyExampleGroup diff --git a/spec/support/spec_config.rb b/spec/support/spec_config.rb index eb090db2ac..34d6c6d478 100644 --- a/spec/support/spec_config.rb +++ b/spec/support/spec_config.rb @@ -172,6 +172,11 @@ def serverless? !!ENV['SERVERLESS'] end + def kill_all_server_sessions? + !serverless? && # Serverless instances do not support killAllSessions command. + ClusterConfig.instance.fcv_ish >= '3.6' + end + # Test suite configuration def client_debug? From 5cf1f105491a21c76262cdd707bfa70fa393ba80 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 13 Sep 2023 08:01:31 -0600 Subject: [PATCH 155/198] RUBY-3324 bump drivers-evergreen-tools to get updated atlas setup/teardown (#2780) also, expose task_id expansion as environment variable to those scripts --- .evergreen/config.yml | 2 ++ .evergreen/config/common.yml.erb | 2 ++ .mod/drivers-evergreen-tools | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index e43a13b7b3..b969649c3c 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -489,6 +489,7 @@ task_groups: DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" \ LAMBDA_STACK_NAME="dbx-ruby-lambda" \ MONGODB_VERSION="7.0" \ + task_id="${task_id}" \ $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh - command: expansions.update params: @@ -505,6 +506,7 @@ task_groups: DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + task_id="${task_id}" \ $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh tasks: - test-full-atlas-task diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 306adc1b7a..c5a6137fa4 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -486,6 +486,7 @@ task_groups: DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" \ LAMBDA_STACK_NAME="dbx-ruby-lambda" \ MONGODB_VERSION="7.0" \ + task_id="${task_id}" \ $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh - command: expansions.update params: @@ -502,6 +503,7 @@ task_groups: DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + task_id="${task_id}" \ $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh tasks: - test-full-atlas-task diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index 0361b3fd3a..6f42c3b6ea 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit 0361b3fd3a9f1641518aa2f37d872d3346e8a450 +Subproject commit 6f42c3b6ea3efecb900781504caabea91e0922a6 From dd27df7b63b0add1e2f1c1b8ab2c59fd3d868a7b Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 18 Sep 2023 10:50:43 -0600 Subject: [PATCH 156/198] RUBY-3328 add `execution` expansion to environment for atlas cluster name uniqueness (#2783) --- .evergreen/config.yml | 2 ++ .evergreen/config/common.yml.erb | 2 ++ .mod/drivers-evergreen-tools | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index b969649c3c..91d3b78bea 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -490,6 +490,7 @@ task_groups: LAMBDA_STACK_NAME="dbx-ruby-lambda" \ MONGODB_VERSION="7.0" \ task_id="${task_id}" \ + execution="${execution}" \ $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh - command: expansions.update params: @@ -507,6 +508,7 @@ task_groups: DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ LAMBDA_STACK_NAME="dbx-ruby-lambda" \ task_id="${task_id}" \ + execution="${execution}" \ $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh tasks: - test-full-atlas-task diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index c5a6137fa4..e78f08d846 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -487,6 +487,7 @@ task_groups: LAMBDA_STACK_NAME="dbx-ruby-lambda" \ MONGODB_VERSION="7.0" \ task_id="${task_id}" \ + execution="${execution}" \ $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh - command: expansions.update params: @@ -504,6 +505,7 @@ task_groups: DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ LAMBDA_STACK_NAME="dbx-ruby-lambda" \ task_id="${task_id}" \ + execution="${execution}" \ $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh tasks: - test-full-atlas-task diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index 6f42c3b6ea..1f018c7a24 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit 6f42c3b6ea3efecb900781504caabea91e0922a6 +Subproject commit 1f018c7a248c4fcda6cb7a77043fd673755e0986 From cddfeeddd18e4bca27e94ab8ca70abeb183709d2 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 18 Sep 2023 19:06:42 +0200 Subject: [PATCH 157/198] RUBY-3372 Fix failing explain tests (#2782) --- spec/mongo/collection/view/aggregation_spec.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/mongo/collection/view/aggregation_spec.rb b/spec/mongo/collection/view/aggregation_spec.rb index 7657460697..da363d9992 100644 --- a/spec/mongo/collection/view/aggregation_spec.rb +++ b/spec/mongo/collection/view/aggregation_spec.rb @@ -321,7 +321,12 @@ min_server_fcv '4.2' let(:result) do - aggregation.explain['queryPlanner']['collation']['locale'] + if aggregation.explain.key?('queryPlanner') + aggregation.explain['queryPlanner']['collation']['locale'] + else + # 7.2+ sharded cluster + aggregation.explain['shards'].first.last['queryPlanner']['collation']['locale'] + end end it_behaves_like 'applies the collation' From e7b4a221961a98282d7309d7d069a2d96294e9f0 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 18 Sep 2023 19:07:23 +0200 Subject: [PATCH 158/198] RUBY-3279 Bump min server version for FLE test (#2779) --- spec/spec_tests/data/client_side_encryption/explain.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/spec_tests/data/client_side_encryption/explain.yml b/spec/spec_tests/data/client_side_encryption/explain.yml index c0dd9c57c0..5d1874565b 100644 --- a/spec/spec_tests/data/client_side_encryption/explain.yml +++ b/spec/spec_tests/data/client_side_encryption/explain.yml @@ -1,5 +1,5 @@ runOn: - - minServerVersion: "4.1.10" + - minServerVersion: "7.0.0" database_name: &database_name "default" collection_name: &collection_name "default" @@ -54,4 +54,4 @@ tests: # Outcome is checked using a separate MongoClient without auto encryption. data: - *doc0_encrypted - - *doc1_encrypted \ No newline at end of file + - *doc1_encrypted From bbc09415e2c39bf2830125b7e4105404a5d0d4d3 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 19 Sep 2023 08:48:33 -0600 Subject: [PATCH 159/198] RUBY-3267 make specs pass when X.509 authentication is active (#2781) * RUBY-3267 ignore SDAM unified specs when X.509 auth is enabled This is because the SDAM unified specs set a failpoint for an authentication conversation that is never used with X.509 authentication, and so can not pass with that active. * re-enable the x.509 specs * auth flow here assumes SASL; X.509 mechanism is incompatible --- .evergreen/config.yml | 33 +++++++++++++--------------- .evergreen/config/standard.yml.erb | 33 +++++++++++++--------------- spec/mongo/server/connection_spec.rb | 4 ++++ spec/spec_tests/sdam_unified_spec.rb | 2 ++ spec/support/constraints.rb | 6 +++++ 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 91d3b78bea..76631a375a 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1304,24 +1304,21 @@ buildvariants: tasks: - name: "test-mlaunch" - # The X.509 tests are failing with exceptions being expected, but never - # raised. (RUBY-3267) - # - # - matrix_name: "x509-tests" - # matrix_spec: - # auth-and-ssl: "x509" - # ruby: "ruby-3.2" - # # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure - # # the server for certain auth mechanisms. Once run-tests.sh is made smart - # # enough to install mongosh, and then use either mongo or mongosh - # # (depending on server version and what's available), we can bump this to - # # the latest stable db version. - # mongodb-version: "5.3" - # topology: standalone - # os: rhel8 - # display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - # tasks: - # - name: "test-mlaunch" + - matrix_name: "x509-tests" + matrix_spec: + auth-and-ssl: "x509" + ruby: "ruby-3.2" + # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure + # the server for certain auth mechanisms. Once run-tests.sh is made smart + # enough to install mongosh, and then use either mongo or mongosh + # (depending on server version and what's available), we can bump this to + # the latest stable db version. + mongodb-version: "5.3" + topology: standalone + os: rhel8 + display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + tasks: + - name: "test-mlaunch" - matrix_name: "jruby-auth" matrix_spec: diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index 3bcd1b9acb..89cbc81ef1 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -232,24 +232,21 @@ buildvariants: tasks: - name: "test-mlaunch" - # The X.509 tests are failing with exceptions being expected, but never - # raised. (RUBY-3267) - # - # - matrix_name: "x509-tests" - # matrix_spec: - # auth-and-ssl: "x509" - # ruby: <%= latest_ruby %> - # # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure - # # the server for certain auth mechanisms. Once run-tests.sh is made smart - # # enough to install mongosh, and then use either mongo or mongosh - # # (depending on server version and what's available), we can bump this to - # # the latest stable db version. - # mongodb-version: <%= latest_5x_mdb %> - # topology: standalone - # os: rhel8 - # display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - # tasks: - # - name: "test-mlaunch" + - matrix_name: "x509-tests" + matrix_spec: + auth-and-ssl: "x509" + ruby: <%= latest_ruby %> + # needs the latest_5x_mdb because run-tests.sh uses `mongo` to configure + # the server for certain auth mechanisms. Once run-tests.sh is made smart + # enough to install mongosh, and then use either mongo or mongosh + # (depending on server version and what's available), we can bump this to + # the latest stable db version. + mongodb-version: <%= latest_5x_mdb %> + topology: standalone + os: rhel8 + display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + tasks: + - name: "test-mlaunch" - matrix_name: "jruby-auth" matrix_spec: diff --git a/spec/mongo/server/connection_spec.rb b/spec/mongo/server/connection_spec.rb index f1e0dabc56..a1acf2f69c 100644 --- a/spec/mongo/server/connection_spec.rb +++ b/spec/mongo/server/connection_spec.rb @@ -265,6 +265,10 @@ class ConnectionSpecTestException < Exception; end context 'when #authenticate! raises an exception' do require_auth + # because the mock/stub flow here doesn't cover the flow used by + # the X.509 authentication mechanism... + forbid_x509_auth + let(:server_options) do Mongo::Client.canonicalize_ruby_options( SpecConfig.instance.all_test_options, diff --git a/spec/spec_tests/sdam_unified_spec.rb b/spec/spec_tests/sdam_unified_spec.rb index 8454edc004..21d6e1cbb3 100644 --- a/spec/spec_tests/sdam_unified_spec.rb +++ b/spec/spec_tests/sdam_unified_spec.rb @@ -9,5 +9,7 @@ SDAM_UNIFIED_TESTS = Dir.glob("#{base}/**/*.yml").sort describe 'SDAM unified spec tests' do + forbid_x509_auth + define_unified_spec_tests(base, SDAM_UNIFIED_TESTS) end diff --git a/spec/support/constraints.rb b/spec/support/constraints.rb index 47057743fb..6d92409937 100644 --- a/spec/support/constraints.rb +++ b/spec/support/constraints.rb @@ -17,6 +17,12 @@ def require_local_tls end end + def forbid_x509_auth + before(:all) do + skip 'X.509 auth not allowed' if SpecConfig.instance.x509_auth? + end + end + def max_bson_version(version) required_version = version.split('.').map(&:to_i) actual_version = bson_version(required_version.length) From 7fd9ad4e7cdd35867b4132edb181aa03f2becbfc Mon Sep 17 00:00:00 2001 From: Chris Cho Date: Thu, 21 Sep 2023 15:46:59 -0400 Subject: [PATCH 160/198] DOCSP-32949: docs link to the BSON 4.x Tutorial (#2784) * DOCSP-32949: docs link to the BSON 4.x Tutorial * resolve link errors --------- Co-authored-by: Jamis Buck --- docs/index.txt | 5 ++--- docs/reference/crud-operations.txt | 2 +- docs/tutorials.txt | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 79358a0ac5..0493d6c63f 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -25,7 +25,7 @@ BSON The Ruby BSON implementation is packaged in a separate gem with C and Java extensions for speed depending on the runtime enviroment. -For reference on the Ruby BSON gem, see :doc:`/bson-tutorials`. +For reference on the Ruby BSON gem, see the :doc:`/tutorials/bson-v4`. Object Mappers ============== @@ -61,8 +61,7 @@ For tutorials on Mongoid, see the `Mongoid Manual + API release-notes reference/additional-resources contribute diff --git a/docs/reference/crud-operations.txt b/docs/reference/crud-operations.txt index bbae4c202a..f482adcbe3 100644 --- a/docs/reference/crud-operations.txt +++ b/docs/reference/crud-operations.txt @@ -93,7 +93,7 @@ Specify a ``Decimal128`` number .. versionadded:: 3.4 :manual:`Decimal128` is a -:doc:`BSON datatype ` +:doc:`BSON datatype ` that employs 128-bit decimal-based floating-point values capable of emulating decimal rounding with exact precision. This functionality is intended for applications that handle diff --git a/docs/tutorials.txt b/docs/tutorials.txt index 00d8ce7b1e..1272795ed6 100644 --- a/docs/tutorials.txt +++ b/docs/tutorials.txt @@ -15,3 +15,5 @@ operations available in the Ruby driver. tutorials/quick-start tutorials/common-errors + tutorials/bson-v4 + From bfe7a35f2cd173c37a101b624a90b6ba99e97c54 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 5 Oct 2023 16:56:22 +0200 Subject: [PATCH 161/198] RUBY-2706 Test find related options (#2789) * 2706 * Add unified test for all find options * Add missing options * Clarify collation on collection * Add tests for read_concern option * Add tests for read_preference option --- lib/mongo/collection.rb | 4 +- lib/mongo/collection/view/iterable.rb | 1 + spec/integration/find_options_spec.rb | 190 ++++++++++ spec/runners/unified/crud_operations.rb | 12 + .../crud_unified/find-test-all-options.yml | 345 ++++++++++++++++++ 5 files changed, 551 insertions(+), 1 deletion(-) create mode 100644 spec/integration/find_options_spec.rb create mode 100644 spec/spec_tests/data/crud_unified/find-test-all-options.yml diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 90514f203f..58d56e1b94 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -339,7 +339,9 @@ def capped? # inserted or updated documents where the clustered index key value # matches an existing value in the index. # - *:name* -- Optional. A name that uniquely identifies the clustered index. - # @option opts [ Hash ] :collation The collation to use. + # @option opts [ Hash ] :collation The collation to use when creating the + # collection. This option will not be sent to the server when calling + # collection methods. # @option opts [ Hash ] :encrypted_fields Hash describing encrypted fields # for queryable encryption. # @option opts [ Integer ] :expire_after Number indicating diff --git a/lib/mongo/collection/view/iterable.rb b/lib/mongo/collection/view/iterable.rb index f9d7b1603a..159719bcb1 100644 --- a/lib/mongo/collection/view/iterable.rb +++ b/lib/mongo/collection/view/iterable.rb @@ -162,6 +162,7 @@ def initial_query_op(session) let: options[:let], limit: limit, allow_disk_use: options[:allow_disk_use], + allow_partial_results: options[:allow_partial_results], read: read, read_concern: options[:read_concern] || read_concern, batch_size: batch_size, diff --git a/spec/integration/find_options_spec.rb b/spec/integration/find_options_spec.rb new file mode 100644 index 0000000000..e5a822a3ea --- /dev/null +++ b/spec/integration/find_options_spec.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Find operation options' do + require_mri + require_no_auth + min_server_fcv '4.4' + + let(:subscriber) { Mrss::EventSubscriber.new } + + let(:seeds) do + [ SpecConfig.instance.addresses.first ] + end + + let(:client) do + ClientRegistry.instance.new_local_client( + seeds, + SpecConfig.instance.test_options.merge(client_options) + ).tap do |client| + client.subscribe(Mongo::Monitoring::COMMAND, subscriber) + end + end + + let(:collection) do + client['find_options', collection_options] + end + + let(:find_command) do + subscriber.started_events.find { |cmd| cmd.command_name == 'find' } + end + + before do + ClientRegistry.instance.global_client('authorized')['find_options'].drop + collection.insert_many([ { a: 1 }, { a: 2 }, { a: 3 } ]) + end + + describe 'collation' do + let(:client_options) do + {} + end + + let(:collation) do + { 'locale' => 'en_US' } + end + + context 'when defined on the collection' do + let(:collection_options) do + { collation: collation } + end + + it 'uses the collation defined on the collection' do + collection.find.to_a + expect(find_command.command['collation']).to be_nil + end + end + + context 'when defined on the operation' do + let(:collection_options) do + {} + end + + it 'uses the collation defined on the collection' do + collection.find({}, collation: collation).to_a + expect(find_command.command['collation']).to eq(collation) + end + end + + context 'when defined on both collection and operation' do + let(:collection_options) do + { 'locale' => 'de_AT' } + end + + it 'uses the collation defined on the collection' do + collection.find({}, collation: collation).to_a + expect(find_command.command['collation']).to eq(collation) + end + end + end + + describe 'read concern' do + context 'when defined on the client' do + let(:client_options) do + { read_concern: { level: :local } } + end + + let(:collection_options) do + {} + end + + it 'uses the read concern defined on the client' do + collection.find.to_a + expect(find_command.command['readConcern']).to eq('level' => 'local') + end + + context 'when defined on the collection' do + let(:collection_options) do + { read_concern: { level: :majority } } + end + + it 'uses the read concern defined on the collection' do + collection.find.to_a + expect(find_command.command['readConcern']).to eq('level' => 'majority') + end + + context 'when defined on the operation' do + let(:operation_read_concern) do + { level: :available } + end + + it 'uses the read concern defined on the operation' do + collection.find({}, read_concern: operation_read_concern).to_a + expect(find_command.command['readConcern']).to eq('level' => 'available') + end + end + end + + context 'when defined on the operation' do + let(:collection_options) do + {} + end + + let(:operation_read_concern) do + { level: :available } + end + + it 'uses the read concern defined on the operation' do + collection.find({}, read_concern: operation_read_concern).to_a + expect(find_command.command['readConcern']).to eq('level' => 'available') + end + end + end + + context 'when defined on the collection' do + let(:client_options) do + {} + end + + let(:collection_options) do + { read_concern: { level: :majority } } + end + + it 'uses the read concern defined on the collection' do + collection.find.to_a + expect(find_command.command['readConcern']).to eq('level' => 'majority') + end + + context 'when defined on the operation' do + let(:operation_read_concern) do + { level: :available } + end + + it 'uses the read concern defined on the operation' do + collection.find({}, read_concern: operation_read_concern).to_a + expect(find_command.command['readConcern']).to eq('level' => 'available') + end + end + end + end + + describe 'read preference' do + require_topology :replica_set + + context 'when defined on the client' do + let(:client_options) do + { read: { mode: :secondary } } + end + + let(:collection_options) do + {} + end + + it 'uses the read preference defined on the client' do + collection.find.to_a + expect(find_command.command['$readPreference']).to eq('mode' => 'secondary') + end + + context 'when defined on the collection' do + let(:collection_options) do + { read: { mode: :secondary_preferred } } + end + + it 'uses the read concern defined on the collection' do + collection.find.to_a + expect(find_command.command['$readPreference']).to eq('mode' => 'secondaryPreferred') + end + end + end + end +end diff --git a/spec/runners/unified/crud_operations.rb b/spec/runners/unified/crud_operations.rb index 9274bf62df..69e35513a8 100644 --- a/spec/runners/unified/crud_operations.rb +++ b/spec/runners/unified/crud_operations.rb @@ -32,6 +32,18 @@ def get_find_view(op) if session = args.use('session') opts[:session] = entities.get(:session, session) end + if collation = args.use('collation') + opts[:collation] = collation + end + if args.key?('noCursorTimeout') + opts[:no_cursor_timeout] = args.use('noCursorTimeout') + end + if args.key?('oplogReplay') + opts[:oplog_replay] = args.use('oplogReplay') + end + if args.key?('allowPartialResults') + opts[:allow_partial_results] = args.use('allowPartialResults') + end req = collection.find(args.use!('filter'), **opts) if batch_size = args.use('batchSize') req = req.batch_size(batch_size) diff --git a/spec/spec_tests/data/crud_unified/find-test-all-options.yml b/spec/spec_tests/data/crud_unified/find-test-all-options.yml new file mode 100644 index 0000000000..24be5ee08d --- /dev/null +++ b/spec/spec_tests/data/crud_unified/find-test-all-options.yml @@ -0,0 +1,345 @@ +description: "find options" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name find-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +tests: + - description: "sort" + operations: + - name: find + arguments: + filter: &filter { _name: "John" } + sort: &sort { _id: 1 } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + sort: *sort + commandName: find + + - description: "projection" + operations: + - name: find + arguments: + filter: *filter + projection: &projection { _id: 1 } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + projection: *projection + commandName: find + databaseName: *database0Name + + - description: "hint" + operations: + - name: find + arguments: + filter: *filter + hint: &hint { _id: 1 } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + hint: *hint + commandName: find + databaseName: *database0Name + + - description: "skip" + operations: + - name: find + arguments: + filter: *filter + skip: &skip 10 + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + skip: *skip + commandName: find + databaseName: *database0Name + + - description: "limit" + operations: + - name: find + arguments: + filter: *filter + limit: &limit 10 + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + limit: *limit + commandName: find + databaseName: *database0Name + + - description: "batchSize" + operations: + - name: find + arguments: + filter: *filter + batchSize: &batchSize 10 + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + batchSize: *batchSize + commandName: find + databaseName: *database0Name + + - description: "comment" + operations: + - name: find + arguments: + filter: *filter + comment: &comment 'comment' + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + comment: *comment + commandName: find + databaseName: *database0Name + + - description: "maxTimeMS" + operations: + - name: find + arguments: + filter: *filter + maxTimeMS: &maxTimeMS 1000 + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + maxTimeMS: *maxTimeMS + commandName: find + databaseName: *database0Name + + - description: "max" + operations: + - name: find + arguments: + filter: *filter + max: &max { _id: 10 } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + max: *max + commandName: find + databaseName: *database0Name + + - description: "min" + operations: + - name: find + arguments: + filter: *filter + hint: { name: 1 } + min: &min { name: 'John' } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + min: *min + commandName: find + databaseName: *database0Name + + - description: "returnKey" + operations: + - name: find + arguments: + filter: *filter + returnKey: &returnKey false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + returnKey: *returnKey + commandName: find + databaseName: *database0Name + + - description: "showRecordId" + operations: + - name: find + arguments: + filter: *filter + showRecordId: &showRecordId false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + showRecordId: *showRecordId + commandName: find + databaseName: *database0Name + + - description: "oplogReplay" + operations: + - name: find + arguments: + filter: *filter + oplogReplay: &oplogReplay false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + oplogReplay: *oplogReplay + commandName: find + databaseName: *database0Name + + - description: "noCursorTimeout" + operations: + - name: find + arguments: + filter: *filter + noCursorTimeout: &noCursorTimeout false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + noCursorTimeout: *noCursorTimeout + commandName: find + databaseName: *database0Name + + - description: "allowPartialResults" + operations: + - name: find + arguments: + filter: *filter + allowPartialResults: &allowPartialResults false + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + allowPartialResults: *allowPartialResults + commandName: find + databaseName: *database0Name + + - description: "collation" + operations: + - name: find + arguments: + filter: *filter + collation: &collation { locale: "en" } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + collation: *collation + commandName: find + databaseName: *database0Name + + - description: "allowDiskUse" + runOnRequirements: + - minServerVersion: 4.4 + operations: + - name: find + arguments: + filter: *filter + allowDiskUse: &allowDiskUse true + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + allowDiskUse: *allowDiskUse + commandName: find + databaseName: *database0Name + + - description: "let" + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: find + arguments: + filter: *filter + let: &let { name: "Mary" } + object: *collection0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: *filter + let: *let + commandName: find + databaseName: *database0Name From e9bed683289b1bce738c43d71aa0670afb3edb58 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Fri, 6 Oct 2023 13:58:38 -0400 Subject: [PATCH 162/198] RUBY-3331: Inline server lifecycle callout (#2790) --- docs/reference/driver-compatibility.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/reference/driver-compatibility.txt b/docs/reference/driver-compatibility.txt index 0402c17f83..664b19149d 100644 --- a/docs/reference/driver-compatibility.txt +++ b/docs/reference/driver-compatibility.txt @@ -28,6 +28,13 @@ particular version of the driver will generally work with newer versions of the server but may not take advantage of the functionality released in the newer version of the server. +.. important:: + + MongoDB ensures compatibility between the MongoDB Server and the drivers + for three years after the server version's end of life (EOL) date. To learn + more about the MongoDB release and EOL dates, see + `MongoDB Software Lifecycle Schedules `__. + The first column lists the driver versions.“D” in other columns means support for that MongoDB version is deprecated and will be removed in a future driver version. From 16b0d29fa93564fe99bc0736fc8843e38eebd416 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 11 Oct 2023 08:13:44 -0600 Subject: [PATCH 163/198] RUBY-3296 Write to log when non-genuine host is detected (#2791) * RUBY-3296 write to log when non-genuine host is detected * rubocop appeasement * apparently the Lint/Debugger rubocop is needed in CI, but not locally? --- lib/mongo/cluster.rb | 26 +++++++++++++++++++++ lib/mongo/cluster/topology/base.rb | 16 +++++++++++++ spec/mongo/cluster_spec.rb | 36 ++++++++++++++++++++++++++++++ spec/support/recording_logger.rb | 27 ++++++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 spec/support/recording_logger.rb diff --git a/lib/mongo/cluster.rb b/lib/mongo/cluster.rb index b5981c7471..509a66f2e3 100644 --- a/lib/mongo/cluster.rb +++ b/lib/mongo/cluster.rb @@ -186,6 +186,8 @@ def initialize(seeds, monitoring, options = Options::Redacted.new) recreate_topology(topology, opening_topology) end + possibly_warn_about_compatibility! + if load_balanced? # We are required by the specifications to produce certain SDAM events # when in load-balanced topology. @@ -1082,6 +1084,30 @@ def recreate_topology(new_topology_template, previous_topology) Monitoring::Event::TopologyChanged.new(previous_topology, @topology) ) end + + COSMOSDB_HOST_PATTERNS = %w[ .cosmos.azure.com ] + COSMOSDB_LOG_MESSAGE = 'You appear to be connected to a CosmosDB cluster. ' \ + 'For more information regarding feature compatibility and support please visit ' \ + 'https://www.mongodb.com/supportability/cosmosdb' + + DOCUMENTDB_HOST_PATTERNS = %w[ .docdb.amazonaws.com .docdb-elastic.amazonaws.com ] + DOCUMENTDB_LOG_MESSAGE = 'You appear to be connected to a DocumentDB cluster. ' \ + 'For more information regarding feature compatibility and support please visit ' \ + 'https://www.mongodb.com/supportability/documentdb' + + # Compares the server hosts with address suffixes of known services + # that provide limited MongoDB API compatibility, and warns about them. + def possibly_warn_about_compatibility! + if topology.server_hosts_match_any?(COSMOSDB_HOST_PATTERNS) + log_info COSMOSDB_LOG_MESSAGE + return + end + + if topology.server_hosts_match_any?(DOCUMENTDB_HOST_PATTERNS) + log_info DOCUMENTDB_LOG_MESSAGE + return + end + end end end diff --git a/lib/mongo/cluster/topology/base.rb b/lib/mongo/cluster/topology/base.rb index e92181e4fa..999d14bf75 100644 --- a/lib/mongo/cluster/topology/base.rb +++ b/lib/mongo/cluster/topology/base.rb @@ -211,6 +211,22 @@ def new_max_set_version(description) end end + # Compares each server address against the list of patterns. + # + # @param [ Array ] patterns the URL suffixes to compare + # each server against. + # + # @return [ true | false ] whether any of the addresses match any of + # the patterns or not. + # + # @api private + def server_hosts_match_any?(patterns) + server_descriptions.any? do |addr_spec, _desc| + addr, _port = addr_spec.split(/:/) + patterns.any? { |pattern| addr.end_with?(pattern) } + end + end + private # Validates and/or transforms options as necessary for the topology. diff --git a/spec/mongo/cluster_spec.rb b/spec/mongo/cluster_spec.rb index b93f529a0e..b87733f261 100644 --- a/spec/mongo/cluster_spec.rb +++ b/spec/mongo/cluster_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'spec_helper' +require 'support/recording_logger' # let these existing styles stand, rather than going in for a deep refactoring # of these specs. @@ -84,6 +85,41 @@ ) end end + + context 'when a non-genuine host is detected' do + before { described_class.new(host_names, monitoring, logger: logger, monitoring_io: false) } + + let(:logger) { RecordingLogger.new } + + shared_examples 'an action that logs' do + it 'writes a warning to the log' do + expect(logger.lines).to include(a_string_matching(expected_log_output)) + end + end + + context 'when CosmosDB is detected' do + let(:host_names) { %w[ xyz.cosmos.azure.com ] } + let(:expected_log_output) { %r{https://www.mongodb.com/supportability/cosmosdb} } + + it_behaves_like 'an action that logs' + end + + context 'when DocumentDB is detected' do + let(:expected_log_output) { %r{https://www.mongodb.com/supportability/documentdb} } + + context 'with docdb uri' do + let(:host_names) { [ 'xyz.docdb.amazonaws.com' ] } + + it_behaves_like 'an action that logs' + end + + context 'with docdb-elastic uri' do + let(:host_names) { [ 'xyz.docdb-elastic.amazonaws.com' ] } + + it_behaves_like 'an action that logs' + end + end + end end describe '#==' do diff --git a/spec/support/recording_logger.rb b/spec/support/recording_logger.rb new file mode 100644 index 0000000000..cfed9b0f28 --- /dev/null +++ b/spec/support/recording_logger.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +# rubocop:todo all + +require 'stringio' + +# A "Logger-alike" class, quacking like ::Logger, used for recording messages +# as they are written to the log +class RecordingLogger < Logger + def initialize(*args, **kwargs) + @buffer = StringIO.new + super(@buffer, *args, **kwargs) + end + + # Accesses the raw contents of the log + # + # @return [ String ] the raw contents of the log + def contents + @buffer.string + end + + # Returns the contents of the log as individual lines. + # + # @return [ Array ] the individual log lines + def lines + contents.split(/\n/) + end +end From 11dc883b81c518fd9e57cc83cac8137458421c06 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 17 Oct 2023 13:53:35 +0200 Subject: [PATCH 164/198] RUBY-3332 Fix tailable cursors (#2793) * RUBY-3332 Fix tailable cursors * Extract method * Fix code review remarks --- lib/mongo/collection/view.rb | 1 + lib/mongo/collection/view/iterable.rb | 15 ++++++++++ spec/integration/find_options_spec.rb | 41 +++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/mongo/collection/view.rb b/lib/mongo/collection/view.rb index 794e04e5c7..dcc0f89752 100644 --- a/lib/mongo/collection/view.rb +++ b/lib/mongo/collection/view.rb @@ -127,6 +127,7 @@ def hash # return in each response from MongoDB. # @option options [ Hash ] :collation The collation to use. # @option options [ String ] :comment Associate a comment with the query. + # @option options [ :tailable, :tailable_await ] :cursor_type The type of cursor to use. # @option options [ Hash ] :explain Execute an explain with the provided # explain options (known options are :verbose and :verbosity) rather # than a find. diff --git a/lib/mongo/collection/view/iterable.rb b/lib/mongo/collection/view/iterable.rb index 159719bcb1..83ec0e458b 100644 --- a/lib/mongo/collection/view/iterable.rb +++ b/lib/mongo/collection/view/iterable.rb @@ -186,6 +186,8 @@ def initial_query_op(session) collection.client.log_warn("The :oplog_replay option is deprecated and ignored by MongoDB 4.4 and later") end + maybe_set_tailable_options(spec) + if explained? spec[:explain] = options[:explain] Operation::Explain.new(spec) @@ -201,6 +203,19 @@ def send_initial_query(server, session = nil) def use_query_cache? QueryCache.enabled? && !collection.system_collection? end + + # Add tailable cusror options to the command specifiction if needed. + # + # @param [ Hash ] spec The command specification. + def maybe_set_tailable_options(spec) + case cursor_type + when :tailable + spec[:tailable] = true + when :tailable_await + spec[:tailable] = true + spec[:await_data] = true + end + end end end end diff --git a/spec/integration/find_options_spec.rb b/spec/integration/find_options_spec.rb index e5a822a3ea..519a164b70 100644 --- a/spec/integration/find_options_spec.rb +++ b/spec/integration/find_options_spec.rb @@ -13,10 +13,20 @@ [ SpecConfig.instance.addresses.first ] end + let(:client_options) do + {} + end + + let(:collection_options) do + {} + end + let(:client) do ClientRegistry.instance.new_local_client( seeds, - SpecConfig.instance.test_options.merge(client_options) + SpecConfig.instance.test_options + .merge(database: SpecConfig.instance.test_db) + .merge(client_options) ).tap do |client| client.subscribe(Mongo::Monitoring::COMMAND, subscriber) end @@ -30,8 +40,11 @@ subscriber.started_events.find { |cmd| cmd.command_name == 'find' } end + let(:should_create_collection) { true } + before do - ClientRegistry.instance.global_client('authorized')['find_options'].drop + client['find_options'].drop + collection.create if should_create_collection collection.insert_many([ { a: 1 }, { a: 2 }, { a: 3 } ]) end @@ -71,6 +84,8 @@ { 'locale' => 'de_AT' } end + let(:should_create_collection) { false } + it 'uses the collation defined on the collection' do collection.find({}, collation: collation).to_a expect(find_command.command['collation']).to eq(collation) @@ -187,4 +202,26 @@ end end end + + describe 'cursor type' do + let(:collection_options) do + { capped: true, size: 1000 } + end + + context 'when cursor type is :tailable' do + it 'sets the cursor type to tailable' do + collection.find({}, cursor_type: :tailable).first + expect(find_command.command['tailable']).to be true + expect(find_command.command['awaitData']).to be_falsey + end + end + + context 'when cursor type is :tailable_await' do + it 'sets the cursor type to tailable' do + collection.find({}, cursor_type: :tailable_await).first + expect(find_command.command['tailable']).to be true + expect(find_command.command['awaitData']).to be true + end + end + end end From 00d178e7ddc6f0d49b5556a9e44810dfd1e96bfb Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 20 Oct 2023 17:44:20 +0200 Subject: [PATCH 165/198] RUBY-3149 Test on AWS Lambda (#2800) --- .evergreen/atlas | 1 + .evergreen/config.yml | 90 +++++ .evergreen/config/common.yml.erb | 82 +++++ .evergreen/config/standard.yml.erb | 8 + .evergreen/run-deployed-lambda-aws-tests.sh | 117 +++++++ .evergreen/run-tests-deployed-lambda.sh | 16 + .gitignore | 3 + .mod/drivers-evergreen-tools | 2 +- .rubocop.yml | 1 + spec/faas/ruby-sam-app/.gitignore | 345 ++++++++++++++++++++ spec/faas/ruby-sam-app/Gemfile | 9 + spec/faas/ruby-sam-app/mongodb/Gemfile | 4 + spec/faas/ruby-sam-app/mongodb/app.rb | 149 +++++++++ spec/faas/ruby-sam-app/template.yaml | 48 +++ 14 files changed, 874 insertions(+), 1 deletion(-) create mode 120000 .evergreen/atlas create mode 100755 .evergreen/run-deployed-lambda-aws-tests.sh create mode 100755 .evergreen/run-tests-deployed-lambda.sh create mode 100644 spec/faas/ruby-sam-app/.gitignore create mode 100644 spec/faas/ruby-sam-app/Gemfile create mode 100644 spec/faas/ruby-sam-app/mongodb/Gemfile create mode 100644 spec/faas/ruby-sam-app/mongodb/app.rb create mode 100644 spec/faas/ruby-sam-app/template.yaml diff --git a/.evergreen/atlas b/.evergreen/atlas new file mode 120000 index 0000000000..ef871b9a28 --- /dev/null +++ b/.evergreen/atlas @@ -0,0 +1 @@ +../.mod/drivers-evergreen-tools/.evergreen/atlas \ No newline at end of file diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 76631a375a..e41d305254 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -482,6 +482,8 @@ task_groups: script: | ${PREPARE_SHELL} + echo "Setting up Atlas cluster" + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ @@ -492,6 +494,8 @@ task_groups: task_id="${task_id}" \ execution="${execution}" \ $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh + + echo "MONGODB_URI=${MONGODB_URI}" - command: expansions.update params: file: src/atlas-expansion.yml @@ -513,6 +517,52 @@ task_groups: tasks: - test-full-atlas-task + - name: test_aws_lambda_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: create expansions + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + + echo "Setting up Atlas cluster" + + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ + DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ + DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ + DRIVERS_ATLAS_LAMBDA_USER="${DRIVERS_ATLAS_LAMBDA_USER}" \ + DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" \ + LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + MONGODB_VERSION="7.0" \ + task_id="${task_id}" \ + execution="${execution}" \ + $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh + - command: expansions.update + params: + file: src/atlas-expansion.yml + teardown_group: + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ + DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ + DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ + LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + task_id="${task_id}" \ + execution="${execution}" \ + $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh + tasks: + - test-aws-lambda-deployed + - name: testgcpkms_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 # 30 minutes @@ -702,6 +752,38 @@ tasks: export AZUREKMS_VMNAME=${AZUREKMS_VMNAME} export AZUREKMS_PRIVATEKEYPATH="/tmp/testazurekms_private_key_file" AZUREKMS_CMD="TEST_FLE_AZURE_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_AZURE_TENANT_ID="${MONGO_RUBY_DRIVER_AZURE_TENANT_ID}" MONGO_RUBY_DRIVER_AZURE_CLIENT_ID="${MONGO_RUBY_DRIVER_AZURE_CLIENT_ID}" MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET="${MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET}" MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT="${MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT}" MONGO_RUBY_DRIVER_AZURE_KEY_VAULT_ENDPOINT="${testazurekms_keyvaultendpoint}" MONGO_RUBY_DRIVER_AZURE_KEY_NAME="${testazurekms_keyname}" ./.evergreen/run-tests-azure.sh" .evergreen/csfle/azurekms/run-command.sh + + - name: "test-aws-lambda-deployed" + commands: + - command: ec2.assume_role + params: + role_arn: ${LAMBDA_AWS_ROLE_ARN} + duration_seconds: 3600 + - command: shell.exec + type: test + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + export MONGODB_URI=${MONGODB_URI} + .evergreen/run-tests-deployed-lambda.sh + env: + TEST_LAMBDA_DIRECTORY: ${PROJECT_DIRECTORY}/spec/faas/ruby-sam-app + AWS_REGION: us-east-1 + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + DRIVERS_TOOLS: ${DRIVERS_TOOLS} + DRIVERS_ATLAS_PUBLIC_API_KEY: ${DRIVERS_ATLAS_PUBLIC_API_KEY} + DRIVERS_ATLAS_PRIVATE_API_KEY: ${DRIVERS_ATLAS_PRIVATE_API_KEY} + DRIVERS_ATLAS_LAMBDA_USER: ${DRIVERS_ATLAS_LAMBDA_USER} + DRIVERS_ATLAS_LAMBDA_PASSWORD: ${DRIVERS_ATLAS_LAMBDA_PASSWORD} + DRIVERS_ATLAS_GROUP_ID: ${DRIVERS_ATLAS_GROUP_ID} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN} + LAMBDA_STACK_NAME: "dbx-ruby-lambda" + RVM_RUBY: ruby-3.2 + MONGODB_URI: ${MONGODB_URI} axes: - id: preload @@ -1689,3 +1771,11 @@ buildvariants: display_name: "Atlas serverless ${ruby} single mongos" tasks: - name: test-serverless + + - matrix_name: "aws-lambda" + matrix_spec: + ruby: "ruby-3.2" + os: ubuntu2204 + display_name: "AWS Lambda" + tasks: + - name: test_aws_lambda_task_group diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index e78f08d846..90e8def128 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -479,6 +479,8 @@ task_groups: script: | ${PREPARE_SHELL} + echo "Setting up Atlas cluster" + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ @@ -489,6 +491,8 @@ task_groups: task_id="${task_id}" \ execution="${execution}" \ $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh + + echo "MONGODB_URI=${MONGODB_URI}" - command: expansions.update params: file: src/atlas-expansion.yml @@ -510,6 +514,52 @@ task_groups: tasks: - test-full-atlas-task + - name: test_aws_lambda_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: fetch source + - func: create expansions + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + + echo "Setting up Atlas cluster" + + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ + DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ + DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ + DRIVERS_ATLAS_LAMBDA_USER="${DRIVERS_ATLAS_LAMBDA_USER}" \ + DRIVERS_ATLAS_LAMBDA_PASSWORD="${DRIVERS_ATLAS_LAMBDA_PASSWORD}" \ + LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + MONGODB_VERSION="7.0" \ + task_id="${task_id}" \ + execution="${execution}" \ + $DRIVERS_TOOLS/.evergreen/atlas/setup-atlas-cluster.sh + - command: expansions.update + params: + file: src/atlas-expansion.yml + teardown_group: + - command: shell.exec + params: + shell: "bash" + working_dir: "src" + script: | + ${PREPARE_SHELL} + + DRIVERS_ATLAS_PUBLIC_API_KEY="${DRIVERS_ATLAS_PUBLIC_API_KEY}" \ + DRIVERS_ATLAS_PRIVATE_API_KEY="${DRIVERS_ATLAS_PRIVATE_API_KEY}" \ + DRIVERS_ATLAS_GROUP_ID="${DRIVERS_ATLAS_GROUP_ID}" \ + LAMBDA_STACK_NAME="dbx-ruby-lambda" \ + task_id="${task_id}" \ + execution="${execution}" \ + $DRIVERS_TOOLS/.evergreen/atlas/teardown-atlas-cluster.sh + tasks: + - test-aws-lambda-deployed + - name: testgcpkms_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 # 30 minutes @@ -699,3 +749,35 @@ tasks: export AZUREKMS_VMNAME=${AZUREKMS_VMNAME} export AZUREKMS_PRIVATEKEYPATH="/tmp/testazurekms_private_key_file" AZUREKMS_CMD="TEST_FLE_AZURE_AUTO=1 RVM_RUBY=ruby-3.1 FLE=helper TOPOLOGY=standalone MONGODB_VERSION=6.0 MONGO_RUBY_DRIVER_AZURE_TENANT_ID="${MONGO_RUBY_DRIVER_AZURE_TENANT_ID}" MONGO_RUBY_DRIVER_AZURE_CLIENT_ID="${MONGO_RUBY_DRIVER_AZURE_CLIENT_ID}" MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET="${MONGO_RUBY_DRIVER_AZURE_CLIENT_SECRET}" MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT="${MONGO_RUBY_DRIVER_AZURE_IDENTITY_PLATFORM_ENDPOINT}" MONGO_RUBY_DRIVER_AZURE_KEY_VAULT_ENDPOINT="${testazurekms_keyvaultendpoint}" MONGO_RUBY_DRIVER_AZURE_KEY_NAME="${testazurekms_keyname}" ./.evergreen/run-tests-azure.sh" .evergreen/csfle/azurekms/run-command.sh + + - name: "test-aws-lambda-deployed" + commands: + - command: ec2.assume_role + params: + role_arn: ${LAMBDA_AWS_ROLE_ARN} + duration_seconds: 3600 + - command: shell.exec + type: test + params: + working_dir: "src" + shell: "bash" + script: | + ${PREPARE_SHELL} + export MONGODB_URI=${MONGODB_URI} + .evergreen/run-tests-deployed-lambda.sh + env: + TEST_LAMBDA_DIRECTORY: ${PROJECT_DIRECTORY}/spec/faas/ruby-sam-app + AWS_REGION: us-east-1 + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + DRIVERS_TOOLS: ${DRIVERS_TOOLS} + DRIVERS_ATLAS_PUBLIC_API_KEY: ${DRIVERS_ATLAS_PUBLIC_API_KEY} + DRIVERS_ATLAS_PRIVATE_API_KEY: ${DRIVERS_ATLAS_PRIVATE_API_KEY} + DRIVERS_ATLAS_LAMBDA_USER: ${DRIVERS_ATLAS_LAMBDA_USER} + DRIVERS_ATLAS_LAMBDA_PASSWORD: ${DRIVERS_ATLAS_LAMBDA_PASSWORD} + DRIVERS_ATLAS_GROUP_ID: ${DRIVERS_ATLAS_GROUP_ID} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN} + LAMBDA_STACK_NAME: "dbx-ruby-lambda" + RVM_RUBY: ruby-3.2 + MONGODB_URI: ${MONGODB_URI} diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index 89cbc81ef1..aa7f68c59d 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -520,3 +520,11 @@ buildvariants: display_name: "Atlas serverless ${ruby} single mongos" tasks: - name: test-serverless + + - matrix_name: "aws-lambda" + matrix_spec: + ruby: <%= latest_ruby %> + os: ubuntu2204 + display_name: "AWS Lambda" + tasks: + - name: test_aws_lambda_task_group diff --git a/.evergreen/run-deployed-lambda-aws-tests.sh b/.evergreen/run-deployed-lambda-aws-tests.sh new file mode 100755 index 0000000000..32eebfbf2b --- /dev/null +++ b/.evergreen/run-deployed-lambda-aws-tests.sh @@ -0,0 +1,117 @@ +#!/bin/bash +set -o errexit # Exit the script with error if any of the commands fail + +# Explanation of required environment variables: +# +# TEST_LAMBDA_DIRECTORY: The root of the project's Lambda sam project. +# DRIVERS_ATLAS_PUBLIC_API_KEY: The public Atlas key for the drivers org. +# DRIVERS_ATLAS_PRIVATE_API_KEY: The private Atlas key for the drivers org. +# DRIVERS_ATLAS_LAMBDA_USER: The user for the lambda cluster. +# DRIVERS_ATLAS_LAMBDA_PASSWORD: The password for the user. +# DRIVERS_ATLAS_GROUP_ID: The id of the individual projects under the drivers org, per language. +# LAMBDA_STACK_NAME: The name of the stack on lambda "dbx--lambda" +# AWS_REGION: The region for the function - generally us-east-1 + +VARLIST=( +TEST_LAMBDA_DIRECTORY +DRIVERS_ATLAS_PUBLIC_API_KEY +DRIVERS_ATLAS_PRIVATE_API_KEY +DRIVERS_ATLAS_LAMBDA_USER +DRIVERS_ATLAS_LAMBDA_PASSWORD +DRIVERS_ATLAS_GROUP_ID +LAMBDA_STACK_NAME +AWS_REGION +) + +# Ensure that all variables required to run the test are set, otherwise throw +# an error. +for VARNAME in ${VARLIST[*]}; do +[[ -z "${!VARNAME}" ]] && echo "ERROR: $VARNAME not set" && exit 1; +done + +# Set up the common variables +. `dirname "$0"`/atlas/setup-variables.sh + +# Restarts the cluster's primary node. +restart_cluster_primary () +{ + echo "Testing Atlas primary restart..." + curl \ + --digest -u ${DRIVERS_ATLAS_PUBLIC_API_KEY}:${DRIVERS_ATLAS_PRIVATE_API_KEY} \ + -X POST \ + "${ATLAS_BASE_URL}/groups/${DRIVERS_ATLAS_GROUP_ID}/clusters/${FUNCTION_NAME}/restartPrimaries" +} + +# Deploys a lambda function to the set stack name. +deploy_lambda_function () +{ + echo "Deploying Lambda function..." + sam deploy \ + --stack-name "${FUNCTION_NAME}" \ + --capabilities CAPABILITY_IAM \ + --resolve-s3 \ + --parameter-overrides "MongoDbUri=${MONGODB_URI}" \ + --region ${AWS_REGION} +} + +# Get the ARN for the Lambda function we created and export it. +get_lambda_function_arn () +{ + echo "Getting Lambda function ARN..." + LAMBDA_FUNCTION_ARN=$(sam list stack-outputs \ + --stack-name ${FUNCTION_NAME} \ + --region ${AWS_REGION} \ + --output json | jq '.[] | select(.OutputKey == "MongoDBFunction") | .OutputValue' | tr -d '"' + ) + echo "Lambda function ARN: $LAMBDA_FUNCTION_ARN" + export LAMBDA_FUNCTION_ARN=$LAMBDA_FUNCTION_ARN +} + +delete_lambda_function () +{ + echo "Deleting Lambda Function..." + sam delete --stack-name ${FUNCTION_NAME} --no-prompts --region us-east-1 +} + +cleanup () +{ + delete_lambda_function +} + +trap cleanup EXIT SIGHUP + +cd "${TEST_LAMBDA_DIRECTORY}" + +sam build --use-container + +deploy_lambda_function + +get_lambda_function_arn + + +check_lambda_output () { + if grep -q FunctionError output.json + then + echo "Exiting due to FunctionError!" + exit 1 + fi + cat output.json | jq -r '.LogResult' | base64 --decode +} + +aws lambda invoke --function-name ${LAMBDA_FUNCTION_ARN} --log-type Tail lambda-invoke-standard.json > output.json +cat lambda-invoke-standard.json +check_lambda_output + +echo "Sleeping 1 minute to build up some streaming protocol heartbeats..." +sleep 60 +aws lambda invoke --function-name ${LAMBDA_FUNCTION_ARN} --log-type Tail lambda-invoke-frozen.json > output.json +cat lambda-invoke-frozen.json +check_lambda_output + +restart_cluster_primary + +echo "Sleeping 1 minute to build up some streaming protocol heartbeats..." +sleep 60 +aws lambda invoke --function-name ${LAMBDA_FUNCTION_ARN} --log-type Tail lambda-invoke-outage.json > output.json +cat lambda-invoke-outage.json +check_lambda_output diff --git a/.evergreen/run-tests-deployed-lambda.sh b/.evergreen/run-tests-deployed-lambda.sh new file mode 100755 index 0000000000..9b5d01d526 --- /dev/null +++ b/.evergreen/run-tests-deployed-lambda.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -ex + +. `dirname "$0"`/../spec/shared/shlib/distro.sh +. `dirname "$0"`/../spec/shared/shlib/set_env.sh +. `dirname "$0"`/functions.sh + +set_env_vars +set_env_python +set_env_ruby + +export MONGODB_URI=${MONGODB_URI} +export TEST_LAMBDA_DIRECTORY=`dirname "$0"`/../spec/faas/ruby-sam-app + +. `dirname "$0"`/run-deployed-lambda-aws-tests.sh diff --git a/.gitignore b/.gitignore index 24a54bc470..f1acca9738 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ profile/benchmarking/data secrets-export.sh secrets-expansion.yml atlas-expansion.yml +# AWS SAM-generated files +spec/faas/ruby-sam-app/.aws-sam +spec/faas/ruby-sam-app/events/event.json diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index 1f018c7a24..c3335b46e7 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit 1f018c7a248c4fcda6cb7a77043fd673755e0986 +Subproject commit c3335b46e7190beeab6b65eb9a76bd0e09f69903 diff --git a/.rubocop.yml b/.rubocop.yml index 788bd56daf..c21c088e8c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,6 +8,7 @@ AllCops: NewCops: enable Exclude: - 'spec/shared/**/*' + - 'spec/faas/**/*' - 'vendor/**/*' Bundler: diff --git a/spec/faas/ruby-sam-app/.gitignore b/spec/faas/ruby-sam-app/.gitignore new file mode 100644 index 0000000000..4bccb52c85 --- /dev/null +++ b/spec/faas/ruby-sam-app/.gitignore @@ -0,0 +1,345 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,pycharm,visualstudiocode,sam +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,linux,python,windows,pycharm,visualstudiocode,sam + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### SAM ### +# Ignore build directories for the AWS Serverless Application Model (SAM) +# Info: https://aws.amazon.com/serverless/sam/ +# Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html + +**/.aws-sam + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/osx,linux,python,windows,pycharm,visualstudiocode,sam diff --git a/spec/faas/ruby-sam-app/Gemfile b/spec/faas/ruby-sam-app/Gemfile new file mode 100644 index 0000000000..64004bea4c --- /dev/null +++ b/spec/faas/ruby-sam-app/Gemfile @@ -0,0 +1,9 @@ +source "https://rubygems.org" + +gem "httparty" +gem "mongo" + +group :test do + gem "test-unit" + gem "mocha" +end diff --git a/spec/faas/ruby-sam-app/mongodb/Gemfile b/spec/faas/ruby-sam-app/mongodb/Gemfile new file mode 100644 index 0000000000..8eee1f8e03 --- /dev/null +++ b/spec/faas/ruby-sam-app/mongodb/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem "httparty" +gem "mongo" diff --git a/spec/faas/ruby-sam-app/mongodb/app.rb b/spec/faas/ruby-sam-app/mongodb/app.rb new file mode 100644 index 0000000000..0eb7e9e46a --- /dev/null +++ b/spec/faas/ruby-sam-app/mongodb/app.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require 'mongo' +require 'json' + +class StatsAggregator + + def initialize + @open_connections = 0 + @heartbeats_count = 0 + @total_heartbeat_time = 0 + @commands_count = 0 + @total_command_time = 0 + end + + def add_command(duration) + @commands_count += 1 + @total_command_time += duration + end + + def add_heartbeat(duration) + @heartbeats_count += 1 + @total_heartbeat_time += duration + end + + def add_connection + @open_connections += 1 + end + + def remove_connection + @open_connections -= 1 + end + + def average_heartbeat_time + if @heartbeats_count == 0 + 0 + else + @total_heartbeat_time / @heartbeats_count + end + end + + def average_command_time + if @commands_count == 0 + 0 + else + @total_command_time / @commands_count + end + end + + def reset + @open_connections = 0 + @heartbeats_count = 0 + @total_heartbeat_time = 0 + @commands_count = 0 + @total_command_time = 0 + end + + def result + { + average_heartbeat_time: average_heartbeat_time, + average_command_time: average_command_time, + heartbeats_count: @heartbeats_count, + open_connections: @open_connections, + } + end +end + +class CommandMonitor + + def initialize(stats_aggregator) + @stats_aggregator = stats_aggregator + end + + def started(event); end + + def failed(event) + @stats_aggregator.add_command(event.duration) + end + + def succeeded(event) + @stats_aggregator.add_command(event.duration) + end +end + +class HeartbeatMonitor + + def initialize(stats_aggregator) + @stats_aggregator = stats_aggregator + end + + def started(event); end + + def succeeded(event) + @stats_aggregator.add_heartbeat(event.duration) + end + + def failed(event) + @stats_aggregator.add_heartbeat(event.duration) + end +end + +class PoolMonitor + + def initialize(stats_aggregator) + @stats_aggregator = stats_aggregator + end + + def published(event) + case event + when Mongo::Monitoring::Event::Cmap::ConnectionCreated + @stats_aggregator.add_connection + when Mongo::Monitoring::Event::Cmap::ConnectionClosed + @stats_aggregator.remove_connection + end + end +end + +$stats_aggregator = StatsAggregator.new + +command_monitor = CommandMonitor.new($stats_aggregator) +heartbeat_monitor = HeartbeatMonitor.new($stats_aggregator) +pool_monitor = PoolMonitor.new($stats_aggregator) + +sdam_proc = proc do |client| + client.subscribe(Mongo::Monitoring::COMMAND, command_monitor) + client.subscribe(Mongo::Monitoring::SERVER_HEARTBEAT, heartbeat_monitor) + client.subscribe(Mongo::Monitoring::CONNECTION_POOL, pool_monitor) +end + +puts 'Connecting' +$client = Mongo::Client.new(ENV['MONGODB_URI'], sdam_proc: sdam_proc) +# Populate the connection pool +$client.use('lambda_test').database.list_collections +puts 'Connected' + +def lambda_handler(event:, context:) + db = $client.use('lambda_test') + collection = db[:test_collection] + result = collection.insert_one({ name: 'test' }) + collection.delete_one({ _id: result.inserted_id }) + response = $stats_aggregator.result.to_json + $stats_aggregator.reset + puts "Response: #{response}" + + { + statusCode: 200, + body: response + } +end diff --git a/spec/faas/ruby-sam-app/template.yaml b/spec/faas/ruby-sam-app/template.yaml new file mode 100644 index 0000000000..c42df95e3b --- /dev/null +++ b/spec/faas/ruby-sam-app/template.yaml @@ -0,0 +1,48 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + Sample SAM Template for ruby-sam-app + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 30 + MemorySize: 128 + +Parameters: + MongoDbUri: + Type: String + Description: The MongoDB connection string. + +Resources: + MongoDBFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: mongodb/ + Environment: + Variables: + MONGODB_URI: !Ref MongoDbUri + Handler: app.lambda_handler + Runtime: ruby3.2 + Architectures: + - x86_64 + Events: + MongoDB: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /mongodb + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + MongoDBApi: + Description: "API Gateway endpoint URL for Prod stage for MongoDB function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/mongodb/" + MongoDBFunction: + Description: "MongoDB Lambda Function ARN" + Value: !GetAtt MongoDBFunction.Arn + MongoDBFunctionIamRole: + Description: "Implicit IAM Role created for MongoDB function" + Value: !GetAtt MongoDBFunctionRole.Arn From 29610650df18b59868da0facaa9446814e2a961c Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 31 Oct 2023 16:02:02 +0100 Subject: [PATCH 166/198] Fix serverless tests (#2802) --- .evergreen/run-tests-serverless.sh | 3 ++- spec/spec_tests/data/crud_unified/find-test-all-options.yml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.evergreen/run-tests-serverless.sh b/.evergreen/run-tests-serverless.sh index 32bd9ea110..cd71e41d6e 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -75,7 +75,8 @@ wait_for_kms_server 5698 echo "Waiting for mock KMS servers to start... done." # Obtain temporary AWS credentials -. ./set-temp-creds.sh +pip3 install boto3 +PYTHON=python3 . ./set-temp-creds.sh cd - echo "Running specs" diff --git a/spec/spec_tests/data/crud_unified/find-test-all-options.yml b/spec/spec_tests/data/crud_unified/find-test-all-options.yml index 24be5ee08d..0f456b9cdf 100644 --- a/spec/spec_tests/data/crud_unified/find-test-all-options.yml +++ b/spec/spec_tests/data/crud_unified/find-test-all-options.yml @@ -1,6 +1,9 @@ description: "find options" schemaVersion: "1.0" +runOnRequirements: + - serverless: 'forbid' + createEntities: - client: From 511e324f02d0cdc0971498edf124c563b9658fdb Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Thu, 2 Nov 2023 17:28:25 +0100 Subject: [PATCH 167/198] RUBY-3333 Update AWS auth tests (#2803) --- .evergreen/config.yml | 9 ++++++--- .evergreen/config/axes.yml.erb | 5 ++++- .evergreen/config/common.yml.erb | 2 +- .evergreen/config/standard.yml.erb | 2 +- .evergreen/provision-local | 4 ++-- .evergreen/run-tests-aws-auth.sh | 5 ++++- spec/shared | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index e41d305254..06340d9a5f 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -179,7 +179,7 @@ functions: IAM_AUTH_ECS_SECURITY_GROUP="${iam_auth_ecs_security_group}" IAM_AUTH_ECS_SUBNET_A="${iam_auth_ecs_subnet_a}" IAM_AUTH_ECS_SUBNET_B="${iam_auth_ecs_subnet_b}" - IAM_AUTH_ECS_TASK_DEFINITION="${iam_auth_ecs_task_definition}" + IAM_AUTH_ECS_TASK_DEFINITION="${iam_auth_ecs_task_definition_ubuntu2004}" IAM_WEB_IDENTITY_ISSUER="${iam_web_identity_issuer}" IAM_WEB_IDENTITY_JWKS_URI="${iam_web_identity_jwks_uri}" @@ -972,7 +972,7 @@ axes: display_name: jruby-9.4 variables: RVM_RUBY: "jruby-9.4" - + - id: "os" display_name: OS values: @@ -982,6 +982,9 @@ axes: - id: ubuntu2204 display_name: "Ubuntu 22.04" run_on: ubuntu2204-small + - id: ubuntu2004 + display_name: "Ubuntu 20.04" + run_on: ubuntu2004-small - id: rhel8 display_name: "RHEL 8" run_on: rhel80-small @@ -1530,7 +1533,7 @@ buildvariants: # (depending on server version and what's available), we can bump this to # the latest stable db version. mongodb-version: "5.3" - os: rhel8 + os: ubuntu2004 display_name: "AWS ${auth-and-ssl} ${mongodb-version} ${ruby}" tasks: - name: "test-aws-auth" diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index e6f101e8bd..882c38b730 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -186,7 +186,7 @@ axes: display_name: jruby-9.4 variables: RVM_RUBY: "jruby-9.4" - + - id: "os" display_name: OS values: @@ -196,6 +196,9 @@ axes: - id: ubuntu2204 display_name: "Ubuntu 22.04" run_on: ubuntu2204-small + - id: ubuntu2004 + display_name: "Ubuntu 20.04" + run_on: ubuntu2004-small - id: rhel8 display_name: "RHEL 8" run_on: rhel80-small diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 90e8def128..67d72dc942 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -176,7 +176,7 @@ functions: IAM_AUTH_ECS_SECURITY_GROUP="${iam_auth_ecs_security_group}" IAM_AUTH_ECS_SUBNET_A="${iam_auth_ecs_subnet_a}" IAM_AUTH_ECS_SUBNET_B="${iam_auth_ecs_subnet_b}" - IAM_AUTH_ECS_TASK_DEFINITION="${iam_auth_ecs_task_definition}" + IAM_AUTH_ECS_TASK_DEFINITION="${iam_auth_ecs_task_definition_ubuntu2004}" IAM_WEB_IDENTITY_ISSUER="${iam_web_identity_issuer}" IAM_WEB_IDENTITY_JWKS_URI="${iam_web_identity_jwks_uri}" diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index aa7f68c59d..dbf9c1788c 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -376,7 +376,7 @@ buildvariants: # (depending on server version and what's available), we can bump this to # the latest stable db version. mongodb-version: <%= latest_5x_mdb %> - os: rhel8 + os: ubuntu2004 display_name: "AWS ${auth-and-ssl} ${mongodb-version} ${ruby}" tasks: - name: "test-aws-auth" diff --git a/.evergreen/provision-local b/.evergreen/provision-local index 3ad2654b4b..1ce14d240c 100755 --- a/.evergreen/provision-local +++ b/.evergreen/provision-local @@ -27,5 +27,5 @@ sudo env DEBIAN_FRONTEND=noninteractive \ # Need binutils for `strings` utility per # https://aws.amazon.com/premiumsupport/knowledge-center/ecs-iam-task-roles-config-errors/ sudo env DEBIAN_FRONTEND=noninteractive \ - apt-get install -y libsnmp30 libyaml-0-2 gcc make git lsb-release \ - krb5-user bzip2 libgmp-dev python-pip python2.7-dev binutils + apt-get install -y libsnmp35 libyaml-0-2 gcc make git lsb-release \ + krb5-user bzip2 libgmp-dev python3-pip python2.7-dev binutils diff --git a/.evergreen/run-tests-aws-auth.sh b/.evergreen/run-tests-aws-auth.sh index 1813400700..c9649e4a64 100755 --- a/.evergreen/run-tests-aws-auth.sh +++ b/.evergreen/run-tests-aws-auth.sh @@ -97,9 +97,11 @@ case "$AUTH" in aws-web-identity) cd `dirname "$0"`/auth_aws - . ./activate_venv.sh + echo "Activating virtual environment 'authawsvenv'..." + . ./activate-authawsvenv.sh export AWS_ACCESS_KEY_ID="`get_var IAM_AUTH_EC2_INSTANCE_ACCOUNT`" export AWS_SECRET_ACCESS_KEY="`get_var IAM_AUTH_EC2_INSTANCE_SECRET_ACCESS_KEY`" + echo "Unassigning instance profile..." python -u lib/aws_unassign_instance_profile.py unset AWS_ACCESS_KEY_ID unset AWS_SECRET_ACCESS_KEY @@ -113,6 +115,7 @@ case "$AUTH" in unset IDP_JWKS_URI unset IDP_RSA_KEY + deactivate cd - export MONGO_RUBY_DRIVER_AWS_AUTH_ACCESS_KEY_ID="`get_var IAM_AUTH_EC2_INSTANCE_ACCOUNT`" export MONGO_RUBY_DRIVER_AWS_AUTH_SECRET_ACCESS_KEY="`get_var IAM_AUTH_EC2_INSTANCE_SECRET_ACCESS_KEY`" diff --git a/spec/shared b/spec/shared index ce1f8945bd..bd968a969a 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit ce1f8945bd61e614ca4cc7a24d95d16071c46a59 +Subproject commit bd968a969aed7ae4d579855f2ce5b3e3201444a4 From cc799913ea6a89ac030095214dd80aafba32b4a0 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 15 Nov 2023 08:18:29 -0700 Subject: [PATCH 168/198] add docs for the Atlas search index management API (#2806) --- docs/reference/schema-operations.txt | 1 + docs/reference/search-indexes.txt | 129 +++++++++++++++++++++++++++ docs/release-notes.txt | 4 +- 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 docs/reference/search-indexes.txt diff --git a/docs/reference/schema-operations.txt b/docs/reference/schema-operations.txt index 6d3617302a..8d67d569fb 100644 --- a/docs/reference/schema-operations.txt +++ b/docs/reference/schema-operations.txt @@ -15,4 +15,5 @@ including managing databases, collections, indexes and users. /reference/database-tasks /reference/collection-tasks /reference/indexing + /reference/search-indexes /reference/collations diff --git a/docs/reference/search-indexes.txt b/docs/reference/search-indexes.txt new file mode 100644 index 0000000000..91f99670f7 --- /dev/null +++ b/docs/reference/search-indexes.txt @@ -0,0 +1,129 @@ +******************** +Atlas Search Indexes +******************** + +.. default-domain:: mongodb + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +If you are using a database hosted by MongoDB Atlas, the driver provides the +ability to create, drop and view `Atlas search indexes `_ +on a collection through the ``search_indexes`` attribute: + +.. code-block:: ruby + + client = Mongo::Client.new(your_atlas_uri, database: 'music') + client[:bands].search_indexes + # => # ...> + + +Creating Search Indexes +======================= + +Search indexes can be created one at a time, or several can be created in +parallel in a single operation. + +To create a single index, use ``search_indexes#create_one``, passing the index +definition as the first argument, and an optional name for the index as the +second argument. + +.. code-block:: ruby + + client[:bands].search_indexes.create_one({ dynamic: true }) + + client[:bands].search_indexes.create_one( + { + dynamic: false, + fields: { + name: { type: 'string', analyzer: 'lucene.simple' } + } + }, + 'band-name-index' + ) + +To create multiple indexes, use ``search_indexes#create_many`` which accepts +an array of index specifications. Unlike ``create_one``, each index +specification is a hash with at least a ``definition`` key, which +defines the index. Each has may also specify a ``name`` key, to name +the index. + +.. code-block:: ruby + + client[:bands].search_indexes.create_many([ + { definition: { dynamic: true } }, + { name: 'band-name-index, + definition: { + dynamic: false, + fields: { + name: { type: 'string', analyzer: 'lucene.simple' } + } + } + }, + ]) + +Note that whether you call ``create_one`` or ``create_many``, the +method will return immediately, before the indexes are created. The +indexes are then created in the background, asynchronously. + + +Update Search Indexes +===================== + +You can programmatically update an Atlas search index. For example, you +might do this to change the analyzer used, or to provide an explicit field +mapping, instead of a dynamic one. To do this, use the ``search_indexes#update_one`` +method: + +.. code-block:: ruby + + client[:bands].search_indexes.update_one(new_definition, id: index_id) + + client[:bands].search_indexes.update_one(new_definition, name: index_name) + +Indexes may be identified by either id, or name, but you must specify one +or the other. The new index definition must be a complete definition--it will +take precedence as specified over the existing definition. + +To get the id or name of an index that you wish to update, you can +`list the search indexes <#listing-search-indexes>`_. + + +Dropping Search Indexes +======================= + +To drop Atlas search indexes, call ``search_indexes#drop_one`` and +provide either the ``id`` or the ``name`` of the index you wish to +drop. + +.. code-block:: ruby + + client[:bands].search_indexes.drop_one(id: index_id) + + client[:bands].search_indexes.drop_one(name: index_name) + +In either case, the method will return immediately and the index will +be dropped in the background, asynchronously. + +To get the id or name of an index that you wish to drop, you can +`list the search indexes <#listing-search-indexes>`_. + + +Listing Search Indexes +====================== + +To list the available search indexes, iterate over the +``search_indexes`` object: + +.. code-block:: ruby + + client[:bands].search_indexes.each do |index_spec| + p index_spec['id'] + p index_spec['name'] + p index_spec['status'] + p index_spec['queryable'] + p index_spec['latestDefinition'] + end diff --git a/docs/release-notes.txt b/docs/release-notes.txt index f8eebf104a..d69063a17e 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -42,10 +42,12 @@ This release includes the following new features: when Azure Key Vault is used for client side encryption. - `Queryable Encryption `_ support is extended. - Added support for Queryable Encryption Range Indexes. -- A `crypt_shared `_ +- A `crypt_shared `_ library can be now used instead of ``mongocryptd``. - Added support for AWS IAM Roles for service accounts, EKS in particular. - AWS Credentials are now cached when possible. +- Added support for creating and managing `Atlas search indexes `_ via the + Ruby driver. .. _release-notes-2.18: From 0002cf81efd49fa6eec3dc61467aa3e710a49577 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 21 Nov 2023 08:18:49 -0700 Subject: [PATCH 169/198] RUBY-3347 add additional serverless project to tests (#2810) --- .evergreen/config.yml | 22 ++++++++++++++++++++-- .evergreen/config/axes.yml.erb | 10 ++++++++++ .evergreen/config/common.yml.erb | 9 ++++++++- .evergreen/config/standard.yml.erb | 3 ++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 06340d9a5f..934829aae4 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -123,7 +123,14 @@ functions: export ATLAS_SERVERLESS_URI="${atlas_serverless_uri}" export ATLAS_SERVERLESS_LB_URI="${atlas_serverless_lb_uri}" export RVM_RUBY="${RVM_RUBY}" - export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" + + if [[ -n "${USE_PROXY_SERVERLESS}" ]]; + then + export SERVERLESS_DRIVERS_GROUP="${PROXY_SERVERLESS_DRIVERS_GROUP}" + else + export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" + fi + export SERVERLESS_API_PUBLIC_KEY="${SERVERLESS_API_PUBLIC_KEY}" export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" @@ -1197,6 +1204,16 @@ axes: - id: no display_name: No + - id: serverless-project + display_name: serverless project + values: + - id: original + display_name: Original + - id: proxy + display_name: Proxy + variables: + USE_PROXY_SERVERLESS: 1 + buildvariants: - matrix_name: "auth/ssl" matrix_spec: @@ -1771,7 +1788,8 @@ buildvariants: ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] fle: path os: rhel8 - display_name: "Atlas serverless ${ruby} single mongos" + serverless-project: [ original, proxy ] + display_name: "Atlas serverless ${ruby} ${serverless-project}" tasks: - name: test-serverless diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index 882c38b730..074cba7a06 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -405,3 +405,13 @@ axes: API_VERSION_REQUIRED: 1 - id: no display_name: No + + - id: serverless-project + display_name: serverless project + values: + - id: original + display_name: Original + - id: proxy + display_name: Proxy + variables: + USE_PROXY_SERVERLESS: 1 diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 67d72dc942..54eee122a1 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -120,7 +120,14 @@ functions: export ATLAS_SERVERLESS_URI="${atlas_serverless_uri}" export ATLAS_SERVERLESS_LB_URI="${atlas_serverless_lb_uri}" export RVM_RUBY="${RVM_RUBY}" - export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" + + if [[ -n "${USE_PROXY_SERVERLESS}" ]]; + then + export SERVERLESS_DRIVERS_GROUP="${PROXY_SERVERLESS_DRIVERS_GROUP}" + else + export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" + fi + export SERVERLESS_API_PUBLIC_KEY="${SERVERLESS_API_PUBLIC_KEY}" export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index dbf9c1788c..1964946d4c 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -517,7 +517,8 @@ buildvariants: ruby: <%= supported_mri_rubies %> fle: path os: rhel8 - display_name: "Atlas serverless ${ruby} single mongos" + serverless-project: [ original, proxy ] + display_name: "Atlas serverless ${ruby} ${serverless-project}" tasks: - name: test-serverless From 308326dac07bcccd8a9816a92ccbeea599c0b752 Mon Sep 17 00:00:00 2001 From: Jordan Smith <45415425+jordan-smith721@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:37:56 -0800 Subject: [PATCH 170/198] update API link (#2812) --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index 0493d6c63f..44e4e33fc8 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -61,7 +61,7 @@ For tutorials on Mongoid, see the `Mongoid Manual + API release-notes reference/additional-resources contribute From f30f563066ec9779adc43fd6a525e22241c85270 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Tue, 28 Nov 2023 12:41:19 -0500 Subject: [PATCH 171/198] Fix broken link in Mongo::Monitoring::Event::Secure (#2775) --- lib/mongo/monitoring/event/secure.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mongo/monitoring/event/secure.rb b/lib/mongo/monitoring/event/secure.rb index 79abfd5a1b..0ad628e518 100644 --- a/lib/mongo/monitoring/event/secure.rb +++ b/lib/mongo/monitoring/event/secure.rb @@ -58,7 +58,7 @@ def sensitive?(command_name:, document:) # According to Command Monitoring spec,for hello/legacy hello commands # when speculativeAuthenticate is present, their commands AND replies # MUST be redacted from the events. - # See https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst#security + # See https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.rst#security true else false From e84afdef034234efc592b3a376dc1a5da4c457c4 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 5 Dec 2023 17:26:48 +0100 Subject: [PATCH 172/198] RUBY-3341 Document error handling in transactions (#2809) Co-authored-by: Alex Bevilacqua --- docs/reference/transactions.txt | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/reference/transactions.txt b/docs/reference/transactions.txt index a4e8571320..722038ca8f 100644 --- a/docs/reference/transactions.txt +++ b/docs/reference/transactions.txt @@ -76,6 +76,31 @@ which are read concern, write concern and read preference: collection.insert_one({hello: 'world'}, session: session) end +Handling Errors Within the ``with_transaction`` Block +----------------------------------------------------- + +If a command inside the ``with_transaction`` block fails, it may cause +the transaction on the server to be aborted. This situation is normally handled +transparently by the driver. However, if the application catches such an error +and does not re-raise it, the driver will not be able to determine whether +the transaction was aborted or not. The driver will then retry the block +indefinitely. + +To avoid this situation, the application must not silently handle errors within +``with_transaction`` block. If the application needs to handle errors within +the block, it must re-raise the errors. + +.. code-block:: ruby + + session.with_transaction do + collection.insert_one({hello: 'world'}, session: session) + rescue Mongo::Error::OperationFailure => e + # Do something in response to the error + raise e + end + +If the applications needs to handle errors in a custom way, it should use +the low level API instead. Low Level API ============= @@ -145,6 +170,14 @@ session if one is in progress: # ok c2.database.drop +Handling Errors +--------------- + +If a command inside the transaction fails, the transaction may be aborted +on the server. Errors that abort transactions do not have +``TransientTransactionError`` in their error labels. An attempt to commit such a +transaction will be rejected with ``NoSuchTransaction`` error. + Retrying Commits ================ From d75a4229384c4c96556ba5adc063d626f57eca22 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 9 Jan 2024 08:09:40 -0700 Subject: [PATCH 173/198] RUBY-3365 remove spec (#2820) --- .../cmap/pool-clear-interrupt-immediately.yml | 49 ------------------- 1 file changed, 49 deletions(-) delete mode 100644 spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml diff --git a/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml b/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml deleted file mode 100644 index afbb880df0..0000000000 --- a/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml +++ /dev/null @@ -1,49 +0,0 @@ -version: 1 -style: unit -description: Connections MUST be interrupted as soon as possible (interruptInUseConnections=true) -# Remove the topology runOn requirement when cmap specs are adjusted for lbs -runOn: - - topology: [ "single", "replicaset", "sharded" ] -poolOptions: - # ensure it's not involved by default - backgroundThreadIntervalMS: 10000 -operations: - - name: ready - - name: checkOut - - name: checkOut - label: conn - - name: clear - interruptInUseConnections: true - - name: waitForEvent - event: ConnectionPoolCleared - count: 1 - timeout: 1000 - - name: waitForEvent - event: ConnectionClosed - count: 2 - timeout: 1000 - - name: close -events: - - type: ConnectionCheckedOut - connectionId: 1 - address: 42 - - type: ConnectionCheckedOut - connectionId: 2 - address: 42 - - type: ConnectionPoolCleared - interruptInUseConnections: true - - type: ConnectionClosed - reason: stale - address: 42 - - type: ConnectionClosed - reason: stale - address: 42 - - type: ConnectionPoolClosed - address: 42 -ignore: - - ConnectionCreated - - ConnectionPoolReady - - ConnectionReady - - ConnectionCheckOutStarted - - ConnectionPoolCreated - - ConnectionCheckedIn From f18c4092c5637dfc27ee284e4b6a199ff23b885e Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 9 Jan 2024 08:10:51 -0700 Subject: [PATCH 174/198] RUBY-3362 sync specs to fix failing tests on server version 7.3 (#2819) --- .../data/index_management/createSearchIndex.yml | 8 +++++--- .../data/index_management/createSearchIndexes.yml | 11 +++++++---- .../data/index_management/dropSearchIndex.yml | 3 ++- .../data/index_management/listSearchIndexes.yml | 11 +++++++---- .../data/index_management/updateSearchIndex.yml | 3 ++- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/spec/spec_tests/data/index_management/createSearchIndex.yml b/spec/spec_tests/data/index_management/createSearchIndex.yml index 6aa56f3bc4..2e3cf50f8d 100644 --- a/spec/spec_tests/data/index_management/createSearchIndex.yml +++ b/spec/spec_tests/data/index_management/createSearchIndex.yml @@ -30,8 +30,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: @@ -50,8 +51,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: @@ -59,4 +61,4 @@ tests: command: createSearchIndexes: *collection0 indexes: [ { definition: *definition, name: 'test index' } ] - $db: *database0 \ No newline at end of file + $db: *database0 diff --git a/spec/spec_tests/data/index_management/createSearchIndexes.yml b/spec/spec_tests/data/index_management/createSearchIndexes.yml index 54a6e84ccb..db8f02e551 100644 --- a/spec/spec_tests/data/index_management/createSearchIndexes.yml +++ b/spec/spec_tests/data/index_management/createSearchIndexes.yml @@ -30,8 +30,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: @@ -51,8 +52,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: @@ -71,8 +73,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: @@ -80,4 +83,4 @@ tests: command: createSearchIndexes: *collection0 indexes: [ { definition: *definition, name: 'test index' } ] - $db: *database0 \ No newline at end of file + $db: *database0 diff --git a/spec/spec_tests/data/index_management/dropSearchIndex.yml b/spec/spec_tests/data/index_management/dropSearchIndex.yml index e384cf26c5..8a8e829454 100644 --- a/spec/spec_tests/data/index_management/dropSearchIndex.yml +++ b/spec/spec_tests/data/index_management/dropSearchIndex.yml @@ -30,8 +30,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: diff --git a/spec/spec_tests/data/index_management/listSearchIndexes.yml b/spec/spec_tests/data/index_management/listSearchIndexes.yml index a50becdf1d..f05a368585 100644 --- a/spec/spec_tests/data/index_management/listSearchIndexes.yml +++ b/spec/spec_tests/data/index_management/listSearchIndexes.yml @@ -28,8 +28,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: @@ -48,8 +49,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: @@ -71,8 +73,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: @@ -82,4 +85,4 @@ tests: cursor: { batchSize: 10 } pipeline: - $listSearchIndexes: { name: *indexName } - $db: *database0 \ No newline at end of file + $db: *database0 diff --git a/spec/spec_tests/data/index_management/updateSearchIndex.yml b/spec/spec_tests/data/index_management/updateSearchIndex.yml index bb18ab512e..2c56e75ef6 100644 --- a/spec/spec_tests/data/index_management/updateSearchIndex.yml +++ b/spec/spec_tests/data/index_management/updateSearchIndex.yml @@ -31,8 +31,9 @@ tests: expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. isError: true - errorContains: Search index commands are only supported with Atlas + errorContains: Atlas expectEvents: - client: *client0 events: From e06e03a78a2da62e4d8d571dad2d294bd0108e42 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 9 Jan 2024 08:12:06 -0700 Subject: [PATCH 175/198] RUBY-3361 resync specs (#2818) --- .../unified/bulkWrite-serverErrors.yml | 9 +++------ .../unified/insertOne-serverErrors.yml | 9 +++------ .../data/unified/valid-pass/poc-crud.yml | 2 +- .../unified/valid-pass/poc-retryable-writes.yml | 14 +++++++------- .../data/unified/valid-pass/poc-sessions.yml | 7 +++---- .../valid-pass/poc-transactions-convenient-api.yml | 2 +- .../poc-transactions-mongos-pin-auto.yml | 2 +- .../data/unified/valid-pass/poc-transactions.yml | 6 +++--- 8 files changed, 22 insertions(+), 29 deletions(-) diff --git a/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml b/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml index dc664ab76b..7d1375793e 100644 --- a/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml +++ b/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml @@ -3,8 +3,10 @@ description: "retryable-writes bulkWrite serverErrors" schemaVersion: "1.0" runOnRequirements: - - minServerVersion: "3.6" + - minServerVersion: "4.0" topologies: [ replicaset ] + - minServerVersion: "4.1.7" + topologies: [ sharded ] createEntities: - client: @@ -29,11 +31,6 @@ initialData: tests: - description: "BulkWrite succeeds after retryable writeConcernError in first batch" - runOnRequirements: - - minServerVersion: "4.0" - topologies: [ replicaset ] - - minServerVersion: "4.1.7" - topologies: [ sharded ] operations: - name: failPoint object: testRunner diff --git a/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml b/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml index 689328818b..231569fb0d 100644 --- a/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml +++ b/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml @@ -3,8 +3,10 @@ description: "retryable-writes insertOne serverErrors" schemaVersion: "1.0" runOnRequirements: - - minServerVersion: "3.6" + - minServerVersion: "4.0" topologies: [ replicaset ] + - minServerVersion: "4.1.7" + topologies: [ sharded ] createEntities: - client: @@ -29,11 +31,6 @@ initialData: tests: - description: "InsertOne succeeds after retryable writeConcernError" - runOnRequirements: - - minServerVersion: "4.0" - topologies: [ replicaset ] - - minServerVersion: "4.1.7" - topologies: [ sharded ] operations: - name: failPoint object: testRunner diff --git a/spec/spec_tests/data/unified/valid-pass/poc-crud.yml b/spec/spec_tests/data/unified/valid-pass/poc-crud.yml index b7d05d75af..5748c0779f 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-crud.yml @@ -143,7 +143,7 @@ tests: - description: "readConcern majority with out stage" runOnRequirements: - minServerVersion: "4.1.0" - topologies: [ replicaset, sharded-replicaset ] + topologies: [ replicaset, sharded ] serverless: "forbid" operations: - name: aggregate diff --git a/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml b/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml index fa882e2836..258043764a 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml @@ -2,10 +2,6 @@ description: "poc-retryable-writes" schemaVersion: "1.0" -runOnRequirements: - - minServerVersion: "3.6" - topologies: [ replicaset ] - createEntities: - client: id: &client0 client0 @@ -42,6 +38,9 @@ initialData: tests: - description: "FindOneAndUpdate is committed on first attempt" + runOnRequirements: &onPrimaryTransactionalWrite_requirements + - minServerVersion: "3.6" + topologies: [ replicaset ] operations: - name: failPoint object: testRunner @@ -65,6 +64,7 @@ tests: - { _id: 2, x: 22 } - description: "FindOneAndUpdate is not committed on first attempt" + runOnRequirements: *onPrimaryTransactionalWrite_requirements operations: - name: failPoint object: testRunner @@ -89,6 +89,7 @@ tests: - { _id: 2, x: 22 } - description: "FindOneAndUpdate is never committed" + runOnRequirements: *onPrimaryTransactionalWrite_requirements operations: - name: failPoint object: testRunner @@ -117,9 +118,7 @@ tests: - minServerVersion: "4.0" topologies: [ replicaset ] - minServerVersion: "4.1.7" - # Original test uses "sharded", but retryable writes requires a sharded - # cluster backed by replica sets - topologies: [ sharded-replicaset ] + topologies: [ sharded ] operations: - name: failPoint object: testRunner @@ -192,6 +191,7 @@ tests: mode: { times: 2 } data: failCommands: [ insert ] + errorLabels: [ RetryableWriteError ] writeConcernError: code: 91 # ShutdownInProgress errmsg: "Replication is being shut down" diff --git a/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml b/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml index e770f7fc6d..a93096a222 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml @@ -125,12 +125,11 @@ tests: - description: "Dirty explicit session is discarded" skipReason: RUBY-1813 # Original test specified retryWrites=true, but that is now the default. - # Retryable writes will require a sharded-replicaset, though. runOnRequirements: - minServerVersion: "4.0" topologies: [ replicaset ] - minServerVersion: "4.1.8" - topologies: [ sharded-replicaset ] + topologies: [ sharded ] operations: - name: failPoint object: testRunner @@ -181,7 +180,7 @@ tests: - commandStartedEvent: &insert_attempt command: insert: *collection0Name - documents: + documents: - { _id: 2 } ordered: true lsid: { $$sessionLsid: *session0 } @@ -192,7 +191,7 @@ tests: - commandStartedEvent: command: insert: *collection0Name - documents: + documents: - { _id: 3 } ordered: true lsid: { $$sessionLsid: *session0 } diff --git a/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml b/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml index 4f981d15dd..94fadda0aa 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml @@ -6,7 +6,7 @@ runOnRequirements: - minServerVersion: "4.0" topologies: [ replicaset ] - minServerVersion: "4.1.8" - topologies: [ sharded-replicaset ] + topologies: [ sharded ] createEntities: - client: diff --git a/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml b/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml index 47db7c3188..33cd2a2521 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml @@ -4,7 +4,7 @@ schemaVersion: "1.0" runOnRequirements: - minServerVersion: "4.1.8" - topologies: [ sharded-replicaset ] + topologies: [ sharded ] createEntities: - client: diff --git a/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml b/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml index 0a66b9bd7f..8a12c8b39a 100644 --- a/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +++ b/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml @@ -6,7 +6,7 @@ runOnRequirements: - minServerVersion: "4.0" topologies: [ replicaset ] - minServerVersion: "4.1.8" - topologies: [ sharded-replicaset ] + topologies: [ sharded ] createEntities: - client: @@ -51,7 +51,7 @@ tests: - description: "explicitly create collection using create command" runOnRequirements: - minServerVersion: "4.3.4" - topologies: [ replicaset, sharded-replicaset ] + topologies: [ replicaset, sharded ] operations: - name: dropCollection object: *database0 @@ -109,7 +109,7 @@ tests: - description: "create index on a non-existing collection" runOnRequirements: - minServerVersion: "4.3.4" - topologies: [ replicaset, sharded-replicaset ] + topologies: [ replicaset, sharded ] operations: - name: dropCollection object: *database0 From 69b0ec14d3a21c1650db1dacc242cd62ab6189d6 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Fri, 12 Jan 2024 09:17:53 -0700 Subject: [PATCH 176/198] RUBY-3298 Add container info to handshake metadata (#2821) * RUBY-3298 include container info in handshake metadata * make sure it works even when faas info is not present * look for .dockerenv, not Dockerfile * add a test to check the value of DOCKERENV_PATH * linter appeasement --- .rubocop.yml | 3 + lib/mongo/server/app_metadata.rb | 9 +- lib/mongo/server/app_metadata/environment.rb | 73 ++++++++-- .../server/app_metadata/environment_spec.rb | 135 ++++++++++++++++++ spec/mongo/server/app_metadata_spec.rb | 14 +- 5 files changed, 219 insertions(+), 15 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index c21c088e8c..fa7d04f8c6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -51,6 +51,9 @@ Layout/SpaceInsideArrayLiteralBrackets: Layout/SpaceInsidePercentLiteralDelimiters: Enabled: false +Metrics/ClassLength: + Max: 200 + Metrics/ModuleLength: Enabled: false diff --git a/lib/mongo/server/app_metadata.rb b/lib/mongo/server/app_metadata.rb index 2d982c4f4b..81e595d9aa 100644 --- a/lib/mongo/server/app_metadata.rb +++ b/lib/mongo/server/app_metadata.rb @@ -187,13 +187,14 @@ def os_doc } end - # Returns the environment doc describing the current FaaS environment. + # Returns the environment doc describing the current execution + # environment. # - # @return [ Hash | nil ] the environment doc (or nil if not in a FaaS - # environment). + # @return [ Hash | nil ] the environment doc (or nil if no relevant + # environment info was detected) def env_doc env = Environment.new - env.faas? ? env.to_h : nil + env.present? ? env.to_h : nil end def type diff --git a/lib/mongo/server/app_metadata/environment.rb b/lib/mongo/server/app_metadata/environment.rb index fafb1c73e7..029257faec 100644 --- a/lib/mongo/server/app_metadata/environment.rb +++ b/lib/mongo/server/app_metadata/environment.rb @@ -18,9 +18,12 @@ module Mongo class Server class AppMetadata # Implements the logic from the handshake spec, for deducing and - # reporting the current FaaS environment in which the program is + # reporting the current environment in which the program is # executing. # + # This includes FaaS environment checks, as well as checks for the + # presence of a container (Docker) and/or orchestrator (Kubernetes). + # # @api private class Environment # Error class for reporting that too many discriminators were found @@ -39,6 +42,10 @@ class TypeMismatch < Mongo::Error; end # Error class for reporting that the value for a field is too long. class ValueTooLong < Mongo::Error; end + # The name and location of the .dockerenv file that will signal the + # presence of Docker. + DOCKERENV_PATH = '/.dockerenv' + # This value is not explicitly specified in the spec, only implied to be # less than 512. MAXIMUM_VALUE_LENGTH = 500 @@ -102,9 +109,11 @@ class ValueTooLong < Mongo::Error; end # if the environment contains invalid or contradictory state, it will # be initialized with {{name}} set to {{nil}}. def initialize + @fields = {} @error = nil @name = detect_environment - populate_fields + populate_faas_fields + detect_container rescue TooManyEnvironments => e self.error = "too many environments detected: #{e.message}" rescue MissingVariable => e @@ -115,6 +124,23 @@ def initialize self.error = "value for #{e.message} is too long" end + # Queries the detected container information. + # + # @return [ Hash | nil ] the detected container information, or + # nil if no container was detected. + def container + fields[:container] + end + + # Queries whether any environment information was able to be + # detected. + # + # @return [ true | false ] if any environment information was + # detected. + def present? + @name || fields.any? + end + # Queries whether the current environment is a valid FaaS environment. # # @return [ true | false ] whether the environment is a FaaS @@ -159,14 +185,11 @@ def vercel? @name == 'vercel' end - # Compiles the detected environment information into a Hash. It will - # always include a {{name}} key, but may include other keys as well, - # depending on the detected FaaS environment. (See the handshake - # spec for details.) + # Compiles the detected environment information into a Hash. # # @return [ Hash ] the detected environment information. def to_h - fields.merge(name: name) + name ? fields.merge(name: name) : fields end private @@ -192,6 +215,38 @@ def detect_environment names.first end + # Looks for the presence of a container. Currently can detect + # Docker (by the existence of a .dockerenv file in the root + # directory) and Kubernetes (by the existence of the KUBERNETES_SERVICE_HOST + # environment variable). + def detect_container + runtime = docker_present? && 'docker' + orchestrator = kubernetes_present? && 'kubernetes' + + return unless runtime || orchestrator + + fields[:container] = {} + fields[:container][:runtime] = runtime if runtime + fields[:container][:orchestrator] = orchestrator if orchestrator + end + + # Checks for the existence of a .dockerenv in the root directory. + def docker_present? + File.exist?(dockerenv_path) + end + + # Implementing this as a method so that it can be mocked in tests, to + # test the presence or absence of Docker. + def dockerenv_path + DOCKERENV_PATH + end + + # Checks for the presence of a non-empty KUBERNETES_SERVICE_HOST + # environment variable. + def kubernetes_present? + !ENV['KUBERNETES_SERVICE_HOST'].to_s.empty? + end + # Determines whether the named environment variable exists, and (if # a pattern has been declared for that descriminator) whether the # pattern matches the value of the variable. @@ -212,10 +267,10 @@ def discriminator_matches?(var) # Extracts environment information from the current environment # variables, based on the detected FaaS environment. Populates the # {{@fields}} instance variable. - def populate_fields + def populate_faas_fields return unless name - @fields = FIELDS[name].each_with_object({}) do |(var, defn), fields| + FIELDS[name].each_with_object(@fields) do |(var, defn), fields| fields[defn[:field]] = extract_field(var, defn) end end diff --git a/spec/mongo/server/app_metadata/environment_spec.rb b/spec/mongo/server/app_metadata/environment_spec.rb index b63e174e16..0b0b844b59 100644 --- a/spec/mongo/server/app_metadata/environment_spec.rb +++ b/spec/mongo/server/app_metadata/environment_spec.rb @@ -1,8 +1,52 @@ # frozen_string_literal: true +# rubocop:todo all require 'spec_helper' +require 'fileutils' + +MOCKED_DOCKERENV_PATH = File.expand_path(File.join(Dir.pwd, '.dockerenv-mocked')) + +module ContainerChecking + def mock_dockerenv_path + before do + allow_any_instance_of(Mongo::Server::AppMetadata::Environment) + .to receive(:dockerenv_path) + .and_return(MOCKED_DOCKERENV_PATH) + end + end + + def with_docker + mock_dockerenv_path + + around do |example| + File.write(MOCKED_DOCKERENV_PATH, 'placeholder') + example.run + ensure + File.delete(MOCKED_DOCKERENV_PATH) + end + end + + def without_docker + mock_dockerenv_path + + around do |example| + FileUtils.rm_f(MOCKED_DOCKERENV_PATH) + example.run + end + end + + def with_kubernetes + local_env 'KUBERNETES_SERVICE_HOST' => 'kubernetes.default.svc.cluster.local' + end + + def without_kubernetes + local_env 'KUBERNETES_SERVICE_HOST' => nil + end +end describe Mongo::Server::AppMetadata::Environment do + extend ContainerChecking + let(:env) { described_class.new } shared_examples_for 'running in a FaaS environment' do @@ -17,6 +61,36 @@ end end + shared_examples_for 'not running in a Docker container' do + it 'does not detect Docker' do + expect(env.container || {}).not_to include :runtime + end + end + + shared_examples_for 'not running under Kubernetes' do + it 'does not detect Kubernetes' do + expect(env.container || {}).not_to include :orchestrator + end + end + + shared_examples_for 'running under Kubernetes' do + it 'detects that Kubernetes is present' do + expect(env.container[:orchestrator]).to be == 'kubernetes' + end + end + + shared_examples_for 'running in a Docker container' do + it 'detects that Docker is present' do + expect(env.container[:runtime]).to be == 'docker' + end + end + + shared_examples_for 'running under Kerbenetes' do + it 'detects that kubernetes is present' do + expect(env.container['orchestrator']).to be == 'kubernetes' + end + end + context 'when run outside of a FaaS environment' do it_behaves_like 'running outside a FaaS environment' end @@ -204,6 +278,67 @@ timeout_sec: 60, region: 'us-central1', } end + + context 'when a container is present' do + with_kubernetes + with_docker + + it 'includes a container key' do + expect(env.to_h[:container]).to be == { + runtime: 'docker', + orchestrator: 'kubernetes' + } + end + end + + context 'when no container is present' do + without_kubernetes + without_docker + + it 'does not include a container key' do + expect(env.to_h).not_to include(:container) + end + end + end + end + + # have a specific test for this, since the tests that check + # for Docker use a mocked value for the .dockerenv path. + it 'should look for dockerenv in root directory' do + expect(described_class::DOCKERENV_PATH).to be == '/.dockerenv' + end + + context 'when no container is present' do + without_kubernetes + without_docker + + it_behaves_like 'not running in a Docker container' + it_behaves_like 'not running under Kubernetes' + end + + context 'when container is present' do + context 'when kubernetes is present' do + without_docker + with_kubernetes + + it_behaves_like 'not running in a Docker container' + it_behaves_like 'running under Kubernetes' + end + + context 'when docker is present' do + with_docker + without_kubernetes + + it_behaves_like 'running in a Docker container' + it_behaves_like 'not running under Kubernetes' + end + + context 'when both kubernetes and docker are present' do + with_docker + with_kubernetes + + it_behaves_like 'running in a Docker container' + it_behaves_like 'running under Kubernetes' end end end diff --git a/spec/mongo/server/app_metadata_spec.rb b/spec/mongo/server/app_metadata_spec.rb index 47da5fa8bb..a3b8619b63 100644 --- a/spec/mongo/server/app_metadata_spec.rb +++ b/spec/mongo/server/app_metadata_spec.rb @@ -87,8 +87,18 @@ end context 'when run outside of a FaaS environment' do - it 'excludes the :env key from the client document' do - expect(app_metadata.client_document.key?(:env)).to be false + context 'when a container is present' do + local_env 'KUBERNETES_SERVICE_HOST' => 'something' + + it 'includes the :env key in the client document' do + expect(app_metadata.client_document.key?(:env)).to be true + end + end + + context 'when no container is present' do + it 'excludes the :env key from the client document' do + expect(app_metadata.client_document.key?(:env)).to be false + end end end From 9228f5f393274b7edb5449e038d9d0abbe94b661 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 15 Jan 2024 06:56:51 -0700 Subject: [PATCH 177/198] RUBY-1813 Discard ServerSessions involved in network errors (#2825) * RUBY-1813 Discard ServerSessions involved in network errors * no need to test the now-non-existant SessionPool.create method * Tweak from code review Co-authored-by: Dmitry Rybakov * Another tweak from code review Co-authored-by: Dmitry Rybakov --------- Co-authored-by: Dmitry Rybakov --- lib/mongo/cluster.rb | 2 +- lib/mongo/collection.rb | 2 +- lib/mongo/operation/shared/executable.rb | 70 ++-- .../operation/shared/response_handling.rb | 48 ++- lib/mongo/session.rb | 17 + lib/mongo/session/server_session.rb | 3 + lib/mongo/session/server_session/dirtyable.rb | 52 +++ lib/mongo/session/session_pool.rb | 30 +- spec/mongo/session/session_pool_spec.rb | 17 +- spec/runners/unified/support_operations.rb | 8 +- .../driver-sessions-dirty-session-errors.yml | 351 ++++++++++++++++++ 11 files changed, 507 insertions(+), 93 deletions(-) create mode 100644 lib/mongo/session/server_session/dirtyable.rb create mode 100644 spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml diff --git a/lib/mongo/cluster.rb b/lib/mongo/cluster.rb index 509a66f2e3..eac6d6229d 100644 --- a/lib/mongo/cluster.rb +++ b/lib/mongo/cluster.rb @@ -157,7 +157,7 @@ def initialize(seeds, monitoring, options = Options::Redacted.new) # @sdam_flow_lock covers just the sdam flow. Note it does not apply # to @topology replacements which are done under @update_lock. @sdam_flow_lock = Mutex.new - Session::SessionPool.create(self) + @session_pool = Session::SessionPool.new(self) if seeds.empty? && load_balanced? raise ArgumentError, 'Load-balanced clusters with no seeds are prohibited' diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 58d56e1b94..a2b2076b7d 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -790,7 +790,7 @@ def inspect def insert_one(document, opts = {}) QueryCache.clear_namespace(namespace) - client.send(:with_session, opts) do |session| + client.with_session(opts) do |session| write_concern = if opts[:write_concern] WriteConcern.get(opts[:write_concern]) else diff --git a/lib/mongo/operation/shared/executable.rb b/lib/mongo/operation/shared/executable.rb index 229479e24b..9b61476631 100644 --- a/lib/mongo/operation/shared/executable.rb +++ b/lib/mongo/operation/shared/executable.rb @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'mongo/error' + module Mongo module Operation @@ -30,40 +32,42 @@ def do_execute(connection, context, options = {}) session&.materialize_if_needed unpin_maybe(session, connection) do add_error_labels(connection, context) do - add_server_diagnostics(connection) do - get_result(connection, context, options).tap do |result| - if session - if session.in_transaction? && - connection.description.load_balancer? - then - if session.pinned_connection_global_id - unless session.pinned_connection_global_id == connection.global_id - raise( - Error::InternalDriverError, - "Expected operation to use connection #{session.pinned_connection_global_id} but it used #{connection.global_id}" - ) + check_for_network_error do + add_server_diagnostics(connection) do + get_result(connection, context, options).tap do |result| + if session + if session.in_transaction? && + connection.description.load_balancer? + then + if session.pinned_connection_global_id + unless session.pinned_connection_global_id == connection.global_id + raise( + Error::InternalDriverError, + "Expected operation to use connection #{session.pinned_connection_global_id} but it used #{connection.global_id}" + ) + end + else + session.pin_to_connection(connection.global_id) + connection.pin end - else - session.pin_to_connection(connection.global_id) - connection.pin end - end - if session.snapshot? && !session.snapshot_timestamp - session.snapshot_timestamp = result.snapshot_timestamp + if session.snapshot? && !session.snapshot_timestamp + session.snapshot_timestamp = result.snapshot_timestamp + end end - end - if result.has_cursor_id? && - connection.description.load_balancer? - then - if result.cursor_id == 0 - connection.unpin - else - connection.pin + if result.has_cursor_id? && + connection.description.load_balancer? + then + if result.cursor_id == 0 + connection.unpin + else + connection.pin + end end + process_result(result, connection) end - process_result(result, connection) end end end @@ -144,6 +148,18 @@ def process_result_for_sdam(result, connection) connection.server.scan_semaphore.signal end end + + NETWORK_ERRORS = [ + Error::SocketError, + Error::SocketTimeoutError + ].freeze + + def check_for_network_error + yield + rescue *NETWORK_ERRORS + session&.dirty! + raise + end end end end diff --git a/lib/mongo/operation/shared/response_handling.rb b/lib/mongo/operation/shared/response_handling.rb index af4c83ca75..799721a8de 100644 --- a/lib/mongo/operation/shared/response_handling.rb +++ b/lib/mongo/operation/shared/response_handling.rb @@ -50,35 +50,33 @@ def validate_result(result, connection, context) # the operation is performed. # @param [ Mongo::Operation::Context ] context The operation context. def add_error_labels(connection, context) - begin - yield - rescue Mongo::Error::SocketError => e - if context.in_transaction? && !context.committing_transaction? - e.add_label('TransientTransactionError') - end - if context.committing_transaction? - e.add_label('UnknownTransactionCommitResult') - end + yield + rescue Mongo::Error::SocketError => e + if context.in_transaction? && !context.committing_transaction? + e.add_label('TransientTransactionError') + end + if context.committing_transaction? + e.add_label('UnknownTransactionCommitResult') + end - maybe_add_retryable_write_error_label!(e, connection, context) - - raise e - rescue Mongo::Error::SocketTimeoutError => e - maybe_add_retryable_write_error_label!(e, connection, context) - raise e - rescue Mongo::Error::OperationFailure => e - if context.committing_transaction? - if e.write_retryable? || e.wtimeout? || (e.write_concern_error? && - !Session::UNLABELED_WRITE_CONCERN_CODES.include?(e.write_concern_error_code) - ) || e.max_time_ms_expired? - e.add_label('UnknownTransactionCommitResult') - end + maybe_add_retryable_write_error_label!(e, connection, context) + + raise e + rescue Mongo::Error::SocketTimeoutError => e + maybe_add_retryable_write_error_label!(e, connection, context) + raise e + rescue Mongo::Error::OperationFailure => e + if context.committing_transaction? + if e.write_retryable? || e.wtimeout? || (e.write_concern_error? && + !Session::UNLABELED_WRITE_CONCERN_CODES.include?(e.write_concern_error_code) + ) || e.max_time_ms_expired? + e.add_label('UnknownTransactionCommitResult') end + end - maybe_add_retryable_write_error_label!(e, connection, context) + maybe_add_retryable_write_error_label!(e, connection, context) - raise e - end + raise e end # Unpins the session and/or the connection if the yielded to block diff --git a/lib/mongo/session.rb b/lib/mongo/session.rb index bc12896af0..d3725adfba 100644 --- a/lib/mongo/session.rb +++ b/lib/mongo/session.rb @@ -123,6 +123,23 @@ def snapshot? # @since 2.5.0 attr_reader :operation_time + # Sets the dirty state to the given value for the underlying server + # session. If there is no server session, this does nothing. + # + # @param [ true | false ] mark whether to mark the server session as + # dirty, or not. + def dirty!(mark = true) + @server_session&.dirty!(mark) + end + + # @return [ true | false | nil ] whether the underlying server session is + # dirty. If no server session exists for this session, returns nil. + # + # @api private + def dirty? + @server_session&.dirty? + end + # @return [ Hash ] The options for the transaction currently being executed # on this session. # diff --git a/lib/mongo/session/server_session.rb b/lib/mongo/session/server_session.rb index 6d0410903a..6f7283c79e 100644 --- a/lib/mongo/session/server_session.rb +++ b/lib/mongo/session/server_session.rb @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'mongo/session/server_session/dirtyable' + module Mongo class Session @@ -25,6 +27,7 @@ class Session # # @since 2.5.0 class ServerSession + include Dirtyable # Regex for removing dashes from the UUID string. # diff --git a/lib/mongo/session/server_session/dirtyable.rb b/lib/mongo/session/server_session/dirtyable.rb new file mode 100644 index 0000000000..0df262c85b --- /dev/null +++ b/lib/mongo/session/server_session/dirtyable.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# Copyright (C) 2024 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Session + class ServerSession + # Functionality for manipulating and querying a session's + # "dirty" state, per the last paragraph at + # https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#server-session-pool + # + # If a driver has a server session pool and a network error is + # encountered when executing any command with a ClientSession, the + # driver MUST mark the associated ServerSession as dirty. Dirty server + # sessions are discarded when returned to the server session pool. It is + # valid for a dirty session to be used for subsequent commands (e.g. an + # implicit retry attempt, a later command in a bulk write, or a later + # operation on an explicit session), however, it MUST remain dirty for + # the remainder of its lifetime regardless if later commands succeed. + # + # @api private + module Dirtyable + # Query whether the server session has been marked dirty or not. + # + # @return [ true | false ] the server session's dirty state + def dirty? + @dirty + end + + # Mark the server session as dirty (the default) or clean. + # + # @param [ true | false ] mark whether the mark the server session + # dirty or not. + def dirty!(mark = true) + @dirty = mark + end + end + end + end +end diff --git a/lib/mongo/session/session_pool.rb b/lib/mongo/session/session_pool.rb index e89d634c31..4fb832a813 100644 --- a/lib/mongo/session/session_pool.rb +++ b/lib/mongo/session/session_pool.rb @@ -25,21 +25,6 @@ class Session # # @since 2.5.0 class SessionPool - - # Create a SessionPool. - # - # @example - # SessionPool.create(cluster) - # - # @param [ Mongo::Cluster ] cluster The cluster that will be associated with this - # session pool. - # - # @since 2.5.0 - def self.create(cluster) - pool = new(cluster) - cluster.instance_variable_set(:@session_pool, pool) - end - # Initialize a SessionPool. # # @example @@ -105,9 +90,7 @@ def checkin(session) @mutex.synchronize do prune! - unless about_to_expire?(session) - @queue.unshift(session) - end + @queue.unshift(session) if return_to_queue?(session) end end @@ -136,6 +119,17 @@ def end_sessions private + # Query whether the given session is okay to return to the + # pool's queue. + # + # @param [ Session::ServerSession ] session the session to query + # + # @return [ true | false ] whether to return the session to the + # queue. + def return_to_queue?(session) + !session.dirty? && !about_to_expire?(session) + end + def about_to_expire?(session) if session.nil? raise ArgumentError, 'session cannot be nil' diff --git a/spec/mongo/session/session_pool_spec.rb b/spec/mongo/session/session_pool_spec.rb index 1dedebed25..caac0117be 100644 --- a/spec/mongo/session/session_pool_spec.rb +++ b/spec/mongo/session/session_pool_spec.rb @@ -17,21 +17,6 @@ end end - describe '.create' do - - let!(:pool) do - described_class.create(cluster) - end - - it 'creates a session pool' do - expect(pool).to be_a(Mongo::Session::SessionPool) - end - - it 'adds the pool as an instance variable on the cluster' do - expect(cluster.session_pool).to eq(pool) - end - end - describe '#initialize' do let(:pool) do @@ -181,7 +166,7 @@ describe '#end_sessions' do let(:pool) do - described_class.create(client.cluster) + client.cluster.session_pool end let!(:session_a) do diff --git a/spec/runners/unified/support_operations.rb b/spec/runners/unified/support_operations.rb index f8b4e53af3..a99d310958 100644 --- a/spec/runners/unified/support_operations.rb +++ b/spec/runners/unified/support_operations.rb @@ -70,8 +70,7 @@ def assert_session_dirty(op) consume_test_runner(op) use_arguments(op) do |args| session = entities.get(:session, args.use!('session')) - # https://jira.mongodb.org/browse/RUBY-1813 - true + session.dirty? || raise(Error::ResultMismatch, 'expected session to be dirty') end end @@ -79,8 +78,7 @@ def assert_session_not_dirty(op) consume_test_runner(op) use_arguments(op) do |args| session = entities.get(:session, args.use!('session')) - # https://jira.mongodb.org/browse/RUBY-1813 - true + session.dirty? && raise(Error::ResultMismatch, 'expected session to be not dirty') end end @@ -92,7 +90,7 @@ def assert_same_lsid_on_last_two_commands(op, expected: true) unless subscriber.started_events.length >= 2 raise Error::ResultMismatch, "Must have at least 2 events, have #{subscriber.started_events.length}" end - lsids = subscriber.started_events[-2...-1].map do |cmd| + lsids = subscriber.started_events[-2..-1].map do |cmd| cmd.command.fetch('lsid') end if expected diff --git a/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml b/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml new file mode 100644 index 0000000000..b7f2917efc --- /dev/null +++ b/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml @@ -0,0 +1,351 @@ +description: "driver-sessions-dirty-session-errors" + +schemaVersion: "1.0" + +runOnRequirements: + - minServerVersion: "4.0" + topologies: [ replicaset ] + - minServerVersion: "4.1.8" + topologies: [ sharded ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name session-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1 } + +tests: + - description: "Dirty explicit session is discarded (insert)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + closeConnection: true + - name: assertSessionNotDirty + object: testRunner + arguments: + session: *session0 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 2 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } } + - name: assertSessionDirty + object: testRunner + arguments: + session: *session0 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 3 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } } + - name: assertSessionDirty + object: testRunner + arguments: + session: *session0 + - name: endSession + object: *session0 + - &find_with_implicit_session + name: find + object: *collection0 + arguments: + filter: { _id: -1 } + expectResult: [] + - name: assertDifferentLsidOnLastTwoCommands + object: testRunner + arguments: + client: *client0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &insert_attempt + command: + insert: *collection0Name + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: 1 + commandName: insert + databaseName: *database0Name + - commandStartedEvent: *insert_attempt + - commandStartedEvent: + command: + insert: *collection0Name + documents: + - { _id: 3 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: 2 + commandName: insert + databaseName: *database0Name + - commandStartedEvent: &find_with_implicit_session_event + command: + find: *collection0Name + filter: { _id: -1 } + # There is no explicit session to use with $$sessionLsid, so + # just assert an arbitrary lsid document + lsid: { $$type: object } + commandName: find + databaseName: *database0Name + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + + - description: "Dirty explicit session is discarded (findAndModify)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + closeConnection: true + - name: assertSessionNotDirty + object: testRunner + arguments: + session: *session0 + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectResult: { _id: 1 } + - name: assertSessionDirty + object: testRunner + arguments: + session: *session0 + - name: endSession + object: *session0 + - *find_with_implicit_session + - name: assertDifferentLsidOnLastTwoCommands + object: testRunner + arguments: + client: *client0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &findAndModify_attempt + command: + findAndModify: *collection0Name + query: { _id: 1 } + update: { $inc: { x: 1 } } + new: false + lsid: { $$sessionLsid: *session0 } + txnNumber: 1 + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database0Name + - commandStartedEvent: *findAndModify_attempt + - commandStartedEvent: *find_with_implicit_session_event + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 1 } + + - description: "Dirty implicit session is discarded (insert)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + closeConnection: true + - name: insertOne + object: *collection0 + arguments: + document: { _id: 2 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } } + - *find_with_implicit_session + - name: assertDifferentLsidOnLastTwoCommands + object: testRunner + arguments: + client: *client0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &insert_attempt + command: + insert: *collection0Name + documents: + - { _id: 2 } + ordered: true + lsid: { $$type: object } + txnNumber: 1 + commandName: insert + databaseName: *database0Name + - commandStartedEvent: *insert_attempt + - commandStartedEvent: *find_with_implicit_session_event + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1 } + - { _id: 2 } + + - description: "Dirty implicit session is discarded (findAndModify)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + closeConnection: true + - name: findOneAndUpdate + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectResult: { _id: 1 } + - *find_with_implicit_session + - name: assertDifferentLsidOnLastTwoCommands + object: testRunner + arguments: + client: *client0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &findAndModify_attempt + command: + findAndModify: *collection0Name + query: { _id: 1 } + update: { $inc: { x: 1 } } + new: false + lsid: { $$type: object } + txnNumber: 1 + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database0Name + - commandStartedEvent: *findAndModify_attempt + - commandStartedEvent: *find_with_implicit_session_event + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 1 } + + - description: "Dirty implicit session is discarded (read returning cursor)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ aggregate ] + closeConnection: true + - name: aggregate + object: *collection0 + arguments: + pipeline: [ { $project: { _id: 1 } } ] + expectResult: [ { _id: 1 } ] + - *find_with_implicit_session + - name: assertDifferentLsidOnLastTwoCommands + object: testRunner + arguments: + client: *client0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &aggregate_attempt + command: + aggregate: *collection0Name + pipeline: [ { $project: { _id: 1 } } ] + lsid: { $$type: object } + commandName: aggregate + databaseName: *database0Name + - commandStartedEvent: *aggregate_attempt + - commandStartedEvent: *find_with_implicit_session_event + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1 } + + - description: "Dirty implicit session is discarded (read not returning cursor)" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ aggregate ] + closeConnection: true + - name: countDocuments + object: *collection0 + arguments: + filter: {} + expectResult: 1 + - *find_with_implicit_session + - name: assertDifferentLsidOnLastTwoCommands + object: testRunner + arguments: + client: *client0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &countDocuments_attempt + command: + aggregate: *collection0Name + pipeline: [ { $match: {} }, { $group: { _id: 1, n: { $sum: 1 } } } ] + lsid: { $$type: object } + commandName: aggregate + databaseName: *database0Name + - commandStartedEvent: *countDocuments_attempt + - commandStartedEvent: *find_with_implicit_session_event + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1 } From 1afa0a5238f24fe45d42a00f21aa531cafc04d96 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 15 Jan 2024 06:57:38 -0700 Subject: [PATCH 178/198] RUBY-3329 Connection String: make delimiting slash between hosts and options optional (#2824) --- lib/mongo/uri.rb | 4 ---- spec/mongo/uri_spec.rb | 9 --------- .../data/connection_string/invalid-uris.yml | 10 ---------- .../data/connection_string/valid-options.yml | 13 +++++++++++++ 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/lib/mongo/uri.rb b/lib/mongo/uri.rb index 899ec94d0a..0b891a858c 100644 --- a/lib/mongo/uri.rb +++ b/lib/mongo/uri.rb @@ -377,10 +377,6 @@ def parse!(remaining) raise_invalid_error!("Options contain an unescaped question mark (?), or the database name contains a question mark and was not escaped") end - if options && !hosts_and_db.index('/') - raise_invalid_error!("MongoDB URI must have a slash (/) after the hosts if options are given") - end - hosts, db = hosts_and_db.split('/', 2) if db && db.index('/') raise_invalid_error!("Database name contains an unescaped slash (/): #{db}") diff --git a/spec/mongo/uri_spec.rb b/spec/mongo/uri_spec.rb index f2e2e55e4f..0fffa6ef62 100644 --- a/spec/mongo/uri_spec.rb +++ b/spec/mongo/uri_spec.rb @@ -266,15 +266,6 @@ end end - context 'no slash after hosts, and options' do - - let(:string) { 'mongodb://example.com?tls=true' } - - it 'raises an error' do - expect { uri }.to raise_error(Mongo::Error::InvalidURI, %r,MongoDB URI must have a slash \(/\) after the hosts if options are given,) - end - end - context 'mongodb://example.com/?w' do let(:string) { 'mongodb://example.com/?w' } diff --git a/spec/spec_tests/data/connection_string/invalid-uris.yml b/spec/spec_tests/data/connection_string/invalid-uris.yml index 395e60eed9..dd4d4ce31c 100644 --- a/spec/spec_tests/data/connection_string/invalid-uris.yml +++ b/spec/spec_tests/data/connection_string/invalid-uris.yml @@ -143,14 +143,6 @@ tests: hosts: ~ auth: ~ options: ~ - - - description: "Missing delimiting slash between hosts and options" - uri: "mongodb://example.com?w=1" - valid: false - warning: ~ - hosts: ~ - auth: ~ - options: ~ - description: "Incomplete key value pair for option" uri: "mongodb://example.com/?w" @@ -257,5 +249,3 @@ tests: hosts: ~ auth: ~ options: ~ - - diff --git a/spec/spec_tests/data/connection_string/valid-options.yml b/spec/spec_tests/data/connection_string/valid-options.yml index e1b94039c8..8cb0dea3a6 100644 --- a/spec/spec_tests/data/connection_string/valid-options.yml +++ b/spec/spec_tests/data/connection_string/valid-options.yml @@ -15,3 +15,16 @@ tests: db: "admin" options: authmechanism: "MONGODB-CR" + - + description: "Missing delimiting slash between hosts and options" + uri: "mongodb://example.com?tls=true" + valid: true + warning: false + hosts: + - + type: "hostname" + host: "example.com" + port: ~ + auth: ~ + options: + tls: true From 199816f59188c60b9e870a881727568876f61f1f Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 15 Jan 2024 06:58:13 -0700 Subject: [PATCH 179/198] RUBY-2254 sort by id when comparing collection contents (#2823) --- spec/mongo/collection_crud_spec.rb | 2 +- spec/mongo/operation/insert_spec.rb | 2 +- spec/runners/crud.rb | 2 +- spec/runners/crud/test.rb | 8 -------- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/spec/mongo/collection_crud_spec.rb b/spec/mongo/collection_crud_spec.rb index 2f26179ffc..7a5651d1d6 100644 --- a/spec/mongo/collection_crud_spec.rb +++ b/spec/mongo/collection_crud_spec.rb @@ -2491,7 +2491,7 @@ def generate end let(:updated) do - authorized_collection.find.to_a.last + authorized_collection.find.sort(_id: 1).to_a.last end it 'reports that a document was written' do diff --git a/spec/mongo/operation/insert_spec.rb b/spec/mongo/operation/insert_spec.rb index 2c15d0fef6..2a01fa6e17 100644 --- a/spec/mongo/operation/insert_spec.rb +++ b/spec/mongo/operation/insert_spec.rb @@ -177,7 +177,7 @@ end it 'inserts the documents into the collection' do - expect(authorized_collection.find.to_a). to eq(documents) + expect(authorized_collection.find.sort(_id: 1).to_a). to eq(documents) end end diff --git a/spec/runners/crud.rb b/spec/runners/crud.rb index 765597bac0..b0a76b5aca 100644 --- a/spec/runners/crud.rb +++ b/spec/runners/crud.rb @@ -26,7 +26,7 @@ require 'runners/crud/verifier' def collection_data(collection) - collection.find.to_a + collection.find.sort(_id: 1).to_a end def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error, diff --git a/spec/runners/crud/test.rb b/spec/runners/crud/test.rb index 57793e23c6..ae210b584f 100644 --- a/spec/runners/crud/test.rb +++ b/spec/runners/crud/test.rb @@ -110,14 +110,6 @@ def setup_test(spec, client) end setup_fail_point(client) end - - def actual_collection_contents(client) - unless @spec.collection_name - raise ArgumentError, 'Spec does not specify a global collection' - end - - client[@spec.collection_name, read_concern: {level: :majority}].find.to_a - end end end end From 5f4a7e30f226b6a814c1fd7287cb5bcead98edbc Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 15 Jan 2024 15:26:18 +0100 Subject: [PATCH 180/198] RUBY-1791 Raise if transactions not supported (#2822) --- lib/mongo/error.rb | 1 + lib/mongo/error/transactions_not_supported.rb | 34 +++++++++++++++++++ lib/mongo/server/description/features.rb | 1 + lib/mongo/session.rb | 15 ++++++++ spec/mongo/session_transaction_spec.rb | 15 ++++++++ 5 files changed, 66 insertions(+) create mode 100644 lib/mongo/error/transactions_not_supported.rb diff --git a/lib/mongo/error.rb b/lib/mongo/error.rb index 36296735d7..92d6d5f4b3 100644 --- a/lib/mongo/error.rb +++ b/lib/mongo/error.rb @@ -217,6 +217,7 @@ def write_concern_error_labels require 'mongo/error/server_api_conflict' require 'mongo/error/server_api_not_supported' require 'mongo/error/server_not_usable' +require 'mongo/error/transactions_not_supported' require 'mongo/error/unknown_payload_type' require 'mongo/error/unmet_dependency' require 'mongo/error/unsupported_option' diff --git a/lib/mongo/error/transactions_not_supported.rb b/lib/mongo/error/transactions_not_supported.rb new file mode 100644 index 0000000000..bbaa6c7c58 --- /dev/null +++ b/lib/mongo/error/transactions_not_supported.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Copyright (C) 2019-2020 MongoDB Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Mongo + class Error + # Transactions are not supported by the cluster. There might be the + # following reasons: + # - topology is standalone + # - topology is replica set and server version is < 4.0 + # - topology is sharded and server version is < 4.2 + # + # @param [ String ] reason The reason why transactions are no supported. + # + # @since 2.7.0 + class TransactionsNotSupported < Error + def initialize(reason) + super("Transactions are not supported for the cluster: #{reason}") + end + end + end +end diff --git a/lib/mongo/server/description/features.rb b/lib/mongo/server/description/features.rb index 849374546c..97a222713b 100644 --- a/lib/mongo/server/description/features.rb +++ b/lib/mongo/server/description/features.rb @@ -48,6 +48,7 @@ class Features # provided by the client during findAndModify operations, requiring the # driver to raise client-side errors when those options are provided. find_and_modify_option_validation: 8, + sharded_transactions: 8, transactions: 7, scram_sha_256: 7, array_filters: 6, diff --git a/lib/mongo/session.rb b/lib/mongo/session.rb index d3725adfba..b22519efc1 100644 --- a/lib/mongo/session.rb +++ b/lib/mongo/session.rb @@ -555,6 +555,8 @@ def with_transaction(options=nil) # # @since 2.6.0 def start_transaction(options = nil) + check_transactions_supported! + if options Lint.validate_read_concern_option(options[:read_concern]) @@ -1202,5 +1204,18 @@ def check_matching_cluster!(client) raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG) end end + + def check_transactions_supported! + raise Mongo::Error::TransactionsNotSupported, "standalone topology" if cluster.single? + + cluster.next_primary.with_connection do |conn| + if cluster.replica_set? && !conn.features.transactions_enabled? + raise Mongo::Error::TransactionsNotSupported, "server version is < 4.0" + end + if cluster.sharded? && !conn.features.sharded_transactions_enabled? + raise Mongo::Error::TransactionsNotSupported, "sharded transactions require server version >= 4.2" + end + end + end end end diff --git a/spec/mongo/session_transaction_spec.rb b/spec/mongo/session_transaction_spec.rb index 851b08433f..37fcc92f08 100644 --- a/spec/mongo/session_transaction_spec.rb +++ b/spec/mongo/session_transaction_spec.rb @@ -26,6 +26,17 @@ class SessionTransactionSpecError < StandardError; end collection.delete_many end + describe 'start_transaction' do + context 'when topology is sharded and server is < 4.2' do + max_server_fcv '4.1' + require_topology :sharded + + it 'raises an error' do + expect { session.start_transaction }.to raise_error(Mongo::Error::TransactionsNotSupported, /sharded transactions require server version/) + end + end + end + describe '#abort_transaction' do require_topology :replica_set @@ -75,6 +86,8 @@ class SessionTransactionSpecError < StandardError; end end describe '#with_transaction' do + require_topology :replica_set + context 'callback successful' do it 'commits' do session.with_transaction do @@ -123,6 +136,7 @@ class SessionTransactionSpecError < StandardError; end expect(Mongo::Utils).to receive(:monotonic_time).ordered.and_return(start + 1) expect(Mongo::Utils).to receive(:monotonic_time).ordered.and_return(start + 2) expect(Mongo::Utils).to receive(:monotonic_time).ordered.and_return(start + 200) + allow(session).to receive('check_transactions_supported!').and_return true expect do session.with_transaction do @@ -156,6 +170,7 @@ class SessionTransactionSpecError < StandardError; end expect(Mongo::Utils).to receive(:monotonic_time).ordered.and_return(start + i) end expect(Mongo::Utils).to receive(:monotonic_time).ordered.and_return(start + 200) + allow(session).to receive('check_transactions_supported!').and_return true exc = Mongo::Error::OperationFailure.new('timeout test') exc.add_label(label) From 60d1f69819eb301cb3ad272a8148fd399471ae99 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Fri, 19 Jan 2024 16:55:50 +0100 Subject: [PATCH 181/198] RUBY-3358 Handle nil session is write_worker (#2826) --- lib/mongo/retryable/write_worker.rb | 3 +- spec/mongo/retryable/write_worker_spec.rb | 39 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 spec/mongo/retryable/write_worker_spec.rb diff --git a/lib/mongo/retryable/write_worker.rb b/lib/mongo/retryable/write_worker.rb index 303ff3af06..339a28b2f1 100644 --- a/lib/mongo/retryable/write_worker.rb +++ b/lib/mongo/retryable/write_worker.rb @@ -103,8 +103,9 @@ def write_with_retry(write_concern, ending_transaction: false, context:, &block) def nro_write_with_retry(write_concern, context:, &block) session = context.session server = select_server(cluster, ServerSelector.primary, session) + options = session&.client&.options || {} - if session&.client.options[:retry_writes] + if options[:retry_writes] begin server.with_connection(connection_global_id: context.connection_global_id) do |connection| yield connection, nil, context diff --git a/spec/mongo/retryable/write_worker_spec.rb b/spec/mongo/retryable/write_worker_spec.rb new file mode 100644 index 0000000000..db0cd9ffaa --- /dev/null +++ b/spec/mongo/retryable/write_worker_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Mongo::Retryable::WriteWorker do + describe '#nro_write_with_retry' do + context 'when session is nil' do + let(:retryable) do + authorized_client['write_worker_test'] + end + + let(:write_concern) do + Mongo::WriteConcern.get(w: 0) + end + + let(:write_worker) do + described_class.new(retryable) + end + + let(:context) do + instance_double(Mongo::Operation::Context).tap do |context| + allow(context).to receive(:session).and_return(nil) + end + end + + before do + # We avoid actual execution of the operation to speed up and simplify + # the spec. + allow(write_worker).to receive(:legacy_write_with_retry).and_return(nil) + end + + it 'does not raise' do + expect do + write_worker.nro_write_with_retry(write_concern, context: context) + end.not_to raise_error + end + end + end +end From 853c268177b7f95ceba2340f6fe5d6e87cac7ff2 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 25 Jan 2024 08:19:35 -0700 Subject: [PATCH 182/198] Fix failing atlas connectivity specs (#2829) * Fix failing atlas connectivity specs * Update spec/atlas/atlas_connectivity_spec.rb Co-authored-by: Dmitry Rybakov --------- Co-authored-by: Dmitry Rybakov --- spec/atlas/atlas_connectivity_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/atlas/atlas_connectivity_spec.rb b/spec/atlas/atlas_connectivity_spec.rb index 0412d1bd84..ecd511ffb3 100644 --- a/spec/atlas/atlas_connectivity_spec.rb +++ b/spec/atlas/atlas_connectivity_spec.rb @@ -11,13 +11,13 @@ describe 'connection to Atlas' do it 'runs ismaster successfully' do - result = client.database.command(:ismaster => 1) - expect(result.documents.first['ismaster']).to be true + expect { client.database.command(:hello => 1) } + .not_to raise_error end it 'runs findOne successfully' do - result = client.use(:test)['test'].find.to_a - expect(result).to be_a(Array) + expect { client.use(:test)['test'].find.to_a } + .not_to raise_error end end end From 3329f15a36eb19974aa489e2780df45b2230b30e Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 25 Jan 2024 09:49:07 -0700 Subject: [PATCH 183/198] RUBY-1250 Run DriverBench benchmarks in Evergreen (#2827) * refactoring/reimplementation of driver benchmarks * computing scores * supplant profile/benchmarking with profile/driver_bench * make the linter happy * optimize some of the setup stages * add benchmark task to evergreen config * rubocop * add task for downloading benchmark data files * just use run-tests.sh for benchmarks, too * add `eg` tasks for evergreen * tweak more things to get it all to work --- .evergreen/config.yml | 26 ++ .evergreen/config/common.yml.erb | 16 ++ .evergreen/config/standard.yml.erb | 10 + .gitignore | 2 +- Rakefile | 28 +- lib/mongo/grid/fs_bucket.rb | 6 + profile/benchmark_21.rb | 62 ----- profile/benchmarking.rb | 82 ------ profile/benchmarking/bson.rb | 151 ----------- profile/benchmarking/helper.rb | 164 ------------ profile/benchmarking/multi_doc.rb | 182 ------------- profile/benchmarking/parallel.rb | 244 ------------------ profile/benchmarking/rake/bson.rake | 122 --------- profile/benchmarking/rake/multi_doc.rake | 34 --- profile/benchmarking/rake/parallel.rake | 36 --- profile/benchmarking/rake/single_doc.rake | 27 -- profile/benchmarking/rake/tasks.rake | 11 - profile/benchmarking/single_doc.rb | 149 ----------- profile/benchmarking/summary.rb | 56 ---- profile/driver_bench.rb | 3 + profile/driver_bench/base.rb | 181 +++++++++++++ profile/driver_bench/bson.rb | 16 ++ profile/driver_bench/bson/base.rb | 30 +++ profile/driver_bench/bson/decodable.rb | 32 +++ profile/driver_bench/bson/deep.rb | 14 + profile/driver_bench/bson/deep/base.rb | 24 ++ profile/driver_bench/bson/deep/decoding.rb | 23 ++ profile/driver_bench/bson/deep/encoding.rb | 23 ++ profile/driver_bench/bson/encodable.rb | 29 +++ profile/driver_bench/bson/flat.rb | 14 + profile/driver_bench/bson/flat/base.rb | 24 ++ profile/driver_bench/bson/flat/decoding.rb | 22 ++ profile/driver_bench/bson/flat/encoding.rb | 22 ++ profile/driver_bench/bson/full.rb | 14 + profile/driver_bench/bson/full/base.rb | 24 ++ profile/driver_bench/bson/full/decoding.rb | 22 ++ profile/driver_bench/bson/full/encoding.rb | 22 ++ profile/driver_bench/multi_doc.rb | 16 ++ profile/driver_bench/multi_doc/base.rb | 47 ++++ profile/driver_bench/multi_doc/bulk_insert.rb | 14 + .../multi_doc/bulk_insert/base.rb | 38 +++ .../multi_doc/bulk_insert/large_doc.rb | 28 ++ .../multi_doc/bulk_insert/small_doc.rb | 28 ++ profile/driver_bench/multi_doc/find_many.rb | 39 +++ profile/driver_bench/multi_doc/grid_fs.rb | 14 + .../driver_bench/multi_doc/grid_fs/base.rb | 32 +++ .../multi_doc/grid_fs/download.rb | 41 +++ .../driver_bench/multi_doc/grid_fs/upload.rb | 36 +++ profile/driver_bench/parallel.rb | 15 ++ profile/driver_bench/parallel/base.rb | 35 +++ profile/driver_bench/parallel/counter.rb | 57 ++++ profile/driver_bench/parallel/dispatcher.rb | 75 ++++++ profile/driver_bench/parallel/gridfs.rb | 14 + profile/driver_bench/parallel/gridfs/base.rb | 41 +++ .../driver_bench/parallel/gridfs/download.rb | 76 ++++++ .../driver_bench/parallel/gridfs/upload.rb | 51 ++++ profile/driver_bench/parallel/ldjson.rb | 14 + profile/driver_bench/parallel/ldjson/base.rb | 50 ++++ .../driver_bench/parallel/ldjson/export.rb | 75 ++++++ .../driver_bench/parallel/ldjson/import.rb | 49 ++++ .../percentiles.rb | 2 +- profile/driver_bench/rake/tasks.rake | 38 +++ profile/driver_bench/single_doc.rb | 17 ++ profile/driver_bench/single_doc/base.rb | 54 ++++ .../driver_bench/single_doc/find_one_by_id.rb | 36 +++ profile/driver_bench/single_doc/insert_one.rb | 14 + .../single_doc/insert_one/base.rb | 30 +++ .../single_doc/insert_one/large_doc.rb | 28 ++ .../single_doc/insert_one/small_doc.rb | 28 ++ .../driver_bench/single_doc/run_command.rb | 36 +++ profile/driver_bench/suite.rb | 142 ++++++++++ profile/profile.rb | 47 ---- 72 files changed, 1934 insertions(+), 1370 deletions(-) delete mode 100644 profile/benchmark_21.rb delete mode 100644 profile/benchmarking.rb delete mode 100644 profile/benchmarking/bson.rb delete mode 100644 profile/benchmarking/helper.rb delete mode 100644 profile/benchmarking/multi_doc.rb delete mode 100644 profile/benchmarking/parallel.rb delete mode 100644 profile/benchmarking/rake/bson.rake delete mode 100644 profile/benchmarking/rake/multi_doc.rake delete mode 100644 profile/benchmarking/rake/parallel.rake delete mode 100644 profile/benchmarking/rake/single_doc.rake delete mode 100644 profile/benchmarking/rake/tasks.rake delete mode 100644 profile/benchmarking/single_doc.rb delete mode 100644 profile/benchmarking/summary.rb create mode 100644 profile/driver_bench.rb create mode 100644 profile/driver_bench/base.rb create mode 100644 profile/driver_bench/bson.rb create mode 100644 profile/driver_bench/bson/base.rb create mode 100644 profile/driver_bench/bson/decodable.rb create mode 100644 profile/driver_bench/bson/deep.rb create mode 100644 profile/driver_bench/bson/deep/base.rb create mode 100644 profile/driver_bench/bson/deep/decoding.rb create mode 100644 profile/driver_bench/bson/deep/encoding.rb create mode 100644 profile/driver_bench/bson/encodable.rb create mode 100644 profile/driver_bench/bson/flat.rb create mode 100644 profile/driver_bench/bson/flat/base.rb create mode 100644 profile/driver_bench/bson/flat/decoding.rb create mode 100644 profile/driver_bench/bson/flat/encoding.rb create mode 100644 profile/driver_bench/bson/full.rb create mode 100644 profile/driver_bench/bson/full/base.rb create mode 100644 profile/driver_bench/bson/full/decoding.rb create mode 100644 profile/driver_bench/bson/full/encoding.rb create mode 100644 profile/driver_bench/multi_doc.rb create mode 100644 profile/driver_bench/multi_doc/base.rb create mode 100644 profile/driver_bench/multi_doc/bulk_insert.rb create mode 100644 profile/driver_bench/multi_doc/bulk_insert/base.rb create mode 100644 profile/driver_bench/multi_doc/bulk_insert/large_doc.rb create mode 100644 profile/driver_bench/multi_doc/bulk_insert/small_doc.rb create mode 100644 profile/driver_bench/multi_doc/find_many.rb create mode 100644 profile/driver_bench/multi_doc/grid_fs.rb create mode 100644 profile/driver_bench/multi_doc/grid_fs/base.rb create mode 100644 profile/driver_bench/multi_doc/grid_fs/download.rb create mode 100644 profile/driver_bench/multi_doc/grid_fs/upload.rb create mode 100644 profile/driver_bench/parallel.rb create mode 100644 profile/driver_bench/parallel/base.rb create mode 100644 profile/driver_bench/parallel/counter.rb create mode 100644 profile/driver_bench/parallel/dispatcher.rb create mode 100644 profile/driver_bench/parallel/gridfs.rb create mode 100644 profile/driver_bench/parallel/gridfs/base.rb create mode 100644 profile/driver_bench/parallel/gridfs/download.rb create mode 100644 profile/driver_bench/parallel/gridfs/upload.rb create mode 100644 profile/driver_bench/parallel/ldjson.rb create mode 100644 profile/driver_bench/parallel/ldjson/base.rb create mode 100644 profile/driver_bench/parallel/ldjson/export.rb create mode 100644 profile/driver_bench/parallel/ldjson/import.rb rename profile/{benchmarking => driver_bench}/percentiles.rb (97%) create mode 100644 profile/driver_bench/rake/tasks.rake create mode 100644 profile/driver_bench/single_doc.rb create mode 100644 profile/driver_bench/single_doc/base.rb create mode 100644 profile/driver_bench/single_doc/find_one_by_id.rb create mode 100644 profile/driver_bench/single_doc/insert_one.rb create mode 100644 profile/driver_bench/single_doc/insert_one/base.rb create mode 100644 profile/driver_bench/single_doc/insert_one/large_doc.rb create mode 100644 profile/driver_bench/single_doc/insert_one/small_doc.rb create mode 100644 profile/driver_bench/single_doc/run_command.rb create mode 100644 profile/driver_bench/suite.rb delete mode 100644 profile/profile.rb diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 934829aae4..3b709daa64 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -377,6 +377,19 @@ functions: set -x .evergreen/test-on-docker -d ${os} MONGODB_VERSION=${mongodb-version} TOPOLOGY=${topology} RVM_RUBY=${ruby} -s .evergreen/run-tests.sh TEST_CMD=true ${PRELOAD_ARG} + "run benchmarks": + - command: shell.exec + type: test + params: + shell: bash + working_dir: "src" + script: | + ${PREPARE_SHELL} + TEST_CMD="bundle exec rake driver_bench" PERFORMANCE_RESULTS_FILE="$PROJECT_DIRECTORY/perf.json" .evergreen/run-tests.sh + - command: perf.send + params: + file: "${PROJECT_DIRECTORY}/perf.json" + "run tests": - command: shell.exec type: test @@ -665,6 +678,9 @@ tasks: - name: "test-mlaunch" commands: - func: "run tests" + - name: "driver-bench" + commands: + - func: "run benchmarks" - name: "test-via-docker" commands: - func: "run tests via docker" @@ -1215,6 +1231,16 @@ axes: USE_PROXY_SERVERLESS: 1 buildvariants: + - matrix_name: DriverBench + matrix_spec: + ruby: "ruby-3.2" + mongodb-version: latest + topology: standalone + run_on: rhel80-large + display_name: DriverBench + tasks: + - name: "driver-bench" + - matrix_name: "auth/ssl" matrix_spec: auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index 54eee122a1..ec80952381 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -374,6 +374,19 @@ functions: set -x .evergreen/test-on-docker -d ${os} MONGODB_VERSION=${mongodb-version} TOPOLOGY=${topology} RVM_RUBY=${ruby} -s .evergreen/run-tests.sh TEST_CMD=true ${PRELOAD_ARG} + "run benchmarks": + - command: shell.exec + type: test + params: + shell: bash + working_dir: "src" + script: | + ${PREPARE_SHELL} + TEST_CMD="bundle exec rake driver_bench" PERFORMANCE_RESULTS_FILE="$PROJECT_DIRECTORY/perf.json" .evergreen/run-tests.sh + - command: perf.send + params: + file: "${PROJECT_DIRECTORY}/perf.json" + "run tests": - command: shell.exec type: test @@ -662,6 +675,9 @@ tasks: - name: "test-mlaunch" commands: - func: "run tests" + - name: "driver-bench" + commands: + - func: "run benchmarks" - name: "test-via-docker" commands: - func: "run tests via docker" diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index 1964946d4c..dcca0c673e 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -41,6 +41,16 @@ %> buildvariants: + - matrix_name: DriverBench + matrix_spec: + ruby: <%= latest_ruby %> + mongodb-version: latest + topology: standalone + run_on: rhel80-large + display_name: DriverBench + tasks: + - name: "driver-bench" + - matrix_name: "auth/ssl" matrix_spec: auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] diff --git a/.gitignore b/.gitignore index f1acca9738..309742c33c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ gemfiles/*.gemfile.lock .env.private* .env build -profile/benchmarking/data +profile/data secrets-export.sh secrets-expansion.yml atlas-expansion.yml diff --git a/Rakefile b/Rakefile index 4a4458070d..627e39389a 100644 --- a/Rakefile +++ b/Rakefile @@ -119,6 +119,32 @@ end task :release => ['release:check_private_key', 'release:do'] +desc 'Build and validate the evergreen config' +task eg: %w[ eg:build eg:validate ] + +# 'eg' == 'evergreen', but evergreen is too many letters for convenience +namespace :eg do + desc 'Builds the .evergreen/config.yml file from the templates' + task :build do + ruby '.evergreen/update-evergreen-configs' + end + + desc 'Validates the .evergreen/config.yml file' + task :validate do + system 'evergreen validate --project mongo-ruby-driver .evergreen/config.yml' + end + + desc 'Updates the evergreen executable to the latest available version' + task :update do + system 'evergreen get-update --install' + end + + desc 'Runs the current branch as an evergreen patch' + task :patch do + system 'evergreen patch --uncommitted --project mongo-ruby-driver --browse --auto-description --yes' + end +end + desc "Generate all documentation" task :docs => 'docs:yard' @@ -131,4 +157,4 @@ namespace :docs do end end -load 'profile/benchmarking/rake/tasks.rake' +load 'profile/driver_bench/rake/tasks.rake' diff --git a/lib/mongo/grid/fs_bucket.rb b/lib/mongo/grid/fs_bucket.rb index 6cb732efc2..8d723cdf55 100644 --- a/lib/mongo/grid/fs_bucket.rb +++ b/lib/mongo/grid/fs_bucket.rb @@ -484,6 +484,12 @@ def write_concern end end + # Drop the collections that implement this bucket. + def drop + files_collection.drop + chunks_collection.drop + end + private # @param [ Hash ] opts The options. diff --git a/profile/benchmark_21.rb b/profile/benchmark_21.rb deleted file mode 100644 index e03c16d565..0000000000 --- a/profile/benchmark_21.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -require 'mongo' -require 'benchmark' - -Mongo::Logger.level = Logger::INFO - -client = Mongo::Client.new( - [ '127.0.0.1:27017' ], - database: 'ruby-driver', - user: 'root-user', - password: 'password', - auth_source: 'admin' -) - -collection = client[:test] - -Benchmark.bm do |bm| - - small_documents = 50000.times.map do |i| - { name: 'user', index: i, embedded: [{ n: i }] } - end - - bm.report('Mongo::Collection#insert_many') do - collection.insert_many(small_documents) - end - - bm.report('Mongo::Cursor#each') do - collection.find.each do |document| - end - end - - bm.report('Mongo::Collection::View#update_many') do - collection.find(name: 'user').update_many({ '$set' => { name: 'user_modified' }}) - end - - bm.report('Mongo::Collection::View#delete_many') do - collection.find(name: 'user_modified').delete_many - end - - large_documents = 50000.times.map do |i| - { name: 'user', index: i, embedded: 100.times.map{ |x| { n: x } }} - end - - bm.report('Mongo::Collection#insert_many') do - collection.insert_many(large_documents) - end - - bm.report('Mongo::Cursor#each') do - collection.find.each do |document| - end - end - - bm.report('Mongo::Collection::View#update_many') do - collection.find(name: 'user').update_many({ '$set' => { name: 'user_modified' }}) - end - - bm.report('Mongo::Collection::View#delete_many') do - collection.find(name: 'user_modified').delete_many - end -end diff --git a/profile/benchmarking.rb b/profile/benchmarking.rb deleted file mode 100644 index 08f6b20423..0000000000 --- a/profile/benchmarking.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -# Copyright (C) 2015-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'benchmark' -require_relative 'benchmarking/helper' -require_relative 'benchmarking/bson' -require_relative 'benchmarking/single_doc' -require_relative 'benchmarking/multi_doc' -require_relative 'benchmarking/parallel' - -module Mongo - # Module with all functionality for running driver benchmark tests. - # - # @since 2.2.3 - module Benchmarking - extend self - - # @return [ String ] Path to Benchmarking test files. - DATA_PATH = [ __dir__, 'benchmarking', 'data' ].join('/').freeze - - # @return [ String ] The file containing the single tweet document. - TWEET_DOCUMENT_FILE = [ DATA_PATH, 'TWEET.json' ].join('/').freeze - - # @return [ String ] The file containing the single small document. - SMALL_DOCUMENT_FILE = [ DATA_PATH, 'SMALL_DOC.json' ].join('/').freeze - - # @return [ String ] The file containing the single large document. - LARGE_DOCUMENT_FILE = [ DATA_PATH, 'LARGE_DOC.json' ].join('/').freeze - - # @return [ String ] The file to upload when testing GridFS. - GRIDFS_FILE = [ DATA_PATH, 'GRIDFS_LARGE' ].join('/').freeze - - # @return [ String ] The file path and base name for the LDJSON files. - LDJSON_FILE_BASE = [ DATA_PATH, 'LDJSON_MULTI', 'LDJSON' ].join('/').freeze - - # @return [ String ] The file path and base name for the emitted LDJSON files. - LDJSON_FILE_OUTPUT_BASE = [ DATA_PATH, 'LDJSON_MULTI', 'output', 'LDJSON' ].join('/').freeze - - # @return [ String ] The file path and base name for the GRIDFS files to upload. - GRIDFS_MULTI_BASE = [ DATA_PATH, 'GRIDFS_MULTI', 'file' ].join('/').freeze - - # @return [ String ] The file path and base name for the emitted GRIDFS downloaded files. - GRIDFS_MULTI_OUTPUT_BASE = [ DATA_PATH, 'GRIDFS_MULTI', 'output', 'file-output' ].join('/').freeze - - # @return [ Integer ] The number of test repetitions. - TEST_REPETITIONS = 100 - - # Convenience helper for loading the single tweet document. - # - # @return [ Hash ] a single parsed JSON document - def tweet_document - Benchmarking.load_file(TWEET_DOCUMENT_FILE).first - end - - # Convenience helper for loading the single small document. - # - # @return [ Hash ] a single parsed JSON document - def small_document - Benchmarking.load_file(SMALL_DOCUMENT_FILE).first - end - - # Convenience helper for loading the single large document. - # - # @return [ Hash ] a single parsed JSON document - def large_document - Benchmarking.load_file(LARGE_DOCUMENT_FILE).first - end - end -end diff --git a/profile/benchmarking/bson.rb b/profile/benchmarking/bson.rb deleted file mode 100644 index 415e58114e..0000000000 --- a/profile/benchmarking/bson.rb +++ /dev/null @@ -1,151 +0,0 @@ -# frozen_string_literal: true - -# Copyright (C) 2015-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require_relative 'percentiles' -require_relative 'summary' - -module Mongo - module Benchmarking - # These tests focus on BSON encoding and decoding; they are client-side only and - # do not involve any transmission of data to or from the server. - module BSON - extend self - - # Runs all of the benchmarks specified by the given mapping. - # - # @example Run a collection of benchmarks. - # Benchmarking::BSON.run_all( - # flat: %i[ encode decode ], - # deep: %i[ encode decode ], - # full: %i[ encode decode ] - # ) - # - # @return [ Hash ] a hash of the results for each benchmark - def run_all(map) - {}.tap do |results| - map.each do |type, actions| - results[type] = {} - - actions.each do |action| - results[type][action] = run(type, action) - end - end - end - end - - # As defined by the spec, the score for a given benchmark is the - # size of the task (in MB) divided by the median wall clock time. - # - # @param [ Symbol ] type the type of the task - # @param [ Mongo::Benchmarking::Percentiles ] percentiles the Percentiles - # object to query for the median time. - # @param [ Numeric ] scale the number of times the operation is performed - # per iteration, used to scale the task size. - # - # @return [ Numeric ] the score for the given task. - def score_for(type, percentiles, scale: 10_000) - task_size(type, scale) / percentiles[50] - end - - # Run a BSON benchmark test. - # - # @example Run a test. - # Benchmarking::BSON.run(:flat) - # - # @param [ Symbol ] type The type of test to run. - # @param [ :encode | :decode ] action The action to perform. - # - # @return [ Hash<:timings,:percentiles,:score> ] The test results for - # the requested benchmark. - def run(type, action) - timings = send(action, file_for(type)) - percentiles = Percentiles.new(timings) - score = score_for(type, percentiles) - - Summary.new(timings, percentiles, score) - end - - # Run an encoding BSON benchmark test. - # - # @example Run an encoding test. - # Benchmarking::BSON.encode(file_name) - # - # @param [ String ] file_name The name of the file with data for the test. - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Array ] The list of the results for each iteration - def encode(file_name) - data = Benchmarking.load_file(file_name) - document = ::BSON::Document.new(data.first) - - Benchmarking.benchmark do - 10_000.times { document.to_bson } - end - end - - # Run a decoding BSON benchmark test. - # - # @example Run an decoding test. - # Benchmarking::BSON.decode(file_name) - # - # @param [ String ] file_name The name of the file with data for the test. - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Array ] The list of the results for each iteration - def decode(file_name) - data = Benchmarking.load_file(file_name) - buffer = ::BSON::Document.new(data.first).to_bson - - Benchmarking.benchmark do - 10_000.times do - ::BSON::Document.from_bson(buffer) - buffer.rewind! - end - end - end - - private - - # The path to the source file for the given task type. - # - # @param [ Symbol ] type the task type - # - # @return [ String ] the path to the source file. - def file_for(type) - File.join(Benchmarking::DATA_PATH, "#{type}_bson.json") - end - - # As defined by the spec, the size of a BSON task is the size of the - # file, multipled by the scale (the number of times the file is processed - # per iteration), divided by a million. - # - # "the dataset size for a task is the size of the single-document source - # file...times 10,000 operations" - # - # "Each task will have defined for it an associated size in - # megabytes (MB)" - # - # @param [ Symbol ] type the type of the task - # @param [ Numeric ] scale the number of times the operation is performed - # per iteration (e.g. 10,000) - # - # @return [ Numeric ] the score for the task, reported in MB - def task_size(type, scale) - File.size(file_for(type)) * scale / 1_000_000.0 - end - end - end -end diff --git a/profile/benchmarking/helper.rb b/profile/benchmarking/helper.rb deleted file mode 100644 index 70d4e34312..0000000000 --- a/profile/benchmarking/helper.rb +++ /dev/null @@ -1,164 +0,0 @@ -# frozen_string_literal: true - -module Mongo - # Helper functions used by benchmarking tasks - module Benchmarking - extend self - - # Load a json file and represent each document as a Hash. - # - # @example Load a file. - # Benchmarking.load_file(file_name) - # - # @param [ String ] The file name. - # - # @return [ Array ] A list of extended-json documents. - # - # @since 2.2.3 - def load_file(file_name) - File.open(file_name, 'r') do |f| - f.each_line.collect do |line| - parse_json(line) - end - end - end - - # Load a json document as a Hash and convert BSON-specific types. - # Replace the _id field as an BSON::ObjectId if it's represented as '$oid'. - # - # @example Parse a json document. - # Benchmarking.parse_json(document) - # - # @param [ Hash ] The json document. - # - # @return [ Hash ] An extended-json document. - # - # @since 2.2.3 - def parse_json(document) - JSON.parse(document).tap do |doc| - doc['_id'] = ::BSON::ObjectId.from_string(doc['_id']['$oid']) if doc['_id'] && doc['_id']['$oid'] - end - end - - # The spec requires that most benchmarks use a variable number of - # iterations, defined as follows: - # - # * iterations should loop for at least 1 minute cumulative execution - # time - # * iterations should stop after 100 iterations or 5 minutes cumulative - # execution time, whichever is shorter - # - # This method will yield once for each iteration. - # - # @param [ Integer ] max_iterations the maximum number of iterations to - # attempt (default: 100) - # @param [ Integer ] min_time the minimum number of seconds to spend - # iterating - # @param [ Integer ] max_time the maximum number of seconds to spend - # iterating. - # - # @return [ Array ] the timings for each iteration - def benchmark(max_iterations: Benchmarking::TEST_REPETITIONS, - min_time: 60, - max_time: 5 * 60, - progress: default_progress_callback, - &block) - progress ||= ->(state) {} # fallback to a no-op callback - progress[:start] - - [].tap do |results| - iteration_count = 0 - cumulative_time = 0 - - loop do - timing = without_gc { Benchmark.realtime(&block) } - progress[:step] - - iteration_count += 1 - cumulative_time += timing - results.push timing - - # always stop after the maximum time has elapsed, regardless of - # iteration count. - break if cumulative_time > max_time - - # otherwise, break if the minimum time has elapsed, and the maximum - # number of iterations have been reached. - break if cumulative_time >= min_time && iteration_count >= max_iterations - end - - progress[:end] - end - end - - # Formats and displays a report of the given results. - # - # @param [ Hash ] results the results of a benchmarking run. - # @param [ Integer ] indent how much the report should be indented. - # @param [ Array ] percentiles the percentile values to report - def report(results, indent: 0, percentiles: [ 10, 25, 50, 75, 90, 95, 98, 99 ]) - results.each do |key, value| - puts format('%*s%s:', indent, '', key) - - if value.respond_to?(:summary) - puts value.summary(indent + 2, percentiles) - else - report(value, indent: indent + 2, percentiles: percentiles) - end - end - end - - # Get the median of values in a list. - # - # @example Get the median. - # Benchmarking.median(values) - # - # @param [ Array ] values The values to get the median of. - # - # @return [ Numeric ] The median of the list. - def median(values) - i = (values.size / 2) - 1 - values.sort[i] - end - - # Runs a given block with GC disabled. - def without_gc - GC.disable - yield - ensure - GC.enable - end - - private - - # Returns the proc object (or nil) corresponding to the "PROGRESS" - # environment variable. - # - # @return [ Proc | nil ] the callback proc to use (or nil if none should - # be used) - def default_progress_callback - case ENV['PROGRESS'] - when '0', 'false', 'none' - nil - when nil, '1', 'true', 'minimal' - method(:minimal_progress_callback).to_proc - else - raise ArgumentError, "unsupported progress callback #{ENV['PROGRESS'].inspect}" - end - end - - # A minimal progress callback implementation, printing '|' when a benchmark - # starts and '.' for each iteration. - # - # @param [ :start | :step | :end ] state the current progress state - def minimal_progress_callback(state) - case state - when :start then print '|' - when :step then print '.' - when :end then puts - end - - $stdout.flush - end - end -end diff --git a/profile/benchmarking/multi_doc.rb b/profile/benchmarking/multi_doc.rb deleted file mode 100644 index 545d5691d1..0000000000 --- a/profile/benchmarking/multi_doc.rb +++ /dev/null @@ -1,182 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -# Copyright (C) 2015-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Benchmarking - - # Multi-doc benchmarks focus on multiple-document read and write operations. - # They are designed to give insight into the efficiency of the driver's implementation - # of bulk/batch operations such as bulk writes and cursor reads. - # - # @since 2.2.3 - module MultiDoc - - extend self - - # Run a multi-document benchmark test. - # - # @example Run a test. - # Benchmarking::MultiDoc.run(:find_many) - # - # @param [ Symbol ] type The type of test to run. - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The test results. - # - # @since 2.2.3 - def run(type, repetitions = Benchmarking::TEST_REPETITIONS) - Mongo::Logger.logger.level = ::Logger::WARN - puts "#{type} : #{send(type, repetitions)}" - end - - # Test finding many documents. - # - # @example Test sending a find and exhausting the cursor. - # Benchmarking::MultiDoc.find_many(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def find_many(repetitions) - client.database.drop - doc = Benchmarking.tweet_document - - 10_000.times do |i| - collection.insert_one(doc) - end - - results = repetitions.times.collect do - Benchmark.realtime do - collection.find.to_a - end - end - client.database.drop - Benchmarking.median(results) - end - - # Test doing a bulk insert of small documents. - # - # @example Test bulk insert of small documents. - # Benchmarking::MultiDoc.bulk_insert_small(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def bulk_insert_small(repetitions) - bulk_insert(repetitions, [Benchmarking.small_document] * 10_000) - end - - # Test doing a bulk insert of large documents. - # - # @example Test bulk insert of large documents. - # Benchmarking::MultiDoc.bulk_insert_large(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def bulk_insert_large(repetitions) - bulk_insert(repetitions, [Benchmarking.large_document] * 10) - end - - # Test uploading to GridFS. - # - # @example Test uploading to GridFS. - # Benchmarking::MultiDoc.gridfs_upload(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def gridfs_upload(repetitions) - client.database.drop - create_collection - fs = client.with(write_concern: { w: 1 }).database.fs(write_concern: { w: 1}) - - s = StringIO.new('a') - fs.upload_from_stream('create-indices.test', s) - - file = File.open(GRIDFS_FILE) - - results = repetitions.times.collect do - file.rewind - Benchmark.realtime do - fs.upload_from_stream('GRIDFS_LARGE', file) - end - end - Benchmarking.median(results) - end - - # Test downloading from GridFS. - # - # @example Test downloading from GridFS. - # Benchmarking::MultiDoc.gridfs_download(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def gridfs_download(repetitions = Benchmarking::TEST_REPETITIONS) - client.database.drop - create_collection - fs = client.with(write_concern: { w: 1 }).database.fs(write_concern: { w: 1}) - - file_id = fs.upload_from_stream('gridfstest', File.open(GRIDFS_FILE)) - io = StringIO.new - - results = repetitions.times.collect do - io.rewind - Benchmark.realtime do - fs.download_to_stream(file_id, io) - end - end - Benchmarking.median(results) - end - - private - - def bulk_insert(repetitions, docs) - client.database.drop - create_collection - - results = repetitions.times.collect do - Benchmark.realtime do - collection.insert_many(docs) - end - end - client.database.drop - Benchmarking.median(results) - end - - def client - @client ||= Mongo::Client.new(["localhost:27017"], database: 'perftest', monitoring: false) - end - - def collection - @collection ||= begin; client[:corpus].tap { |coll| coll.create }; rescue Error::OperationFailure; client[:corpus]; end - end - alias :create_collection :collection - end - end -end diff --git a/profile/benchmarking/parallel.rb b/profile/benchmarking/parallel.rb deleted file mode 100644 index 92a92132ae..0000000000 --- a/profile/benchmarking/parallel.rb +++ /dev/null @@ -1,244 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -# Copyright (C) 2015-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#require 'ruby-prof' - -module Mongo - module Benchmarking - - # Parallel tests simulate ETL operations from disk to database or vice-versa. - # They are designed to be implemented using a language's preferred approach to - # concurrency and thus stress how drivers handle concurrency. - # These intentionally involve overhead above and beyond the driver itself to - # simulate the sort of "real-world" pressures that a drivers would be under - # during concurrent operation. - # - # @since 2.2.3 - module Parallel - - extend self - - # Run a parallel benchmark test. - # - # @example Run a test. - # Benchmarking::Parallel.run(:import) - # - # @param [ Symbol ] type The type of test to run. - # - # @return [ Numeric ] The test results. - # - # @since 2.2.3 - def run(type) - Mongo::Logger.logger.level = ::Logger::WARN - type = type.to_s + '_jruby' if BSON::Environment.jruby? - puts "#{type} : #{send(type)}" - end - - # Test concurrently importing documents from a set of files. - # Using JRuby. - # - # @example Testing concurrently importing files using JRuby. - # Benchmarking::Parallel.import_jruby - # - # @return [ Numeric ] The test result. - # - # @since 2.2.3 - def import_jruby - #require 'jrjackson' - client.database.drop - create_collection - files = [*1..100].collect { |i| "#{LDJSON_FILE_BASE}#{i.to_s.rjust(3, "0")}.txt" } - - threads = [] - result = Benchmark.realtime do - 4.times do |i| - threads << Thread.new do - 25.times do |j| - docs = File.open(files[10 * i + j]).collect { |document| JSON.parse(document) } - #docs = File.open(files[10 * i + j]).collect { |document| JrJackson::Json.load(document) } - collection.insert_many(docs) - end - end - end - threads.collect { |t| t.join } - end - client.database.drop - result - end - - # Test concurrently importing documents from a set of files. - # - # @example Testing concurrently importing files. - # Benchmarking::Parallel.import - # - # @return [ Numeric ] The test result. - # - # @since 2.2.3 - def import - require 'yajl/json_gem' - require 'celluloid' - - Mongo::Collection.send(:include, Celluloid) - Celluloid.boot - - client.database.drop - create_collection - files = [*0..99].collect { |i| "#{LDJSON_FILE_BASE}#{i.to_s.rjust(3, "0")}.txt" } - - result = Benchmark.realtime do - Benchmarking::TEST_REPETITIONS.times do |i| - docs = File.open(files[i]).map{ |document| JSON.parse(document) } - collection.async.insert_many(docs) - end - end - client.database.drop - result - end - - # Test concurrently exporting documents from a collection to a set of files. - # - # @example Testing concurrently importing files. - # Benchmarking::Parallel.export - # - # @return [ Numeric ] The test result. - # - # @since 2.2.3 - def export - #require 'ruby-prof' - insert_files - files = [*1..Benchmarking::TEST_REPETITIONS].collect do |i| - name = "#{LDJSON_FILE_OUTPUT_BASE}#{i.to_s.rjust(3, "0")}.txt" - File.new(name, 'w') - end - #prof = nil - result = Benchmark.realtime do - Benchmarking::TEST_REPETITIONS.times do |i| - #prof = RubyProf.profile do - files[i].write(collection.find(_id: { '$gte' => (i * 5000), - '$lt' => (i+1) * 5000 }).to_a) - end - #end - end - client.database.drop - result - end - - # This benchmark tests driver performance uploading files from disk to GridFS. - # - # @example Test uploading files from disk to GridFS. - # Benchmarking::Parallel.gridfs_upload - # - # @return [ Numeric ] The test result. - # - # @since 2.2.3 - def gridfs_upload - n = 50 - client.database.drop - fs = client.database.fs - - files = [*0...n].collect do |i| - name = "#{GRIDFS_MULTI_BASE}#{i}.txt" - { - file: File.open(name, 'r'), - name: File.basename(name) - } - end - - s = StringIO.new('a') - fs.upload_from_stream('create-indices.test', s) - - Benchmark.realtime do - n.times do |i| - fs.upload_from_stream(files[i][:name], files[i][:file]) - end - end - end - alias :gridfs_upload_jruby :gridfs_upload - - - # This benchmark tests driver performance downloading files from GridFS to disk. - # - # @example Test downloading files from GridFS to disk. - # Benchmarking::Parallel.gridfs_download - # - # @return [ Numeric ] The test result. - # - # @since 2.2.3 - def gridfs_download - n_files = 50 - n_threads = BSON::Environment.jruby? ? 4 : 2 - threads = [] - client.database.drop - fs = client.database.fs - - file_info = [*0...n_files].collect do |i| - name = "#{GRIDFS_MULTI_BASE}#{i}.txt" - { - _id: fs.upload_from_stream(name, File.open(name)), - output_name: "#{GRIDFS_MULTI_OUTPUT_BASE}#{i}.txt" - } - end.freeze - - reps = n_files/n_threads.freeze - Benchmark.realtime do - n_threads.times do |i| - threads << Thread.new do - reps.times do |j| - index = i * reps + j - fs.download_to_stream(file_info[index][:_id], File.open(file_info[index][:output_name], "w")) - end - end - end - threads.collect(&:value) - end - end - alias :gridfs_download_jruby :gridfs_download - - private - - def insert_files - require 'yajl/json_gem' - require 'celluloid' - - Mongo::Collection.send(:include, Celluloid) - - client.database.drop - create_collection - files = [*1..Benchmarking::TEST_REPETITIONS].collect do |i| - File.open("#{LDJSON_FILE_BASE}#{i.to_s.rjust(3, "0")}.txt") - end - - Benchmarking::TEST_REPETITIONS.times do |i| - docs = files[i].each_with_index.collect do |document, offset| - JSON.parse(document).merge(_id: i * 5000 + offset) - end - collection.async.insert_many(docs) - end - puts "Imported #{Benchmarking::TEST_REPETITIONS} files, #{collection.count} documents." - end - - def client - @client ||= Mongo::Client.new(["localhost:27017"], database: 'perftest', monitoring: false) - end - - def collection - @collection ||= begin; client[:corpus].tap { |coll| coll.create }; rescue Error::OperationFailure; client[:corpus]; end - end - alias :create_collection :collection - end - end -end diff --git a/profile/benchmarking/rake/bson.rake b/profile/benchmarking/rake/bson.rake deleted file mode 100644 index 4d5bdb1c04..0000000000 --- a/profile/benchmarking/rake/bson.rake +++ /dev/null @@ -1,122 +0,0 @@ -# frozen_string_literal: true - -# rubocop:disable Layout/FirstHashElementIndentation - -desc 'Run the full BSON benchmarking suite' -task :bson do - puts 'BSON BENCHMARK SUITE' - Mongo::Benchmarking.report({ - bson: Mongo::Benchmarking::BSON.run_all( - flat: %i[ encode decode ], - deep: %i[ encode decode ], - full: %i[ encode decode ] - ) - }) -end - -namespace :bson do # rubocop:disable Metrics/BlockLength - # a convenience task for running all of the bson benchmark tasks; this is - # only useful for testing that they all work. - task test: %w[ - bson - bson:flat bson:flat:encode bson:flat:decode - bson:deep bson:deep:encode bson:deep:decode - bson:full bson:full:encode bson:full:decode - ] - - desc 'Learn how to run the BSON benchmarks' - task :help do - puts <<~HELP - The BSON micro benchmarks require a set of data files that are stored in - the specifications repository, here: - - https://github.com/mongodb/specifications/tree/master/source/benchmarking/data - - Download the `extended_bson.tgz` file and extract its contents. It should - contain a single folder (`extended_bson`) with several files in it. Move - those files to: - - #{Mongo::Benchmarking::DATA_PATH} - - Once there, you may run any of the BSON benchmarking tasks: - - $ rake benchmark:bson:flat:encode - - Tasks may be run in aggregate, as well, by specifying the namespace - directly: - - $ rake benchmark:bson:flat # runs all flat BSON benchmarks - $ rake benchmark:bson:deep # runs all deep BSON benchmarks - $ rake benchmark:bson:full # runs all full BSON benchmarks - # rake benchmark:bson # runs all BSON benchmarks - HELP - end - - desc 'Run the `flat` BSON benchmarking suite' - task :flat do - puts 'BSON BENCHMARK :: FLAT' - Mongo::Benchmarking.report({ - bson: Mongo::Benchmarking::BSON.run_all(flat: %i[ encode decode ]) - }) - end - - namespace :flat do - desc 'Run the `flat` encoding BSON benchmark' - task :encode do - puts 'BSON BENCHMARK :: FLAT :: ENCODE' - Mongo::Benchmarking.report({ bson: { flat: { encode: Mongo::Benchmarking::BSON.run(:flat, :encode) } } }) - end - - desc 'Run the `flat` decoding BSON benchmark' - task :decode do - puts 'BSON BENCHMARK :: FLAT :: DECODE' - Mongo::Benchmarking.report({ bson: { flat: { decode: Mongo::Benchmarking::BSON.run(:flat, :decode) } } }) - end - end - - desc 'Run the `deep` BSON benchmarking suite' - task :deep do - puts 'BSON BENCHMARK :: DEEP' - Mongo::Benchmarking.report({ - bson: Mongo::Benchmarking::BSON.run_all(deep: %i[ encode decode ]) - }) - end - - namespace :deep do - desc 'Run the `deep` encoding BSON benchmark' - task :encode do - puts 'BSON BENCHMARK :: DEEP :: ENCODE' - Mongo::Benchmarking.report({ bson: { deep: { encode: Mongo::Benchmarking::BSON.run(:deep, :encode) } } }) - end - - desc 'Run the `deep` decoding BSON benchmark' - task :decode do - puts 'BSON BENCHMARK :: DEEP :: DECODE' - Mongo::Benchmarking.report({ bson: { deep: { decode: Mongo::Benchmarking::BSON.run(:deep, :decode) } } }) - end - end - - desc 'Run the `full` BSON benchmarking suite' - task :full do - puts 'BSON BENCHMARK :: FULL' - Mongo::Benchmarking.report({ - bson: Mongo::Benchmarking::BSON.run_all({ full: %i[ encode decode ] }) - }) - end - - namespace :full do - desc 'Run the `full` encoding BSON benchmark' - task :encode do - puts 'BSON BENCHMARK :: FULL :: ENCODE' - Mongo::Benchmarking.report({ bson: { full: { encode: Mongo::Benchmarking::BSON.run(:full, :encode) } } }) - end - - desc 'Run the `full` decoding BSON benchmark' - task :decode do - puts 'BSON BENCHMARK :: FULL :: DECODE' - Mongo::Benchmarking.report({ bson: { full: { decode: Mongo::Benchmarking::BSON.run(:full, :decode) } } }) - end - end -end - -# rubocop:enable Layout/FirstHashElementIndentation diff --git a/profile/benchmarking/rake/multi_doc.rake b/profile/benchmarking/rake/multi_doc.rake deleted file mode 100644 index 86c190ef1f..0000000000 --- a/profile/benchmarking/rake/multi_doc.rake +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -namespace :multi_doc do - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called TWEET.json. - task :find_many do - puts 'MULTI DOCUMENT BENCHMARK :: FIND MANY' - Mongo::Benchmarking::MultiDoc.run(:find_many) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called SMALL_DOC.json. - task :bulk_insert_small do - puts 'MULTI DOCUMENT BENCHMARK :: BULK INSERT SMALL' - Mongo::Benchmarking::MultiDoc.run(:bulk_insert_small) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called LARGE_DOC.json. - task :bulk_insert_large do - puts 'MULTI DOCUMENT BENCHMARK :: BULK INSERT LARGE' - Mongo::Benchmarking::MultiDoc.run(:bulk_insert_large) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called GRIDFS_LARGE. - task :gridfs_upload do - puts 'MULTI DOCUMENT BENCHMARK :: GRIDFS UPLOAD' - Mongo::Benchmarking::MultiDoc.run(:gridfs_upload) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called GRIDFS_LARGE. - task :gridfs_download do - puts 'MULTI DOCUMENT BENCHMARK :: GRIDFS DOWNLOAD' - Mongo::Benchmarking::MultiDoc.run(:gridfs_download) - end -end diff --git a/profile/benchmarking/rake/parallel.rake b/profile/benchmarking/rake/parallel.rake deleted file mode 100644 index 98752e231e..0000000000 --- a/profile/benchmarking/rake/parallel.rake +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -namespace :parallel do - # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called LDJSON_MULTI, - # with the files used in this task. - task :import do - puts 'PARALLEL ETL BENCHMARK :: IMPORT' - Mongo::Benchmarking::Parallel.run(:import) - end - - # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called LDJSON_MULTI, - # with the files used in this task. - # Requirement: Another directory in '#{Mongo::Benchmarking::DATA_PATH}/LDJSON_MULTI' - # called 'output'. - task :export do - puts 'PARALLEL ETL BENCHMARK :: EXPORT' - Mongo::Benchmarking::Parallel.run(:export) - end - - # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called GRIDFS_MULTI, - # with the files used in this task. - task :gridfs_upload do - puts 'PARALLEL ETL BENCHMARK :: GRIDFS UPLOAD' - Mongo::Benchmarking::Parallel.run(:gridfs_upload) - end - - # Requirement: A directory in Mongo::Benchmarking::DATA_PATH, called GRIDFS_MULTI, - # with the files used in this task. - # Requirement: Another directory in '#{Mongo::Benchmarking::DATA_PATH}/GRIDFS_MULTI' - # called 'output'. - task :gridfs_download do - puts 'PARALLEL ETL BENCHMARK :: GRIDFS DOWNLOAD' - Mongo::Benchmarking::Parallel.run(:gridfs_download) - end -end diff --git a/profile/benchmarking/rake/single_doc.rake b/profile/benchmarking/rake/single_doc.rake deleted file mode 100644 index 803e28d593..0000000000 --- a/profile/benchmarking/rake/single_doc.rake +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -namespace :single_doc do - task :command do - puts 'SINGLE DOC BENCHMARK :: COMMAND' - Mongo::Benchmarking::SingleDoc.run(:command) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called TWEET.json. - task :find_one do - puts 'SINGLE DOC BENCHMARK :: FIND ONE BY ID' - Mongo::Benchmarking::SingleDoc.run(:find_one) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called SMALL_DOC.json. - task :insert_one_small do - puts 'SINGLE DOC BENCHMARK :: INSERT ONE SMALL DOCUMENT' - Mongo::Benchmarking::SingleDoc.run(:insert_one_small) - end - - # Requirement: A file in Mongo::Benchmarking::DATA_PATH, called LARGE_DOC.json. - task :insert_one_large do - puts 'SINGLE DOC BENCHMARK :: INSERT ONE LARGE DOCUMENT' - Mongo::Benchmarking::SingleDoc.run(:insert_one_large) - end -end diff --git a/profile/benchmarking/rake/tasks.rake b/profile/benchmarking/rake/tasks.rake deleted file mode 100644 index 7feae8d2ab..0000000000 --- a/profile/benchmarking/rake/tasks.rake +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../benchmarking' - -# Some require data files, available from the drivers team. -# See the comments above each task for details. -namespace :benchmark do - %w[ bson single_doc multi_doc parallel ].each do |group| - load File.join(__dir__, "#{group}.rake") - end -end diff --git a/profile/benchmarking/single_doc.rb b/profile/benchmarking/single_doc.rb deleted file mode 100644 index a66635c542..0000000000 --- a/profile/benchmarking/single_doc.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -# Copyright (C) 2015-2020 MongoDB Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -module Mongo - module Benchmarking - - # Single-doc tests focus on single-document read and write operations. - # They are designed to give insights into the efficiency of the driver's - # implementation of the basic wire protocol. - # - # @since 2.2.3 - module SingleDoc - - extend self - - # Run a Single Document benchmark test. - # - # @example Run a test. - # Benchmarking::SingleDoc.run(:command) - # - # @param [ Symbol ] type The type of test to run. - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numberic ] The test results. - # - # @since 2.2.3 - def run(type, repetitions = Benchmarking::TEST_REPETITIONS) - Mongo::Logger.logger.level = ::Logger::WARN - puts "#{type} : #{send(type, repetitions)}" - end - - # Test sending a command to the server. - # - # @example Test sending an ismaster command. - # Benchmarking::SingleDoc.command(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @since 2.2.3 - def command(repetitions) - monitor = client.cluster.servers.first.monitor - results = repetitions.times.collect do - Benchmark.realtime do - 10_000.times do - client.database.command(hello: true) - end - end - end - Benchmarking.median(results) - end - - # Test sending find one by id. - # - # @example Test sending a find. - # Benchmarking::SingleDoc.find_one(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def find_one(repetitions) - client.database.drop - doc = Benchmarking.tweet_document - - 10_000.times do |i| - doc[:_id] = i - collection.insert_one(doc) - end - - results = repetitions.times.collect do - Benchmark.realtime do - 10_000.times do |i| - collection.find({ _id: i }, limit: -1).first - end - end - end - Benchmarking.median(results) - end - - # Test inserting a large document. - # - # @example Test inserting a large document. - # Benchmarking::SingleDoc.insert_one_large(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def insert_one_large(repetitions) - insert_one(repetitions, 10, Benchmarking.large_document) - end - - # Test inserting a small document. - # - # @example Test inserting a small document. - # Benchmarking::SingleDoc.insert_one_small(10) - # - # @param [ Integer ] repetitions The number of test repetitions. - # - # @return [ Numeric ] The median of the results. - # - # @since 2.2.3 - def insert_one_small(repetitions) - insert_one(repetitions, 10_000, Benchmarking.small_document) - end - - private - - def insert_one(repetitions, do_repetitions, doc) - client.database.drop - create_collection - - results = repetitions.times.collect do - Benchmark.realtime do - do_repetitions.times do - collection.insert_one(doc) - end - end - end - Benchmarking.median(results) - end - - def client - @client ||= Mongo::Client.new(["localhost:27017"], database: 'perftest', monitoring: false) - end - - def collection - @collection ||= begin; client[:corpus].tap { |coll| coll.create }; rescue Error::OperationFailure; client[:corpus]; end - end - alias :create_collection :collection - end - end -end diff --git a/profile/benchmarking/summary.rb b/profile/benchmarking/summary.rb deleted file mode 100644 index 93fddf5435..0000000000 --- a/profile/benchmarking/summary.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module Mongo - module Benchmarking - # A utility class for encapsulating the summary information for a - # benchmark, including behaviors for reporting on the summary. - class Summary - # @return [ Array ] the timings of each iteration in the - # benchmark - attr_reader :timings - - # @return [ Percentiles ] the percentiles object for querying the - # timing at a given percentile value. - attr_reader :percentiles - - # @return [ Numeric ] the composite score for the benchmark - attr_reader :score - - # Construct a new Summary object with the given timings, percentiles, - # and score. - # - # @param [ Array ] timings the timings of each iteration in the - # benchmark - # @param [ Percentiles ] percentiles the percentiles object for querying - # the timing at a given percentile value - # @param [ Numeric ] score the composite score for the benchmark - def initialize(timings, percentiles, score) - @timings = timings - @percentiles = percentiles - @score = score - end - - # @return [ Numeric ] the median timing for the benchmark. - def median - percentiles[50] - end - - # Formats and displays the results of a single benchmark run. - # - # @param [ Integer ] indent how much the report should be indented - # @param [ Array ] points the percentile points to report - # - # @return [ String ] a YAML-formatted summary - def summary(indent, points) - [].tap do |lines| - lines << format('%*sscore: %g', indent, '', score) - lines << format('%*smedian: %g', indent, '', median) - lines << format('%*spercentiles:', indent, '') - points.each do |pct| - lines << format('%*s%g: %g', indent + 2, '', pct, percentiles[pct]) - end - end.join("\n") - end - end - end -end diff --git a/profile/driver_bench.rb b/profile/driver_bench.rb new file mode 100644 index 0000000000..f2ab14393d --- /dev/null +++ b/profile/driver_bench.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require 'driver_bench/suite' diff --git a/profile/driver_bench/base.rb b/profile/driver_bench/base.rb new file mode 100644 index 0000000000..e6a7bdb358 --- /dev/null +++ b/profile/driver_bench/base.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +require 'benchmark' +require 'mongo' + +require_relative 'percentiles' + +module Mongo + module DriverBench + # Base class for DriverBench profile benchmarking classes. + # + # @api private + class Base + # A convenience for setting and querying the benchmark's name + def self.bench_name(benchmark_name = nil) + @bench_name = benchmark_name if benchmark_name + @bench_name + end + + # Where to look for the data files + DATA_PATH = File.expand_path('../data/driver_bench', __dir__) + + # The maximum number of iterations to perform when executing the + # micro-benchmark. + attr_reader :max_iterations + + # The minimum number of seconds that the micro-benchmark must run, + # regardless of how many iterations it takes. + attr_reader :min_time + + # The maximum number of seconds that the micro-benchmark must run, + # regardless of how many iterations it takes. + attr_reader :max_time + + # The dataset to be used by the micro-benchmark. + attr_reader :dataset + + # The size of the dataset, computed per the spec, to be + # used for scoring the results. + attr_reader :dataset_size + + # Instantiate a new micro-benchmark class. + def initialize + @max_iterations = debug_mode? ? 10 : 100 + @min_time = debug_mode? ? 1 : 60 + @max_time = 300 # 5 minutes + end + + def debug_mode? + ENV['PERF_DEBUG'] + end + + # Runs the benchmark and returns the score. + # + # @return [ Hash ] the score and other + # attributes of the benchmark. + def run + timings = run_benchmark + percentiles = Percentiles.new(timings) + score = dataset_size / percentiles[50] / 1_000_000.0 + + { name: self.class.bench_name, + score: score, + percentiles: percentiles } + end + + private + + # Runs the micro-benchmark, and returns an array of timings, with one + # entry for each iteration of the benchmark. It may have fewer than + # max_iterations entries if it takes longer than max_time seconds, or + # more than max_iterations entries if it would take less than min_time + # seconds to run. + # + # @return [ Array ] the array of timings (in seconds) for + # each iteration. + # + # rubocop:disable Metrics/AbcSize + def run_benchmark + [].tap do |timings| + iteration_count = 0 + cumulative_time = 0 + + setup + + loop do + before_task + timing = consider_gc { Benchmark.realtime { debug_mode? ? sleep(0.1) : do_task } } + after_task + + iteration_count += 1 + cumulative_time += timing + timings.push timing + + # always stop after the maximum time has elapsed, regardless of + # iteration count. + break if cumulative_time > max_time + + # otherwise, break if the minimum time has elapsed, and the maximum + # number of iterations have been reached. + break if cumulative_time >= min_time && iteration_count >= max_iterations + end + + teardown + end + end + # rubocop:enable Metrics/AbcSize + + # Instantiate a new client. + def new_client(uri = ENV['MONGODB_URI']) + Mongo::Client.new(uri) + end + + # Takes care of garbage collection considerations before + # running the block. + # + # Set BENCHMARK_NO_GC environment variable to suppress GC during + # the core benchmark tasks; note that this may result in obscure issues + # due to memory pressures on larger benchmarks. + def consider_gc + GC.start + GC.disable if ENV['BENCHMARK_NO_GC'] + yield + ensure + GC.enable if ENV['BENCHMARK_NO_GC'] + end + + # By default, the file name is assumed to be relative to the + # DATA_PATH, unless the file name is an absolute path. + def path_to_file(file_name) + return file_name if file_name.start_with?('/') + + File.join(DATA_PATH, file_name) + end + + # Load a json file and represent each document as a Hash. + # + # @param [ String ] file_name The file name. + # + # @return [ Array ] A list of extended-json documents. + def load_file(file_name) + File.readlines(path_to_file(file_name)).map { |line| ::BSON::Document.new(parse_line(line)) } + end + + # Returns the size (in bytes) of the given file. + def size_of_file(file_name) + File.size(path_to_file(file_name)) + end + + # Load a json document as a Hash and convert BSON-specific types. + # Replace the _id field as an BSON::ObjectId if it's represented as '$oid'. + # + # @param [ String ] document The json document. + # + # @return [ Hash ] An extended-json document. + def parse_line(document) + JSON.parse(document).tap do |doc| + doc['_id'] = ::BSON::ObjectId.from_string(doc['_id']['$oid']) if doc['_id'] && doc['_id']['$oid'] + end + end + + # Executed at the start of the micro-benchmark. + def setup; end + + # Executed before each iteration of the benchmark. + def before_task; end + + # Smallest amount of code necessary to do the task, + # invoked once per iteration. + def do_task + raise NotImplementedError + end + + # Executed after each iteration of the benchmark. + def after_task; end + + # Executed at the end of the micro-benchmark. + def teardown; end + end + end +end diff --git a/profile/driver_bench/bson.rb b/profile/driver_bench/bson.rb new file mode 100644 index 0000000000..6da18affc4 --- /dev/null +++ b/profile/driver_bench/bson.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative 'bson/deep' +require_relative 'bson/flat' +require_relative 'bson/full' + +module Mongo + module DriverBench + module BSON + ALL = [ *Deep::ALL, *Flat::ALL, *Full::ALL ].freeze + + # BSONBench consists of all BSON micro-benchmarks + BENCH = ALL + end + end +end diff --git a/profile/driver_bench/bson/base.rb b/profile/driver_bench/bson/base.rb new file mode 100644 index 0000000000..f976a00498 --- /dev/null +++ b/profile/driver_bench/bson/base.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module BSON + # Abstract superclass for all BSON benchmarks. + # + # @api private + class Base < Mongo::DriverBench::Base + private + + # Common setup for these benchmarks. + def setup + # rubocop:disable Naming/MemoizedInstanceVariableName + @dataset ||= load_file(file_name).first + @dataset_size ||= size_of_file(file_name) * 10_000 + # rubocop:enable Naming/MemoizedInstanceVariableName + end + + # Returns the name of the file name that contains + # the dataset to use. + def file_name + raise NotImplementedError + end + end + end + end +end diff --git a/profile/driver_bench/bson/decodable.rb b/profile/driver_bench/bson/decodable.rb new file mode 100644 index 0000000000..84c1506acf --- /dev/null +++ b/profile/driver_bench/bson/decodable.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module BSON + # Common behavior for "decode" benchmarks. + # + # @api private + module Decodable + private + + # The buffer to decode for the test + attr_reader :buffer + + # Before executing the task itself. + def before_task + @buffer = ::BSON::Document.new(dataset).to_bson + end + + # The decode operation, performed 10k times. + def do_task + 10_000.times do + ::BSON::Document.from_bson(buffer) + buffer.rewind! + end + end + end + end + end +end diff --git a/profile/driver_bench/bson/deep.rb b/profile/driver_bench/bson/deep.rb new file mode 100644 index 0000000000..6beff6dbe7 --- /dev/null +++ b/profile/driver_bench/bson/deep.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'deep/encoding' +require_relative 'deep/decoding' + +module Mongo + module DriverBench + module BSON + module Deep + ALL = [ Encoding, Decoding ].freeze + end + end + end +end diff --git a/profile/driver_bench/bson/deep/base.rb b/profile/driver_bench/bson/deep/base.rb new file mode 100644 index 0000000000..350741da03 --- /dev/null +++ b/profile/driver_bench/bson/deep/base.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module BSON + module Deep + # Abstract superclass for deep BSON benchmarks. + # + # @api private + class Base < Mongo::DriverBench::BSON::Base + private + + # @return [ String ] the name of the file to use as the + # dataset for these benchmarks. + def file_name + 'extended_bson/deep_bson.json' + end + end + end + end + end +end diff --git a/profile/driver_bench/bson/deep/decoding.rb b/profile/driver_bench/bson/deep/decoding.rb new file mode 100644 index 0000000000..e830af1ed5 --- /dev/null +++ b/profile/driver_bench/bson/deep/decoding.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative '../decodable' + +module Mongo + module DriverBench + module BSON + module Deep + # "This benchmark tests driver performance decoding documents with + # deeply nested key/value pairs involving subdocuments, strings, + # integers, doubles and booleans." + # + # @api private + class Decoding < Mongo::DriverBench::BSON::Deep::Base + include Decodable + + bench_name 'Deep BSON Decoding' + end + end + end + end +end diff --git a/profile/driver_bench/bson/deep/encoding.rb b/profile/driver_bench/bson/deep/encoding.rb new file mode 100644 index 0000000000..86ceb3466a --- /dev/null +++ b/profile/driver_bench/bson/deep/encoding.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative '../encodable' + +module Mongo + module DriverBench + module BSON + module Deep + # "This benchmark tests driver performance encoding documents with + # deeply nested key/value pairs involving subdocuments, strings, + # integers, doubles and booleans." + # + # @api private + class Encoding < Mongo::DriverBench::BSON::Deep::Base + include Encodable + + bench_name 'Deep BSON Encoding' + end + end + end + end +end diff --git a/profile/driver_bench/bson/encodable.rb b/profile/driver_bench/bson/encodable.rb new file mode 100644 index 0000000000..55138fc868 --- /dev/null +++ b/profile/driver_bench/bson/encodable.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module BSON + # Common behavior for the "encode" benchmarks. + # + # @api private + module Encodable + private + + # The document to encode for the test + attr_reader :document + + # Before each task. + def before_task + @document = ::BSON::Document.new(dataset) + end + + # The encode operation itself, executed 10k times. + def do_task + 10_000.times { document.to_bson } + end + end + end + end +end diff --git a/profile/driver_bench/bson/flat.rb b/profile/driver_bench/bson/flat.rb new file mode 100644 index 0000000000..40ee68d4bd --- /dev/null +++ b/profile/driver_bench/bson/flat.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'flat/encoding' +require_relative 'flat/decoding' + +module Mongo + module DriverBench + module BSON + module Flat + ALL = [ Encoding, Decoding ].freeze + end + end + end +end diff --git a/profile/driver_bench/bson/flat/base.rb b/profile/driver_bench/bson/flat/base.rb new file mode 100644 index 0000000000..64e7caecc8 --- /dev/null +++ b/profile/driver_bench/bson/flat/base.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module BSON + module Flat + # Abstract superclass of flat BSON benchmarks. + # + # @api private + class Base < Mongo::DriverBench::BSON::Base + private + + # @return [ String ] the name of the file to use as the + # dataset for these benchmarks. + def file_name + 'extended_bson/flat_bson.json' + end + end + end + end + end +end diff --git a/profile/driver_bench/bson/flat/decoding.rb b/profile/driver_bench/bson/flat/decoding.rb new file mode 100644 index 0000000000..177e189c32 --- /dev/null +++ b/profile/driver_bench/bson/flat/decoding.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative '../decodable' + +module Mongo + module DriverBench + module BSON + module Flat + # "This benchmark tests driver performance decoding documents with top + # level key/value pairs involving the most commonly-used BSON types." + # + # @api private + class Decoding < Mongo::DriverBench::BSON::Flat::Base + include Decodable + + bench_name 'Flat BSON Decoding' + end + end + end + end +end diff --git a/profile/driver_bench/bson/flat/encoding.rb b/profile/driver_bench/bson/flat/encoding.rb new file mode 100644 index 0000000000..5f12d5c053 --- /dev/null +++ b/profile/driver_bench/bson/flat/encoding.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative '../encodable' + +module Mongo + module DriverBench + module BSON + module Flat + # "This benchmark tests driver performance encoding documents with top + # level key/value pairs involving the most commonly-used BSON types." + # + # @api private + class Encoding < Mongo::DriverBench::BSON::Flat::Base + include Encodable + + bench_name 'Flat BSON Encoding' + end + end + end + end +end diff --git a/profile/driver_bench/bson/full.rb b/profile/driver_bench/bson/full.rb new file mode 100644 index 0000000000..ee0259fcf6 --- /dev/null +++ b/profile/driver_bench/bson/full.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'full/encoding' +require_relative 'full/decoding' + +module Mongo + module DriverBench + module BSON + module Full + ALL = [ Encoding, Decoding ].freeze + end + end + end +end diff --git a/profile/driver_bench/bson/full/base.rb b/profile/driver_bench/bson/full/base.rb new file mode 100644 index 0000000000..0d67d48e46 --- /dev/null +++ b/profile/driver_bench/bson/full/base.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module BSON + module Full + # Abstract superclass for full BSON benchmarks. + # + # @api private + class Base < Mongo::DriverBench::BSON::Base + private + + # @return [ String ] the name of the file to use as the + # dataset for these benchmarks. + def file_name + 'extended_bson/full_bson.json' + end + end + end + end + end +end diff --git a/profile/driver_bench/bson/full/decoding.rb b/profile/driver_bench/bson/full/decoding.rb new file mode 100644 index 0000000000..199f505b96 --- /dev/null +++ b/profile/driver_bench/bson/full/decoding.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative '../decodable' + +module Mongo + module DriverBench + module BSON + module Full + # "This benchmark tests driver performance decoding documents with top + # level key/value pairs involving the full range of BSON types." + # + # @api private + class Decoding < Mongo::DriverBench::BSON::Full::Base + include Decodable + + bench_name 'Full BSON Decoding' + end + end + end + end +end diff --git a/profile/driver_bench/bson/full/encoding.rb b/profile/driver_bench/bson/full/encoding.rb new file mode 100644 index 0000000000..45b2a845a8 --- /dev/null +++ b/profile/driver_bench/bson/full/encoding.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative '../encodable' + +module Mongo + module DriverBench + module BSON + module Full + # "This benchmark tests driver performance encoding documents with top + # level key/value pairs involving the full range of BSON types." + # + # @api private + class Encoding < Mongo::DriverBench::BSON::Full::Base + include Encodable + + bench_name 'Full BSON Encoding' + end + end + end + end +end diff --git a/profile/driver_bench/multi_doc.rb b/profile/driver_bench/multi_doc.rb new file mode 100644 index 0000000000..4cd55f6519 --- /dev/null +++ b/profile/driver_bench/multi_doc.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative 'multi_doc/bulk_insert' +require_relative 'multi_doc/find_many' +require_relative 'multi_doc/grid_fs' + +module Mongo + module DriverBench + module MultiDoc + ALL = [ *BulkInsert::ALL, FindMany, *GridFS::ALL ].freeze + + # MultiBench consists of all Multi-doc micro-benchmarks + BENCH = ALL + end + end +end diff --git a/profile/driver_bench/multi_doc/base.rb b/profile/driver_bench/multi_doc/base.rb new file mode 100644 index 0000000000..16323d6140 --- /dev/null +++ b/profile/driver_bench/multi_doc/base.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module MultiDoc + # Abstract base class for multi-doc benchmarks. + # + # @api private + class Base < Mongo::DriverBench::Base + private + + attr_reader :client, :collection + + def setup + if file_name + @dataset ||= load_file(file_name) + @dataset_size ||= size_of_file(file_name) * scale + end + + prepare_client + end + + # The amount to scale the dataset size by (for scoring purposes). + def scale + 10_000 + end + + def teardown + cleanup_client + end + + def prepare_client + @client = new_client.use('perftest') + @client.database.drop + + @collection = @client.database[:corpus].tap(&:create) + end + + def cleanup_client + @client.database.drop + end + end + end + end +end diff --git a/profile/driver_bench/multi_doc/bulk_insert.rb b/profile/driver_bench/multi_doc/bulk_insert.rb new file mode 100644 index 0000000000..608ecb7bf4 --- /dev/null +++ b/profile/driver_bench/multi_doc/bulk_insert.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'bulk_insert/large_doc' +require_relative 'bulk_insert/small_doc' + +module Mongo + module DriverBench + module MultiDoc + module BulkInsert + ALL = [ LargeDoc, SmallDoc ].freeze + end + end + end +end diff --git a/profile/driver_bench/multi_doc/bulk_insert/base.rb b/profile/driver_bench/multi_doc/bulk_insert/base.rb new file mode 100644 index 0000000000..67e190b318 --- /dev/null +++ b/profile/driver_bench/multi_doc/bulk_insert/base.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module MultiDoc + module BulkInsert + # Abstract superclass for all bulk insert benchmarks. + # + # @api private + class Base < Mongo::DriverBench::MultiDoc::Base + attr_reader :repetitions, :bulk_dataset + + def setup + super + @bulk_dataset = dataset * repetitions + end + + # How much the benchmark's dataset size ought to be scaled (for + # scoring purposes). + def scale + @repetitions + end + + def before_task + collection.drop + collection.create + end + + def do_task + collection.insert_many(bulk_dataset, ordered: true) + end + end + end + end + end +end diff --git a/profile/driver_bench/multi_doc/bulk_insert/large_doc.rb b/profile/driver_bench/multi_doc/bulk_insert/large_doc.rb new file mode 100644 index 0000000000..94d991e19d --- /dev/null +++ b/profile/driver_bench/multi_doc/bulk_insert/large_doc.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Mongo + module DriverBench + module MultiDoc + module BulkInsert + # "This benchmark tests driver performance inserting multiple, large + # documents to the database." + # + # @api private + class LargeDoc < Mongo::DriverBench::MultiDoc::BulkInsert::Base + bench_name 'Large doc bulk insert' + + def initialize + super + @repetitions = 10 + end + + def file_name + 'single_and_multi_document/large_doc.json' + end + end + end + end + end +end diff --git a/profile/driver_bench/multi_doc/bulk_insert/small_doc.rb b/profile/driver_bench/multi_doc/bulk_insert/small_doc.rb new file mode 100644 index 0000000000..f40dc00cab --- /dev/null +++ b/profile/driver_bench/multi_doc/bulk_insert/small_doc.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Mongo + module DriverBench + module MultiDoc + module BulkInsert + # "This benchmark tests driver performance inserting multiple, small + # documents to the database." + # + # @api private + class SmallDoc < Mongo::DriverBench::MultiDoc::BulkInsert::Base + bench_name 'Small doc bulk insert' + + def initialize + super + @repetitions = 10_000 + end + + def file_name + 'single_and_multi_document/small_doc.json' + end + end + end + end + end +end diff --git a/profile/driver_bench/multi_doc/find_many.rb b/profile/driver_bench/multi_doc/find_many.rb new file mode 100644 index 0000000000..9a53665a3f --- /dev/null +++ b/profile/driver_bench/multi_doc/find_many.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Mongo + module DriverBench + module MultiDoc + # "This benchmark tests driver performance retrieving multiple documents + # from a query." + # + # @api private + class FindMany < Mongo::DriverBench::MultiDoc::Base + bench_name 'Find many and empty the cursor' + + private + + def file_name + 'single_and_multi_document/tweet.json' + end + + def setup + super + + prototype = dataset.first + 10.times do + docs = Array.new(1000, prototype) + @collection.insert_many(docs) + end + end + + def do_task + collection.find.each do |result| + # discard the result + end + end + end + end + end +end diff --git a/profile/driver_bench/multi_doc/grid_fs.rb b/profile/driver_bench/multi_doc/grid_fs.rb new file mode 100644 index 0000000000..3deb025792 --- /dev/null +++ b/profile/driver_bench/multi_doc/grid_fs.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'grid_fs/download' +require_relative 'grid_fs/upload' + +module Mongo + module DriverBench + module MultiDoc + module GridFS + ALL = [ Download, Upload ].freeze + end + end + end +end diff --git a/profile/driver_bench/multi_doc/grid_fs/base.rb b/profile/driver_bench/multi_doc/grid_fs/base.rb new file mode 100644 index 0000000000..980ec4c727 --- /dev/null +++ b/profile/driver_bench/multi_doc/grid_fs/base.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module MultiDoc + module GridFS + # Abstract base class for multi-doc GridFS benchmarks. + # + # @api private + class Base < Mongo::DriverBench::MultiDoc::Base + private + + # how much the dataset size ought to be scaled (for scoring + # purposes). + def scale + 1 + end + + def file_name + 'single_and_multi_document/gridfs_large.bin' + end + + def load_file(file_name) + File.read(path_to_file(file_name), encoding: 'BINARY') + end + end + end + end + end +end diff --git a/profile/driver_bench/multi_doc/grid_fs/download.rb b/profile/driver_bench/multi_doc/grid_fs/download.rb new file mode 100644 index 0000000000..b4eadef261 --- /dev/null +++ b/profile/driver_bench/multi_doc/grid_fs/download.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'stringio' +require_relative 'base' + +module Mongo + module DriverBench + module MultiDoc + module GridFS + # "This benchmark tests driver performance downloading a GridFS file + # to memory." + # + # @api private + class Download < Mongo::DriverBench::MultiDoc::GridFS::Base + bench_name 'GridFS Download' + + private + + attr_reader :fs_bucket, :file_id + + def setup + super + + @file_id = client.database.fs + .upload_from_stream 'gridfstest', dataset + end + + def before_task + super + + @fs_bucket = client.database.fs + end + + def do_task + fs_bucket.download_to_stream(file_id, StringIO.new) + end + end + end + end + end +end diff --git a/profile/driver_bench/multi_doc/grid_fs/upload.rb b/profile/driver_bench/multi_doc/grid_fs/upload.rb new file mode 100644 index 0000000000..571b463bd8 --- /dev/null +++ b/profile/driver_bench/multi_doc/grid_fs/upload.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Mongo + module DriverBench + module MultiDoc + module GridFS + # "This benchmark tests driver performance uploading a GridFS file + # from memory." + # + # @api private + class Upload < Mongo::DriverBench::MultiDoc::GridFS::Base + bench_name 'GridFS Upload' + + private + + attr_reader :fs_bucket + + def before_task + super + + @fs_bucket = client.database.fs + @fs_bucket.drop + + @fs_bucket.upload_from_stream 'one-byte-file', "\n" + end + + def do_task + fs_bucket.upload_from_stream file_name, dataset + end + end + end + end + end +end diff --git a/profile/driver_bench/parallel.rb b/profile/driver_bench/parallel.rb new file mode 100644 index 0000000000..44c0d52ff5 --- /dev/null +++ b/profile/driver_bench/parallel.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative 'parallel/gridfs' +require_relative 'parallel/ldjson' + +module Mongo + module DriverBench + module Parallel + ALL = [ *GridFS::ALL, *LDJSON::ALL ].freeze + + # ParallelBench consists of all Parallel micro-benchmarks + BENCH = ALL + end + end +end diff --git a/profile/driver_bench/parallel/base.rb b/profile/driver_bench/parallel/base.rb new file mode 100644 index 0000000000..990b70dea7 --- /dev/null +++ b/profile/driver_bench/parallel/base.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module Parallel + # Abstract base class for parallel micro-benchmarks. + # + # @api private + class Base < Mongo::DriverBench::Base + private + + attr_reader :client + + def setup + prepare_client + end + + def teardown + cleanup_client + end + + def prepare_client + @client = new_client.use('perftest') + @client.database.drop + end + + def cleanup_client + client.database.drop + end + end + end + end +end diff --git a/profile/driver_bench/parallel/counter.rb b/profile/driver_bench/parallel/counter.rb new file mode 100644 index 0000000000..b21f41bee0 --- /dev/null +++ b/profile/driver_bench/parallel/counter.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Mongo + module DriverBench + module Parallel + # An implementation of a counter variable that can be waited on, which + # will signal when the variable reaches zero. + # + # @api private + class Counter + # Create a new Counter object with the given initial value. + # + # @param [ Integer ] value the starting value of the counter (defaults + # to zero). + def initialize(value = 0) + @mutex = Thread::Mutex.new + @condition = Thread::ConditionVariable.new + @counter = value + end + + # Describes a block where the counter is incremented before executing + # it, and decremented afterward. + # + # @yield Calls the provided block with no arguments. + def enter + inc + yield + ensure + dec + end + + # Waits for the counter to be zero. + def wait + @mutex.synchronize do + return if @counter.zero? + + @condition.wait(@mutex) + end + end + + # Increments the counter. + def inc + @mutex.synchronize { @counter += 1 } + end + + # Decrements the counter. If the counter reaches zero, + # a signal is sent to any waiting process. + def dec + @mutex.synchronize do + @counter -= 1 if @counter.positive? + @condition.signal if @counter.zero? + end + end + end + end + end +end diff --git a/profile/driver_bench/parallel/dispatcher.rb b/profile/driver_bench/parallel/dispatcher.rb new file mode 100644 index 0000000000..aed94325d3 --- /dev/null +++ b/profile/driver_bench/parallel/dispatcher.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'etc' + +require_relative 'counter' + +module Mongo + module DriverBench + module Parallel + # Implements a dispatcher for executing multiple workers in parallel. + # + # @api private + class Dispatcher + attr_reader :source + + # Creates a new dispatcher with the given source. The source may be any + # object that responds to ``#next``. It may be assumed that ``#next`` + # will be called in a thread-safe manner, so the source does not need + # to worry about thread-safety in that regard. Each call to ``#next`` + # on the source object should return the next batch of work to be done. + # When the source is empty, ``#next`` must return ``nil``. + # + # @param [ Object ] source an object responding to ``#next``. + # @param [ Integer ] workers the number of workers to employ in + # performing the task. + # + # @yield The associated block is executed in each worker and must + # describe the worker's task to be accomplished. + # + # @yieldparam [ Object ] batch the next batch to be worked on. + def initialize(source, workers: (ENV['WORKERS'] || (Etc.nprocessors * 0.4)).to_i, &block) + @source = source + @counter = Counter.new + @source_mutex = Thread::Mutex.new + + @threads = Array.new(workers).map do + Thread.new do + @counter.enter do + Thread.stop + worker_loop(&block) + end + end + end + + sleep 0.1 until @threads.all? { |t| t.status == 'sleep' } + end + + # Runs the workers and waits for them to finish. + def run + @threads.each(&:wakeup) + @counter.wait + end + + private + + # @return [ Object ] returns the next batch of work to be done (from + # the source object given when the dispatcher was created). + def next_batch + @source_mutex.synchronize do + @source.next + end + end + + # Fetches the next batch and passes it to the block, in a loop. + # Terminates when the next batch is ``nil``. + def worker_loop + loop do + batch = next_batch or return + yield batch + end + end + end + end + end +end diff --git a/profile/driver_bench/parallel/gridfs.rb b/profile/driver_bench/parallel/gridfs.rb new file mode 100644 index 0000000000..9ef69d5451 --- /dev/null +++ b/profile/driver_bench/parallel/gridfs.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'gridfs/download' +require_relative 'gridfs/upload' + +module Mongo + module DriverBench + module Parallel + module GridFS + ALL = [ Download, Upload ].freeze + end + end + end +end diff --git a/profile/driver_bench/parallel/gridfs/base.rb b/profile/driver_bench/parallel/gridfs/base.rb new file mode 100644 index 0000000000..40a25a0790 --- /dev/null +++ b/profile/driver_bench/parallel/gridfs/base.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module Parallel + module GridFS + # Abstract base class of parallel GridFS micro-benchmarks. + # + # @api private + class Base < Mongo::DriverBench::Parallel::Base + def file_name_at(index) + format('parallel/gridfs_multi/file%02d.txt', index) + end + + private + + attr_reader :bucket + + def setup + super + @dataset_size = 50.times.sum { |i| File.size(path_to_file(file_name_at(i))) } + end + + def prepare_bucket(initialize: true) + @bucket = client.database.fs + @bucket.drop + @bucket.upload_from_stream 'one-byte-file', "\n" if initialize + end + + def upload_file(file_name) + File.open(path_to_file(file_name), 'r') do |file| + bucket.upload_from_stream file_name, file + end + end + end + end + end + end +end diff --git a/profile/driver_bench/parallel/gridfs/download.rb b/profile/driver_bench/parallel/gridfs/download.rb new file mode 100644 index 0000000000..1448a1f257 --- /dev/null +++ b/profile/driver_bench/parallel/gridfs/download.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative 'upload' +require_relative '../dispatcher' + +module Mongo + module DriverBench + module Parallel + module GridFS + # This benchmark tests driver performance downloading files from + # GridFS to disk. + # + # @api private + class Download < Mongo::DriverBench::Parallel::GridFS::Base + bench_name 'GridFS multi-file download' + + private + + # The source object to use for this benchmark. Each batch is a tuple + # consisting of the list position, and the element in the list at + # that position. + # + # @api private + class Source + def initialize(list) + @list = list + @n = 0 + end + + def next + id = @list.pop or return nil + [ @n, id ].tap { @n += 1 } + end + end + + def setup + super + prepare_bucket(initialize: false) + + dispatcher = Dispatcher.new(Upload::Source.new(self)) do |file_name| + upload_file(file_name) + end + dispatcher.run + + @destination = File.join(Dir.tmpdir, 'parallel') + end + + def before_task + super + FileUtils.rm_rf(@destination) + FileUtils.mkdir_p(@destination) + + ids = bucket.files_collection.find.map { |doc| doc['_id'] } + @dispatcher = Dispatcher.new(Source.new(ids)) do |(n, id)| + download_file(n, id) + end + end + + def do_task + @dispatcher.run + end + + def download_file(index, id) + path = File.join(@destination, file_name_at(index)) + FileUtils.mkdir_p(File.dirname(path)) + + File.open(path, 'w') do |file| + bucket.download_to_stream(id, file) + end + end + end + end + end + end +end diff --git a/profile/driver_bench/parallel/gridfs/upload.rb b/profile/driver_bench/parallel/gridfs/upload.rb new file mode 100644 index 0000000000..3e9cbb6f0f --- /dev/null +++ b/profile/driver_bench/parallel/gridfs/upload.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative '../dispatcher' + +module Mongo + module DriverBench + module Parallel + module GridFS + # "This benchmark tests driver performance uploading files from disk + # to GridFS." + # + # @api private + class Upload < Mongo::DriverBench::Parallel::GridFS::Base + bench_name 'GridFS multi-file upload' + + # The source object to use for this benchmark. Each batch consists + # of the name of the file to upload. + # + # @api private + class Source + def initialize(bench) + @n = 0 + @bench = bench + end + + def next + return nil if @n >= 50 + + @bench.file_name_at(@n).tap { @n += 1 } + end + end + + private + + def before_task + super + prepare_bucket + @dispatcher = Dispatcher.new(Source.new(self)) do |file_name| + upload_file(file_name) + end + end + + def do_task + @dispatcher.run + end + end + end + end + end +end diff --git a/profile/driver_bench/parallel/ldjson.rb b/profile/driver_bench/parallel/ldjson.rb new file mode 100644 index 0000000000..1a3c76375c --- /dev/null +++ b/profile/driver_bench/parallel/ldjson.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'ldjson/export' +require_relative 'ldjson/import' + +module Mongo + module DriverBench + module Parallel + module LDJSON + ALL = [ Export, Import ].freeze + end + end + end +end diff --git a/profile/driver_bench/parallel/ldjson/base.rb b/profile/driver_bench/parallel/ldjson/base.rb new file mode 100644 index 0000000000..ebb554bff4 --- /dev/null +++ b/profile/driver_bench/parallel/ldjson/base.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module Parallel + module LDJSON + # The abstract base class for parallel LDSON benchmarks. + # + # @api private + class Base < Mongo::DriverBench::Parallel::Base + def file_name_at(index) + format('parallel/ldjson_multi/ldjson%03d.txt', index) + end + + private + + attr_reader :collection + + def insert_docs_from_file(file_name, ids_relative_to: nil) + next_id = ids_relative_to + docs = File.readlines(path_to_file(file_name)).map do |line| + JSON.parse(line).tap do |doc| + if ids_relative_to + doc['_id'] = next_id + next_id += 1 + end + end + end + + collection.insert_many(docs) + end + + def setup + super + @dataset_size = 100.times.sum { |i| File.size(path_to_file(file_name_at(i))) } + end + + def prepare_collection + @collection = @client.database[:corpus].tap do |corpus| + corpus.drop + corpus.create + end + end + end + end + end + end +end diff --git a/profile/driver_bench/parallel/ldjson/export.rb b/profile/driver_bench/parallel/ldjson/export.rb new file mode 100644 index 0000000000..6121660299 --- /dev/null +++ b/profile/driver_bench/parallel/ldjson/export.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'fileutils' + +require_relative 'base' +require_relative '../dispatcher' + +module Mongo + module DriverBench + module Parallel + module LDJSON + # "This benchmark tests driver performance exporting documents to a + # set of LDJSON files." + # + # @api private + class Export < Mongo::DriverBench::Parallel::LDJSON::Base + bench_name 'LDJSON multi-file export' + + private + + # The data source for this benchmark; each batch is a set of 5000 + # documents. + class DataSource + def initialize(collection) + @n = 0 + @collection = collection + end + + def next + return nil if @n >= 100 + + batch = @collection.find(_id: { '$gte' => @n * 5000, '$lt' => (@n + 1) * 5000 }).to_a + [ @n, batch ].tap { @n += 1 } + end + end + + def setup + super + @destination = File.join(Dir.tmpdir, 'parallel') + FileUtils.mkdir_p(@destination) + + prepare_collection + + 100.times do |n| + insert_docs_from_file(file_name_at(n), ids_relative_to: n * 5000) + end + end + + def before_task + super + @dispatcher = Dispatcher.new(DataSource.new(collection)) do |(n, batch)| + worker_task(n, batch) + end + end + + def do_task + @dispatcher.run + end + + def teardown + super + FileUtils.rm_rf(@destination) + end + + def worker_task(index, batch) + path = File.join(@destination, file_name_at(index)) + FileUtils.mkdir_p(File.dirname(path)) + File.write(path, batch.map(&:to_json).join("\n")) + end + end + end + end + end +end diff --git a/profile/driver_bench/parallel/ldjson/import.rb b/profile/driver_bench/parallel/ldjson/import.rb new file mode 100644 index 0000000000..e7c41f7497 --- /dev/null +++ b/profile/driver_bench/parallel/ldjson/import.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require_relative 'base' +require_relative '../dispatcher' + +module Mongo + module DriverBench + module Parallel + module LDJSON + # "This benchmark tests driver performance importing documents from a + # set of LDJSON files." + # + # @api private + class Import < Mongo::DriverBench::Parallel::LDJSON::Base + bench_name 'LDJSON multi-file import' + + private + + # The data source for this benchmark. Each batch is the name of a + # file to read documents file. + class DataSource + def initialize(bench) + @n = 0 + @bench = bench + end + + def next + return nil if @n >= 100 + + @bench.file_name_at(@n).tap { @n += 1 } + end + end + + def before_task + super + prepare_collection + @dispatcher = Dispatcher.new(DataSource.new(self)) do |file_name| + insert_docs_from_file(file_name) + end + end + + def do_task + @dispatcher.run + end + end + end + end + end +end diff --git a/profile/benchmarking/percentiles.rb b/profile/driver_bench/percentiles.rb similarity index 97% rename from profile/benchmarking/percentiles.rb rename to profile/driver_bench/percentiles.rb index aeebe9d1d9..3ec9eb7e5f 100644 --- a/profile/benchmarking/percentiles.rb +++ b/profile/driver_bench/percentiles.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Mongo - module Benchmarking + module DriverBench # A utility class for returning the list item at a given percentile # value. class Percentiles diff --git a/profile/driver_bench/rake/tasks.rake b/profile/driver_bench/rake/tasks.rake new file mode 100644 index 0000000000..b9e148df35 --- /dev/null +++ b/profile/driver_bench/rake/tasks.rake @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require_relative '../suite' + +task driver_bench: %i[ driver_bench:data driver_bench:run ] + +SPECS_REPO_URI = 'git@github.com:mongodb/specifications.git' +SPECS_PATH = File.expand_path('../../../specifications', __dir__) +DRIVER_BENCH_DATA = File.expand_path('../../data/driver_bench', __dir__) + +namespace :driver_bench do + desc 'Downloads the DriverBench data files, if necessary' + task :data do + if File.directory?('./profile/data/driver_bench') + puts 'DriverBench data files are already downloaded' + next + end + + if File.directory?(SPECS_PATH) + puts 'specifications repo is already checked out' + else + sh 'git', 'clone', SPECS_REPO_URI + end + + mkdir_p DRIVER_BENCH_DATA + + Dir.glob(File.join(SPECS_PATH, 'source/benchmarking/data/*.tgz')) do |archive| + Dir.chdir(DRIVER_BENCH_DATA) do + sh 'tar', 'xzf', archive + end + end + end + + desc 'Runs the DriverBench benchmark suite' + task :run do + Mongo::DriverBench::Suite.run! + end +end diff --git a/profile/driver_bench/single_doc.rb b/profile/driver_bench/single_doc.rb new file mode 100644 index 0000000000..878f818379 --- /dev/null +++ b/profile/driver_bench/single_doc.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative 'single_doc/find_one_by_id' +require_relative 'single_doc/insert_one' +require_relative 'single_doc/run_command' + +module Mongo + module DriverBench + module SingleDoc + ALL = [ FindOneByID, *InsertOne::ALL, RunCommand ].freeze + + # SingleBench consists of all Single-doc micro-benchmarks + # except "Run Command" + BENCH = (ALL - [ RunCommand ]).freeze + end + end +end diff --git a/profile/driver_bench/single_doc/base.rb b/profile/driver_bench/single_doc/base.rb new file mode 100644 index 0000000000..cf540a9a4e --- /dev/null +++ b/profile/driver_bench/single_doc/base.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module SingleDoc + # Abstract base class for all single-doc benchmarks. + # + # @api private + class Base < Mongo::DriverBench::Base + private + + attr_reader :client, :collection + + def setup + if file_name + @dataset ||= load_file(file_name).first + @dataset_size ||= size_of_file(file_name) * scale + end + + prepare_client + end + + # The amount by which the dataset size should be scaled (for scoring + # purposes). + def scale + 10_000 + end + + def teardown + cleanup_client + end + + def prepare_client + @client = new_client.use('perftest') + @client.database.drop + + @collection = @client.database[:corpus].tap(&:create) + end + + def cleanup_client + @client.database.drop + end + + # Returns the name of the file that contains + # the dataset to use. + def file_name + nil + end + end + end + end +end diff --git a/profile/driver_bench/single_doc/find_one_by_id.rb b/profile/driver_bench/single_doc/find_one_by_id.rb new file mode 100644 index 0000000000..57603835ed --- /dev/null +++ b/profile/driver_bench/single_doc/find_one_by_id.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Mongo + module DriverBench + module SingleDoc + # "This benchmark tests driver performance sending an indexed query to + # the database and reading a single document in response." + # + # @api private + class FindOneByID < Mongo::DriverBench::SingleDoc::Base + bench_name 'Find one by ID' + + def file_name + 'single_and_multi_document/tweet.json' + end + + def setup + super + + 10.times do |i| + docs = Array.new(1000) { |j| dataset.merge(_id: (i * 1000) + j + 1) } + @collection.insert_many(docs) + end + end + + def do_task + 10_000.times do |i| + collection.find(_id: i + 1).to_a + end + end + end + end + end +end diff --git a/profile/driver_bench/single_doc/insert_one.rb b/profile/driver_bench/single_doc/insert_one.rb new file mode 100644 index 0000000000..64dc442530 --- /dev/null +++ b/profile/driver_bench/single_doc/insert_one.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative 'insert_one/large_doc' +require_relative 'insert_one/small_doc' + +module Mongo + module DriverBench + module SingleDoc + module InsertOne + ALL = [ LargeDoc, SmallDoc ].freeze + end + end + end +end diff --git a/profile/driver_bench/single_doc/insert_one/base.rb b/profile/driver_bench/single_doc/insert_one/base.rb new file mode 100644 index 0000000000..cb111a5396 --- /dev/null +++ b/profile/driver_bench/single_doc/insert_one/base.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative '../base' + +module Mongo + module DriverBench + module SingleDoc + module InsertOne + # Abstract base class for "insert one" benchmarks. + # + # @api private + class Base < Mongo::DriverBench::SingleDoc::Base + attr_reader :repetitions + alias scale repetitions + + def before_task + collection.drop + collection.create + end + + def do_task + repetitions.times do + collection.insert_one(dataset) + end + end + end + end + end + end +end diff --git a/profile/driver_bench/single_doc/insert_one/large_doc.rb b/profile/driver_bench/single_doc/insert_one/large_doc.rb new file mode 100644 index 0000000000..6e3db91ad7 --- /dev/null +++ b/profile/driver_bench/single_doc/insert_one/large_doc.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Mongo + module DriverBench + module SingleDoc + module InsertOne + # "This benchmark tests driver performance inserting a single, large + # document to the database." + # + # @api private + class LargeDoc < Mongo::DriverBench::SingleDoc::InsertOne::Base + bench_name 'Large doc insertOne' + + def initialize + super + @repetitions = 10 + end + + def file_name + 'single_and_multi_document/large_doc.json' + end + end + end + end + end +end diff --git a/profile/driver_bench/single_doc/insert_one/small_doc.rb b/profile/driver_bench/single_doc/insert_one/small_doc.rb new file mode 100644 index 0000000000..41289630a5 --- /dev/null +++ b/profile/driver_bench/single_doc/insert_one/small_doc.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Mongo + module DriverBench + module SingleDoc + module InsertOne + # "This benchmark tests driver performance inserting a single, small + # document to the database." + # + # @api private + class SmallDoc < Mongo::DriverBench::SingleDoc::InsertOne::Base + bench_name 'Small doc insertOne' + + def initialize + super + @repetitions = 10_000 + end + + def file_name + 'single_and_multi_document/small_doc.json' + end + end + end + end + end +end diff --git a/profile/driver_bench/single_doc/run_command.rb b/profile/driver_bench/single_doc/run_command.rb new file mode 100644 index 0000000000..55bed0c6a6 --- /dev/null +++ b/profile/driver_bench/single_doc/run_command.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Mongo + module DriverBench + module SingleDoc + # "This benchmark tests driver performance sending a command to the + # database and reading a response." + # + # @api private + class RunCommand < Mongo::DriverBench::SingleDoc::Base + bench_name 'Run command' + + def setup + super + @dataset_size = { hello: true }.to_bson.length * scale + end + + def prepare_client + @client = new_client + end + + def cleanup_client + # do nothing + end + + def do_task + 10_000.times do + client.database.command(hello: true) + end + end + end + end + end +end diff --git a/profile/driver_bench/suite.rb b/profile/driver_bench/suite.rb new file mode 100644 index 0000000000..bedf7862a6 --- /dev/null +++ b/profile/driver_bench/suite.rb @@ -0,0 +1,142 @@ +# frozen_string_literal: true + +require_relative 'bson' +require_relative 'multi_doc' +require_relative 'parallel' +require_relative 'single_doc' + +module Mongo + module DriverBench + ALL = [ *BSON::ALL, *SingleDoc::ALL, *MultiDoc::ALL, *Parallel::ALL ].freeze + + BENCHES = { + 'BSONBench' => BSON::BENCH, + 'SingleBench' => SingleDoc::BENCH, + 'MultiBench' => MultiDoc::BENCH, + 'ParallelBench' => Parallel::BENCH, + + 'ReadBench' => [ + SingleDoc::FindOneByID, + MultiDoc::FindMany, + MultiDoc::GridFS::Download, + Parallel::LDJSON::Export, + Parallel::GridFS::Download + ].freeze, + + 'WriteBench' => [ + SingleDoc::InsertOne::SmallDoc, + SingleDoc::InsertOne::LargeDoc, + MultiDoc::BulkInsert::SmallDoc, + MultiDoc::BulkInsert::LargeDoc, + MultiDoc::GridFS::Upload, + Parallel::LDJSON::Import, + Parallel::GridFS::Upload + ].freeze + }.freeze + + # A benchmark suite for running all benchmarks and aggregating (and + # reporting) the results. + # + # @api private + class Suite + PERCENTILES = [ 10, 25, 50, 75, 90, 95, 98, 99 ].freeze + + def self.run! + new.run + end + + def run + perf_data = [] + benches = Hash.new { |h, k| h[k] = [] } + + ALL.each do |klass| + result = run_benchmark(klass) + perf_data << compile_perf_data(result) + append_to_benchmarks(klass, result, benches) + end + + perf_data += compile_benchmarks(benches) + + save_perf_data(perf_data) + summarize_perf_data(perf_data) + end + + private + + def run_benchmark(klass) + print klass.bench_name, ': ' + $stdout.flush + + klass.new.run.tap do |result| + puts format('%4.4g', result[:score]) + end + end + + def compile_perf_data(result) + percentile_data = PERCENTILES.map do |percentile| + { 'name' => "time-#{percentile}%", + 'value' => result[:percentiles][percentile] } + end + + { + 'info' => { + 'test_name' => result[:name], + 'args' => {}, + }, + 'metrics' => [ + { 'name' => 'score', + 'value' => result[:score] }, + *percentile_data + ] + } + end + + def append_to_benchmarks(klass, result, benches) + BENCHES.each do |benchmark, list| + benches[benchmark] << result[:score] if list.include?(klass) + end + end + + def compile_benchmarks(benches) + benches.each_key do |key| + benches[key] = benches[key].sum / benches[key].length + end + + benches['DriverBench'] = (benches['ReadBench'] + benches['WriteBench']) / 2 + + benches.map do |bench, score| + { + 'info' => { + 'test_name' => bench, + 'args' => {} + }, + 'metrics' => [ + { 'name' => 'score', + 'value' => score } + ] + } + end + end + + # rubocop:disable Metrics/AbcSize + def summarize_perf_data(data) + puts '===== Performance Results =====' + data.each do |item| + puts format('%s : %4.4g', item['info']['test_name'], item['metrics'][0]['value']) + next unless item['metrics'].length > 1 + + item['metrics'].each do |metric| + next if metric['name'] == 'score' + + puts format(' %s : %4.4g', metric['name'], metric['value']) + end + end + end + # rubocop:enable Metrics/AbcSize + + def save_perf_data(data, file_name: ENV['PERFORMANCE_RESULTS_FILE'] || 'results.json') + File.write(file_name, data.to_json) + end + end + end +end diff --git a/profile/profile.rb b/profile/profile.rb deleted file mode 100644 index 26bf770752..0000000000 --- a/profile/profile.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -# rubocop:todo all - -require 'ruby-prof' -require 'mongo' - -Mongo::Logger.level = Logger::INFO - -client = Mongo::Client.new( - [ '127.0.0.1:27017' ], - database: 'ruby-driver', - user: 'root-user', - password: 'password', - auth_source: 'admin' -) - -collection = client[:test] - -documents = 50000.times.map do |i| - { name: 'user', index: i } -end - -inserts = RubyProf.profile do - collection.insert_many(documents) -end - -iteration = RubyProf.profile do - collection.find.each do |document| - end -end - -updates = RubyProf.profile do - collection.find(name: 'user').update_many({ '$set' => { name: 'user_modified' }}) -end - -deletes = RubyProf.profile do - collection.find(name: 'user_modified').delete_many -end - -p 'Inserts:' -RubyProf::FlatPrinter.new(inserts).print(STDOUT, min_percent: 2) -p 'Iteration:' -RubyProf::FlatPrinter.new(iteration).print(STDOUT, min_percent: 2) -p 'Updates:' -RubyProf::FlatPrinter.new(updates).print(STDOUT, min_percent: 2) -p 'Deletes:' -RubyProf::FlatPrinter.new(deletes).print(STDOUT, min_percent: 2) From 7cb06f0bf5d1c9cf2293089f3b666c584c729294 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Thu, 8 Feb 2024 06:02:17 -0500 Subject: [PATCH 184/198] RUBY-3393: Rename BSON tutorial page (#2832) --- docs/index.txt | 4 ++-- docs/reference/crud-operations.txt | 2 +- docs/release-notes.txt | 2 +- docs/tutorials.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index 44e4e33fc8..d8ab98a35a 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -23,9 +23,9 @@ BSON ==== The Ruby BSON implementation is packaged in a separate gem with C and -Java extensions for speed depending on the runtime enviroment. +Java extensions for speed depending on the runtime environment. -For reference on the Ruby BSON gem, see the :doc:`/tutorials/bson-v4`. +For reference on the Ruby BSON gem, see the :doc:`/tutorials/bson`. Object Mappers ============== diff --git a/docs/reference/crud-operations.txt b/docs/reference/crud-operations.txt index f482adcbe3..9e638ca029 100644 --- a/docs/reference/crud-operations.txt +++ b/docs/reference/crud-operations.txt @@ -93,7 +93,7 @@ Specify a ``Decimal128`` number .. versionadded:: 3.4 :manual:`Decimal128` is a -:doc:`BSON datatype ` +:doc:`BSON datatype ` that employs 128-bit decimal-based floating-point values capable of emulating decimal rounding with exact precision. This functionality is intended for applications that handle diff --git a/docs/release-notes.txt b/docs/release-notes.txt index d69063a17e..c681ca62e6 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -88,7 +88,7 @@ This release includes the following new features: 5.1/5.2/5.3) or newer to use the ``count`` command when API strict is enabled, or avoid setting ``api_strict: true`` when constructing ``Mongo::Client`` instances with server versions 5.0.0-5.0.7 and 5.1.0-5.3.1. -- The DBRef class has been moved to bson-ruby. For backwards compatibility, +- The DBRef class has been moved to ``bson-ruby``. For backwards compatibility, ``BSON::DBRef`` is aliased as ``Mongo::DBRef``. The ``BSON::DBRef`` class derives from ``BSON::Document``, unlike the legacy ``Mongo::DBRef`` which derived from ``Object``. ``BSON::DBRef`` retains all attributes passed into diff --git a/docs/tutorials.txt b/docs/tutorials.txt index 1272795ed6..0972fba7f1 100644 --- a/docs/tutorials.txt +++ b/docs/tutorials.txt @@ -15,5 +15,5 @@ operations available in the Ruby driver. tutorials/quick-start tutorials/common-errors - tutorials/bson-v4 + tutorials/bson From 9aefc4e8681f5f8c5d642eb37104e5f64a2cfb5a Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Mon, 12 Feb 2024 09:39:41 +0100 Subject: [PATCH 185/198] RUBY-3384 Update serverless tests (#2830) --- .evergreen/config.yml | 150 ++++++++++++++++++----------- .evergreen/config/axes.yml.erb | 9 -- .evergreen/config/common.yml.erb | 125 +++++++++++++++--------- .evergreen/config/standard.yml.erb | 16 ++- .mod/drivers-evergreen-tools | 2 +- 5 files changed, 189 insertions(+), 113 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 3b709daa64..ec486ce77f 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -124,25 +124,8 @@ functions: export ATLAS_SERVERLESS_LB_URI="${atlas_serverless_lb_uri}" export RVM_RUBY="${RVM_RUBY}" - if [[ -n "${USE_PROXY_SERVERLESS}" ]]; - then - export SERVERLESS_DRIVERS_GROUP="${PROXY_SERVERLESS_DRIVERS_GROUP}" - else - export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" - fi - - export SERVERLESS_API_PUBLIC_KEY="${SERVERLESS_API_PUBLIC_KEY}" - export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" - - # Needed for generating temporary aws credentials. - if [ -n "${FLE}" ]; - then - export AWS_ACCESS_KEY_ID="${fle_aws_key}" - export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" - export AWS_DEFAULT_REGION="${fle_aws_region}" - fi EOT # See what we've done @@ -154,17 +137,6 @@ functions: params: file: src/expansion.yml - "delete serverless instance": - - command: shell.exec - params: - working_dir: "src" - shell: bash - script: | - ${PREPARE_SHELL} - if [[ -n "${SERVERLESS_INSTANCE_NAME}" ]]; then - SERVERLESS_INSTANCE_NAME=${SERVERLESS_INSTANCE_NAME} .evergreen/serverless/delete-instance.sh - fi - "export AWS auth credentials": - command: shell.exec type: test @@ -398,6 +370,13 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} + # Needed for generating temporary aws credentials. + if [ -n "${FLE}" ]; + then + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + fi .evergreen/run-tests.sh "run tests via docker": @@ -408,6 +387,13 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} + # Needed for generating temporary aws credentials. + if [ -n "${FLE}" ]; + then + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + fi .evergreen/run-tests-docker.sh "run AWS auth tests": @@ -451,21 +437,6 @@ functions: AUTH=${AUTH} SSL=${SSL} TOPOLOGY=${TOPOLOGY} RVM_RUBY="${RVM_RUBY}" ATLAS_REPLICA_SET_URI=${atlas_replica_set_uri} ATLAS_SHARDED_URI=${atlas_sharded_uri} ATLAS_FREE_TIER_URI=${atlas_free_tier_uri} ATLAS_TLS11_URI=${atlas_tls11_uri} ATLAS_TLS12_URI=${atlas_tls12_uri} ATLAS_SERVERLESS_URI=${atlas_serverless_uri} ATLAS_SERVERLESS_LB_URI=${atlas_serverless_lb_uri} .evergreen/run-tests-atlas.sh "run serverless tests": - - command: shell.exec - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - # This hack with PROJECT env is necessary while serverless - # rejects creating instances with too long names. - PROJECT="mongo-ruby-driver" LOADBALANCED="ON" .evergreen/serverless/create-instance.sh - cat serverless-expansion.yml - - - command: expansions.update - params: - file: src/serverless-expansion.yml - - command: shell.exec type: test params: @@ -473,6 +444,14 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} + # Needed for generating temporary aws credentials. + if [ -n "${FLE}" ]; + then + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + fi + CRYPT_SHARED_LIB_PATH="${CRYPT_SHARED_LIB_PATH}" SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh pre: @@ -486,9 +465,67 @@ post: - func: "upload mo artifacts" #- func: "upload test results" - func: "upload test results to s3" - - func: "delete serverless instance" task_groups: + - name: serverless_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: "fetch source" + - func: "create expansions" + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: shell.exec + params: + shell: "bash" + script: | + ${PREPARE_SHELL} + bash ${DRIVERS_TOOLS}/.evergreen/serverless/setup-secrets.sh + bash ${DRIVERS_TOOLS}/.evergreen/serverless/create-instance.sh + - command: expansions.update + params: + file: serverless-expansion.yml + teardown_task: + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + bash ${DRIVERS_TOOLS}/.evergreen/serverless/delete-instance.sh + - func: "upload test results" + tasks: + - "test-serverless" + + - name: serverless_next_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: "fetch source" + - func: "create expansions" + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: shell.exec + params: + shell: "bash" + script: | + ${PREPARE_SHELL} + bash ${DRIVERS_TOOLS}/.evergreen/serverless/setup-secrets.sh serverless_next + bash ${DRIVERS_TOOLS}/.evergreen/serverless/create-instance.sh + - command: expansions.update + params: + file: serverless-expansion.yml + teardown_task: + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + bash ${DRIVERS_TOOLS}/.evergreen/serverless/setup-secrets.sh serverless_next + bash ${DRIVERS_TOOLS}/.evergreen/serverless/delete-instance.sh + - func: "upload test results" + tasks: + - "test-serverless" + - name: testatlas_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 # 30 minutes @@ -1220,15 +1257,6 @@ axes: - id: no display_name: No - - id: serverless-project - display_name: serverless project - values: - - id: original - display_name: Original - - id: proxy - display_name: Proxy - variables: - USE_PROXY_SERVERLESS: 1 buildvariants: - matrix_name: DriverBench @@ -1814,10 +1842,20 @@ buildvariants: ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] fle: path os: rhel8 - serverless-project: [ original, proxy ] - display_name: "Atlas serverless ${ruby} ${serverless-project}" + display_name: "Atlas serverless ${ruby}" + tasks: + - name: serverless_task_group + + - matrix_name: "serverless-next" + matrix_spec: + # https://jira.mongodb.org/browse/RUBY-3217 + # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] + fle: path + os: rhel8 + display_name: "Atlas serverless-next ${ruby}" tasks: - - name: test-serverless + - name: serverless_next_task_group - matrix_name: "aws-lambda" matrix_spec: diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index 074cba7a06..136b835ff8 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -406,12 +406,3 @@ axes: - id: no display_name: No - - id: serverless-project - display_name: serverless project - values: - - id: original - display_name: Original - - id: proxy - display_name: Proxy - variables: - USE_PROXY_SERVERLESS: 1 diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index ec80952381..1189b987eb 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -121,25 +121,8 @@ functions: export ATLAS_SERVERLESS_LB_URI="${atlas_serverless_lb_uri}" export RVM_RUBY="${RVM_RUBY}" - if [[ -n "${USE_PROXY_SERVERLESS}" ]]; - then - export SERVERLESS_DRIVERS_GROUP="${PROXY_SERVERLESS_DRIVERS_GROUP}" - else - export SERVERLESS_DRIVERS_GROUP="${SERVERLESS_DRIVERS_GROUP}" - fi - - export SERVERLESS_API_PUBLIC_KEY="${SERVERLESS_API_PUBLIC_KEY}" - export SERVERLESS_API_PRIVATE_KEY="${SERVERLESS_API_PRIVATE_KEY}" export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" - - # Needed for generating temporary aws credentials. - if [ -n "${FLE}" ]; - then - export AWS_ACCESS_KEY_ID="${fle_aws_key}" - export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" - export AWS_DEFAULT_REGION="${fle_aws_region}" - fi EOT # See what we've done @@ -151,17 +134,6 @@ functions: params: file: src/expansion.yml - "delete serverless instance": - - command: shell.exec - params: - working_dir: "src" - shell: bash - script: | - ${PREPARE_SHELL} - if [[ -n "${SERVERLESS_INSTANCE_NAME}" ]]; then - SERVERLESS_INSTANCE_NAME=${SERVERLESS_INSTANCE_NAME} .evergreen/serverless/delete-instance.sh - fi - "export AWS auth credentials": - command: shell.exec type: test @@ -395,6 +367,13 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} + # Needed for generating temporary aws credentials. + if [ -n "${FLE}" ]; + then + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + fi .evergreen/run-tests.sh "run tests via docker": @@ -405,6 +384,13 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} + # Needed for generating temporary aws credentials. + if [ -n "${FLE}" ]; + then + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + fi .evergreen/run-tests-docker.sh "run AWS auth tests": @@ -448,21 +434,6 @@ functions: AUTH=${AUTH} SSL=${SSL} TOPOLOGY=${TOPOLOGY} RVM_RUBY="${RVM_RUBY}" ATLAS_REPLICA_SET_URI=${atlas_replica_set_uri} ATLAS_SHARDED_URI=${atlas_sharded_uri} ATLAS_FREE_TIER_URI=${atlas_free_tier_uri} ATLAS_TLS11_URI=${atlas_tls11_uri} ATLAS_TLS12_URI=${atlas_tls12_uri} ATLAS_SERVERLESS_URI=${atlas_serverless_uri} ATLAS_SERVERLESS_LB_URI=${atlas_serverless_lb_uri} .evergreen/run-tests-atlas.sh "run serverless tests": - - command: shell.exec - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - # This hack with PROJECT env is necessary while serverless - # rejects creating instances with too long names. - PROJECT="mongo-ruby-driver" LOADBALANCED="ON" .evergreen/serverless/create-instance.sh - cat serverless-expansion.yml - - - command: expansions.update - params: - file: src/serverless-expansion.yml - - command: shell.exec type: test params: @@ -470,6 +441,14 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} + # Needed for generating temporary aws credentials. + if [ -n "${FLE}" ]; + then + export AWS_ACCESS_KEY_ID="${fle_aws_key}" + export AWS_SECRET_ACCESS_KEY="${fle_aws_secret}" + export AWS_DEFAULT_REGION="${fle_aws_region}" + fi + CRYPT_SHARED_LIB_PATH="${CRYPT_SHARED_LIB_PATH}" SERVERLESS=1 SSL=ssl RVM_RUBY="${RVM_RUBY}" SINGLE_MONGOS="${SINGLE_MONGOS}" SERVERLESS_URI="${SERVERLESS_URI}" FLE="${FLE}" SERVERLESS_MONGODB_VERSION="${SERVERLESS_MONGODB_VERSION}" .evergreen/run-tests-serverless.sh pre: @@ -483,9 +462,67 @@ post: - func: "upload mo artifacts" #- func: "upload test results" - func: "upload test results to s3" - - func: "delete serverless instance" task_groups: + - name: serverless_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: "fetch source" + - func: "create expansions" + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: shell.exec + params: + shell: "bash" + script: | + ${PREPARE_SHELL} + bash ${DRIVERS_TOOLS}/.evergreen/serverless/setup-secrets.sh + bash ${DRIVERS_TOOLS}/.evergreen/serverless/create-instance.sh + - command: expansions.update + params: + file: serverless-expansion.yml + teardown_task: + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + bash ${DRIVERS_TOOLS}/.evergreen/serverless/delete-instance.sh + - func: "upload test results" + tasks: + - "test-serverless" + + - name: serverless_next_task_group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: "fetch source" + - func: "create expansions" + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: shell.exec + params: + shell: "bash" + script: | + ${PREPARE_SHELL} + bash ${DRIVERS_TOOLS}/.evergreen/serverless/setup-secrets.sh serverless_next + bash ${DRIVERS_TOOLS}/.evergreen/serverless/create-instance.sh + - command: expansions.update + params: + file: serverless-expansion.yml + teardown_task: + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + bash ${DRIVERS_TOOLS}/.evergreen/serverless/setup-secrets.sh serverless_next + bash ${DRIVERS_TOOLS}/.evergreen/serverless/delete-instance.sh + - func: "upload test results" + tasks: + - "test-serverless" + - name: testatlas_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 # 30 minutes diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index dcca0c673e..b852b48b63 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -527,10 +527,20 @@ buildvariants: ruby: <%= supported_mri_rubies %> fle: path os: rhel8 - serverless-project: [ original, proxy ] - display_name: "Atlas serverless ${ruby} ${serverless-project}" + display_name: "Atlas serverless ${ruby}" tasks: - - name: test-serverless + - name: serverless_task_group + + - matrix_name: "serverless-next" + matrix_spec: + # https://jira.mongodb.org/browse/RUBY-3217 + # ruby: <%= supported_rubies %> + ruby: <%= supported_mri_rubies %> + fle: path + os: rhel8 + display_name: "Atlas serverless-next ${ruby}" + tasks: + - name: serverless_next_task_group - matrix_name: "aws-lambda" matrix_spec: diff --git a/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index c3335b46e7..8cb042bf7f 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit c3335b46e7190beeab6b65eb9a76bd0e09f69903 +Subproject commit 8cb042bf7fda753ec19eda19f02c441d4220a8dc From f92ecf2e59f6cba2700ce2140619ddfb0c20435f Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 12 Feb 2024 08:26:35 -0700 Subject: [PATCH 186/198] RUBY-3397 update the public key for signing gems (#2834) --- gem-public_cert.pem | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gem-public_cert.pem b/gem-public_cert.pem index ab7c15c7ce..68fa1da731 100644 --- a/gem-public_cert.pem +++ b/gem-public_cert.pem @@ -1,7 +1,7 @@ -----BEGIN CERTIFICATE----- MIIEeDCCAuCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBBMREwDwYDVQQDDAhkYngt cnVieTEXMBUGCgmSJomT8ixkARkWB21vbmdvZGIxEzARBgoJkiaJk/IsZAEZFgNj -b20wHhcNMjMwMTMxMTE1NjM1WhcNMjQwMTMxMTE1NjM1WjBBMREwDwYDVQQDDAhk +b20wHhcNMjQwMjA5MTc0NzIyWhcNMjUwMjA4MTc0NzIyWjBBMREwDwYDVQQDDAhk YngtcnVieTEXMBUGCgmSJomT8ixkARkWB21vbmdvZGIxEzARBgoJkiaJk/IsZAEZ FgNjb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC0/Veq9l47cTfX tQ+kHq2NOCwJuJGt1iXWQ/vH/yp7pZ/bLej7gPDl2CfIngAXRjM7r1FkR9ya7VAm @@ -14,13 +14,13 @@ VYSiCHuc3yEDyq5M+98WGX2etbj6esYtzI3rDevpIAHPB6HQmtoJIA4dSl3gjFb+ D+YQSuB2qYu021FI9zeY9sbZyWysEXBxhwrmTk+XUV0qz+OQZkMCAwEAAaN7MHkw CQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFH4nnr4tYlatU57RbExW jG86YM5nMB8GA1UdEQQYMBaBFGRieC1ydWJ5QG1vbmdvZGIuY29tMB8GA1UdEgQY -MBaBFGRieC1ydWJ5QG1vbmdvZGIuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQAVSlgM -nFDWCCNLOCqG5/Lj4U62XoALkdCI+OZ30+WrA8qiRLSL9ZEziVK9AV7ylez+sriQ -m8XKZKsCN5ON4+zXw1S+6Ftz/R4zDg7nTb9Wgw8ibzsoiP6e4pRW3Fls3ZdaG4pW -+qMTbae9OiSrgI2bxNTII+v+1FcbQjOlMu8HPZ3ZfXnurXPgN5GxSyyclZI1QONO -HbUoKHRirZu0F7JCvQQq4EkSuLWPplRJfYEeJIYm05zhhFeEyqea2B/TTlCtXa42 -84vxXsxGzumuO8F2Q9m6/p95sNhqCp0B/SkKXIrRGJ7FBzupoORNRXHviS2OC3ty -4lwUzOlLTF/yO0wwYYfmtQOALQwKnW838vbYthMXvTjxB0EgVZ5PKto99WbjsXzy -wkeAWhd5b+5JS0zgDL4SvGB8/W2IY+y0zELkojBMgJPyrpAWHL/WSsSBMuhyI2Pv -xxaBVLklnJJ/qCCOZ3lG2MyVc/Nb0Mmq8ygWNsfwHmKKYuuWcviit0D0Tek= +MBaBFGRieC1ydWJ5QG1vbmdvZGIuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQBKGtHA +fpi3N/BL1J5O4CBsAjtF4jHDiw2r5MwK+66NzMh3uedjgPI7MoosemLy++SB+8BR +SE8bDkb6gfDQQzrI6KSXXyqH2TbQXpY5Tac7/yqXRiu8G2qOrOj4czB/Hq7j09CV +YoH88v6hL11i5jt6jPjFh8hXYG0hDQxhi3atRz5Wwd98tUf2DSbyJXJiRgCBeZjl +rP7AnKsWMu0C+zPlL+nXtQr+nTFtkKXRWfUJMqePpBqtriQvgQ+Y1ItqYVTSLuiM +iwUMcn/rGhdCMBSaKDXdFkIveCHQE2f2WBo2EdErrcTrgEKYYdNfzcb/43j7L1kx +AUwyTtk+HFrviBynQbKN82rjbZE+5gukVea5c7idQPkqacPYsoU37DI+hTlUyJkV +dcTtfEg44lLlfNukBslfiQf54r+uWbyB0m0rDUN/py7/Ghyzt5GLBU91uCO3dGoI +55uFRHMvEcJMTDeImC/nuucPCAiEGMHggr9+NPC0tqpxjGKTo7lS7GzUFjg= -----END CERTIFICATE----- From 44c607f7ca5830b9b628f45c64fed76f9e3d7865 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov Date: Tue, 13 Feb 2024 17:16:18 +0100 Subject: [PATCH 187/198] RUBY-3399 Skip queryPlanner output specs (#2835) --- spec/mongo/collection/view/explainable_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/mongo/collection/view/explainable_spec.rb b/spec/mongo/collection/view/explainable_spec.rb index 644b865f88..7ca3a5c30b 100644 --- a/spec/mongo/collection/view/explainable_spec.rb +++ b/spec/mongo/collection/view/explainable_spec.rb @@ -42,6 +42,7 @@ max_server_version '3.0' it 'executes the explain' do + skip 'https://jira.mongodb.org/browse/RUBY-3399' explain[:queryPlanner][:parsedQuery].should be_a(Hash) end end @@ -50,6 +51,7 @@ min_server_fcv '3.2' it 'executes the explain' do + skip 'https://jira.mongodb.org/browse/RUBY-3399' explain[:queryPlanner][:mongosPlannerVersion].should == 1 end end From e459c3f75f1b1a2181b8d468751580664a3e9e04 Mon Sep 17 00:00:00 2001 From: Nora Reidy Date: Thu, 22 Feb 2024 11:34:23 -0500 Subject: [PATCH 188/198] DOCSP-37038: Fix broken link (#2841) --- docs/reference/create-client.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index f54805651a..44630ee559 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -687,7 +687,7 @@ Ruby Options * - ``:sdam_proc`` - Since the client begins monitoring the deployment in background as soon as it is constructed, constructing a client and then subscribing - to `SDAM `_ events in a separate statement may result in the + to :ref:`SDAM ` events in a separate statement may result in the subscriber not receiving some of the SDAM events. The ``:sdam_proc`` option permits adding event subscribers on the client being constructed before any SDAM events are published. From 94eb2f530d2fe7f690ea6f5af32303943f865415 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov <160598371+comandeo-mongo@users.noreply.github.com> Date: Tue, 27 Feb 2024 17:00:33 +0100 Subject: [PATCH 189/198] Remove unnecessary file (#2845) --- upload-api-docs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/upload-api-docs b/upload-api-docs index 1915efabb0..494516ed8e 100755 --- a/upload-api-docs +++ b/upload-api-docs @@ -29,6 +29,7 @@ class FileUploader end def upload_docs + puts "Uploading to #{@bucket}" Dir.glob("#{@docs_path}/**/*").each do |file| next if File.directory?(file) @@ -112,6 +113,7 @@ def generate_docs(options) '--exclude', './spec', '--readme', './README.md', '-o', options[:docs_path] + File.delete(File.join(options[:docs_path], 'frames.html')) rescue nil ) end From efdadddfbdc3ddc8ef6db00c3b8221b4ae429948 Mon Sep 17 00:00:00 2001 From: Dmitry Rybakov <160598371+comandeo-mongo@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:11:25 +0100 Subject: [PATCH 190/198] Use latest YARD version (#2847) --- gemfiles/standard.rb | 2 +- upload-api-docs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gemfiles/standard.rb b/gemfiles/standard.rb index ca63921b8f..f29b91cb4b 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -2,7 +2,7 @@ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength def standard_dependencies - gem 'yard' + gem 'yard', '>= 0.9.35' gem 'ffi' group :development, :testing do diff --git a/upload-api-docs b/upload-api-docs index 494516ed8e..79d1be7780 100755 --- a/upload-api-docs +++ b/upload-api-docs @@ -7,7 +7,7 @@ gemfile true do source 'https://rubygems.org' gem 'nokogiri' gem 'aws-sdk-s3' - gem 'yard' + gem 'yard', '>= 0.9.35' end require 'aws-sdk-s3' @@ -113,8 +113,12 @@ def generate_docs(options) '--exclude', './spec', '--readme', './README.md', '-o', options[:docs_path] - File.delete(File.join(options[:docs_path], 'frames.html')) rescue nil ) + begin + File.delete(File.join(options[:docs_path], 'frames.html')) + rescue StandardError + nil + end end options = Options.new From d332cd906431016044c9baad62b242760ebbd7c6 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Wed, 6 Mar 2024 09:07:24 -0700 Subject: [PATCH 191/198] RUBY-3400 fix failing specs (#2846) * don't build tasks for latest server version for now * hmm, let's try putting 'latest' back? * RUBY-3405 make sure the cluster time is applied to new sessions * actually, probably need to get cluster time from the support client? * skip broken tests (see RUBY-3413) * ignore the serverless tests (pending RUBY-3414) --- .evergreen/config.yml | 105 +++++++++--------- .evergreen/config/standard.yml.erb | 52 +++++---- spec/runners/transactions/test.rb | 15 ++- spec/runners/unified/test.rb | 9 +- .../index_management/listSearchIndexes.yml | 9 +- 5 files changed, 102 insertions(+), 88 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index ec486ce77f..4e2b208d4e 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1295,14 +1295,10 @@ buildvariants: # Only JRuby-9.4 is built for arm in the current toolchain, but 9.4 has other # issues. Either we fix those issues, or we build 9.3 for arm in the toolchain, # and hope it fixes the issues... Until then, we can only test MRI ruby on arm. - # - # further: 'latest' is broken until the QE2 changes are merged (RUBY-3211, - # RUBY-3226) - matrix_name: "mongo-recent-arm" matrix_spec: ruby: "ruby-3.2" - #mongodb-version: ['latest', '6.0'] - mongodb-version: ['6.0'] + mongodb-version: ["latest", "7.0", "6.0"] topology: ["standalone", "replica-set", "sharded-cluster"] os: 'rhel8-arm' display_name: "${mongodb-version} ${os} ${topology} ${auth-and-ssl} ${ruby}" @@ -1342,7 +1338,7 @@ buildvariants: - matrix_name: "single-lb" matrix_spec: ruby: "ruby-3.2" - mongodb-version: "6.0" + mongodb-version: "7.0" topology: load-balanced single-mongos: single-mongos os: rhel8 @@ -1364,7 +1360,7 @@ buildvariants: - matrix_name: "single-mongos" matrix_spec: ruby: "ruby-3.2" - mongodb-version: "6.0" + mongodb-version: "7.0" topology: "sharded-cluster" single-mongos: single-mongos os: rhel8 @@ -1376,7 +1372,7 @@ buildvariants: matrix_spec: retry-reads: no-retry-reads ruby: "ruby-3.2" - mongodb-version: "6.0" + mongodb-version: "7.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 display_name: "${mongodb-version} ${topology} ${retry-reads} ${ruby}" @@ -1387,7 +1383,7 @@ buildvariants: matrix_spec: retry-writes: no-retry-writes ruby: "ruby-3.2" - mongodb-version: "6.0" + mongodb-version: "7.0" topology: [replica-set, sharded-cluster] os: rhel8 display_name: "${mongodb-version} ${topology} ${retry-writes} ${ruby}" @@ -1409,7 +1405,7 @@ buildvariants: matrix_spec: lint: on ruby: "ruby-3.2" - mongodb-version: "6.0" + mongodb-version: "7.0" topology: '*' os: rhel8 display_name: "${mongodb-version} ${topology} ${lint} ${ruby}" @@ -1420,7 +1416,7 @@ buildvariants: matrix_spec: fork: on ruby: "ruby-3.2" - mongodb-version: "6.0" + mongodb-version: "7.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 display_name: "${mongodb-version} ${topology} fork ${ruby}" @@ -1431,7 +1427,7 @@ buildvariants: matrix_spec: solo: on ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] - mongodb-version: "6.0" + mongodb-version: "7.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 display_name: "${mongodb-version} ${topology} solo ${ruby}" @@ -1480,7 +1476,7 @@ buildvariants: matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] ruby: jruby-9.3 - mongodb-version: "6.0" + mongodb-version: "7.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" @@ -1491,7 +1487,7 @@ buildvariants: matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] - mongodb-version: "6.0" + mongodb-version: "7.0" topology: "replica-set" compressor: 'zlib' os: rhel8 @@ -1503,7 +1499,7 @@ buildvariants: matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] - mongodb-version: "6.0" + mongodb-version: "7.0" topology: "replica-set" compressor: 'snappy' os: rhel8 @@ -1519,7 +1515,7 @@ buildvariants: matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] ruby: ["ruby-3.2", "ruby-2.7"] - mongodb-version: "6.0" + mongodb-version: "7.0" topology: "replica-set" compressor: 'zstd' os: rhel8 @@ -1530,7 +1526,7 @@ buildvariants: - matrix_name: "activesupport" matrix_spec: ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] - mongodb-version: "6.0" + mongodb-version: "7.0" topology: replica-set as: as os: rhel8 @@ -1541,7 +1537,7 @@ buildvariants: - matrix_name: "bson" matrix_spec: ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] - mongodb-version: "6.0" + mongodb-version: "7.0" topology: replica-set bson: "*" os: rhel8 @@ -1561,7 +1557,7 @@ buildvariants: - matrix_name: "kerberos-unit" matrix_spec: ruby: "ruby-3.2" - mongodb-version: "6.0" + mongodb-version: "7.0" topology: standalone os: rhel8 auth-and-ssl: kerberos @@ -1615,7 +1611,7 @@ buildvariants: # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP verifier: ${mongodb-version} ${ruby}" tasks: @@ -1628,7 +1624,7 @@ buildvariants: ocsp-delegate: on ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 auth-and-ssl: noauth-and-ssl display_name: "OCSP integration - must staple: ${mongodb-version} ${ruby}" @@ -1641,7 +1637,7 @@ buildvariants: ocsp-status: unknown ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 auth-and-ssl: noauth-and-ssl display_name: "OCSP integration - unknown: ${mongodb-version} ${ruby}" @@ -1657,7 +1653,7 @@ buildvariants: extra-uri-options: "none" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1671,7 +1667,7 @@ buildvariants: extra-uri-options: "none" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1685,7 +1681,7 @@ buildvariants: extra-uri-options: "none" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1699,7 +1695,7 @@ buildvariants: extra-uri-options: "tlsInsecure=true" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1713,7 +1709,7 @@ buildvariants: extra-uri-options: "tlsInsecure=true" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1727,7 +1723,7 @@ buildvariants: extra-uri-options: "tlsInsecure=true" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1741,7 +1737,7 @@ buildvariants: extra-uri-options: "tlsAllowInvalidCertificates=true" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1755,7 +1751,7 @@ buildvariants: extra-uri-options: "tlsAllowInvalidCertificates=true" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1769,7 +1765,7 @@ buildvariants: extra-uri-options: "tlsAllowInvalidCertificates=true" ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: @@ -1789,7 +1785,7 @@ buildvariants: ocsp-connectivity: pass ruby: jruby-9.3 topology: standalone - mongodb-version: "6.0" + mongodb-version: "7.0" os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${mongodb-version} ${ruby}" tasks: @@ -1801,7 +1797,7 @@ buildvariants: fle: helper topology: standalone os: rhel8 - mongodb-version: "6.0" + mongodb-version: "7.0" display_name: "GCP KMS" tasks: - name: testgcpkms_task_group @@ -1835,27 +1831,28 @@ buildvariants: tasks: - name: test-atlas - - matrix_name: "serverless" - matrix_spec: - # https://jira.mongodb.org/browse/RUBY-3217 - # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] - ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] - fle: path - os: rhel8 - display_name: "Atlas serverless ${ruby}" - tasks: - - name: serverless_task_group - - - matrix_name: "serverless-next" - matrix_spec: - # https://jira.mongodb.org/browse/RUBY-3217 - # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] - ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] - fle: path - os: rhel8 - display_name: "Atlas serverless-next ${ruby}" - tasks: - - name: serverless_next_task_group +# Commented out, pending RUBY-3414 +# - matrix_name: "serverless" +# matrix_spec: +# # https://jira.mongodb.org/browse/RUBY-3217 +# # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] +# ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] +# fle: path +# os: rhel8 +# display_name: "Atlas serverless ${ruby}" +# tasks: +# - name: serverless_task_group +# +# - matrix_name: "serverless-next" +# matrix_spec: +# # https://jira.mongodb.org/browse/RUBY-3217 +# # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] +# ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] +# fle: path +# os: rhel8 +# display_name: "Atlas serverless-next ${ruby}" +# tasks: +# - name: serverless_next_task_group - matrix_name: "aws-lambda" matrix_spec: diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index b852b48b63..f37729211d 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -32,10 +32,11 @@ supported_rubies = supported_mri_rubies + jrubies # The latest stable version of MongoDB - latest_stable_mdb = "6.0".inspect # so it gets quoted as a string + latest_stable_mdb = "7.0".inspect # so it gets quoted as a string # A few of the most recent MongoDB versions actual_and_upcoming_mdb = %w( latest 7.0 6.0 ) + recent_mdb = %w( 6.0 5.3 ) latest_5x_mdb = "5.3".inspect # so it gets quoted as a string %> @@ -77,14 +78,10 @@ buildvariants: # Only JRuby-9.4 is built for arm in the current toolchain, but 9.4 has other # issues. Either we fix those issues, or we build 9.3 for arm in the toolchain, # and hope it fixes the issues... Until then, we can only test MRI ruby on arm. - # - # further: 'latest' is broken until the QE2 changes are merged (RUBY-3211, - # RUBY-3226) - matrix_name: "mongo-recent-arm" matrix_spec: ruby: <%= latest_ruby %> - #mongodb-version: ['latest', '6.0'] - mongodb-version: ['6.0'] + mongodb-version: <%= actual_and_upcoming_mdb %> topology: <%= topologies %> os: 'rhel8-arm' display_name: "${mongodb-version} ${os} ${topology} ${auth-and-ssl} ${ruby}" @@ -520,27 +517,28 @@ buildvariants: tasks: - name: test-atlas - - matrix_name: "serverless" - matrix_spec: - # https://jira.mongodb.org/browse/RUBY-3217 - # ruby: <%= supported_rubies %> - ruby: <%= supported_mri_rubies %> - fle: path - os: rhel8 - display_name: "Atlas serverless ${ruby}" - tasks: - - name: serverless_task_group - - - matrix_name: "serverless-next" - matrix_spec: - # https://jira.mongodb.org/browse/RUBY-3217 - # ruby: <%= supported_rubies %> - ruby: <%= supported_mri_rubies %> - fle: path - os: rhel8 - display_name: "Atlas serverless-next ${ruby}" - tasks: - - name: serverless_next_task_group +# Commented out, pending RUBY-3414 +# - matrix_name: "serverless" +# matrix_spec: +# # https://jira.mongodb.org/browse/RUBY-3217 +# # ruby: <%= supported_rubies %> +# ruby: <%= supported_mri_rubies %> +# fle: path +# os: rhel8 +# display_name: "Atlas serverless ${ruby}" +# tasks: +# - name: serverless_task_group +# +# - matrix_name: "serverless-next" +# matrix_spec: +# # https://jira.mongodb.org/browse/RUBY-3217 +# # ruby: <%= supported_rubies %> +# ruby: <%= supported_mri_rubies %> +# fle: path +# os: rhel8 +# display_name: "Atlas serverless-next ${ruby}" +# tasks: +# - name: serverless_next_task_group - matrix_name: "aws-lambda" matrix_spec: diff --git a/spec/runners/transactions/test.rb b/spec/runners/transactions/test.rb index ce54d16cd5..8364b1713c 100644 --- a/spec/runners/transactions/test.rb +++ b/spec/runners/transactions/test.rb @@ -132,7 +132,6 @@ def test_client @test_client ||= begin sdam_proc = lambda do |test_client| test_client.subscribe(Mongo::Monitoring::COMMAND, command_subscriber) - test_client.subscribe(Mongo::Monitoring::TOPOLOGY_OPENING, sdam_subscriber) test_client.subscribe(Mongo::Monitoring::SERVER_OPENING, sdam_subscriber) test_client.subscribe(Mongo::Monitoring::SERVER_DESCRIPTION_CHANGED, sdam_subscriber) @@ -321,6 +320,9 @@ def setup_test # without auto_encryption_options for querying results. result_collection_name = outcome&.collection_name || @spec.collection_name @result_collection = support_client.use(@spec.database_name)[result_collection_name] + + # DRIVERS-2816, adjusted for legacy spec runner + @cluster_time = support_client.command(ping: 1).cluster_time end def teardown_test @@ -358,12 +360,19 @@ def resolve_target(client, operation) end end + def new_session(options) + test_client.start_session(options || {}).tap do |s| + # DRIVERS-2816, adjusted for legacy spec runner + s.advance_cluster_time(@cluster_time) + end + end + def session0 - @session0 ||= test_client.start_session(@session_options[:session0] || {}) + @session0 ||= new_session(@session_options[:session0]) end def session1 - @session1 ||= test_client.start_session(@session_options[:session1] || {}) + @session1 ||= new_session(@session_options[:session1]) end end end diff --git a/spec/runners/unified/test.rb b/spec/runners/unified/test.rb index fb9083e6e9..114088f693 100644 --- a/spec/runners/unified/test.rb +++ b/spec/runners/unified/test.rb @@ -227,7 +227,9 @@ def generate_entities(es) opts = {} end - client.start_session(**opts) + client.start_session(**opts).tap do |session| + session.advance_cluster_time(@cluster_time) + end when 'clientEncryption' client_encryption_opts = spec.use!('clientEncryptionOpts') key_vault_client = entities.get(:client, client_encryption_opts['keyVaultClient']) @@ -342,6 +344,11 @@ def set_initial_data raise NotImplementedError, "Unhandled spec keys: #{spec}" end end + + # the cluster time is used to advance the cluster time of any + # sessions created during this test. + # -> see DRIVERS-2816 + @cluster_time = root_authorized_client.command(ping: 1).cluster_time end def run diff --git a/spec/spec_tests/data/index_management/listSearchIndexes.yml b/spec/spec_tests/data/index_management/listSearchIndexes.yml index f05a368585..c0d2d9ddc5 100644 --- a/spec/spec_tests/data/index_management/listSearchIndexes.yml +++ b/spec/spec_tests/data/index_management/listSearchIndexes.yml @@ -22,6 +22,7 @@ runOnRequirements: tests: - description: "when no name is provided, it does not populate the filter" + skipReason: https://jira.mongodb.org/browse/DRIVERS-2794 operations: - name: listSearchIndexes object: *collection0 @@ -41,10 +42,11 @@ tests: - $listSearchIndexes: {} - description: "when a name is provided, it is present in the filter" + skipReason: https://jira.mongodb.org/browse/DRIVERS-2794 operations: - name: listSearchIndexes object: *collection0 - arguments: + arguments: name: &indexName "test index" expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting @@ -63,12 +65,13 @@ tests: $db: *database0 - description: aggregation cursor options are supported + skipReason: https://jira.mongodb.org/browse/DRIVERS-2794 operations: - name: listSearchIndexes object: *collection0 - arguments: + arguments: name: &indexName "test index" - aggregationOptions: + aggregationOptions: batchSize: 10 expectError: # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting From 0c65ebf39d6a682df03453850edc3beb10786392 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 12 Mar 2024 08:02:51 -0600 Subject: [PATCH 192/198] MONGOID-5739 Support BSON 5 (#2848) * MONGOID-5739 support BSON 5 * point at my personal branch to test this * normalize values before comparing * switch back to the official spec/shared repo * actually bump spec/shared (why are submodules so impossible?) * use pessimistic versioning --- mongo.gemspec | 8 +------- .../client_side_encryption/corpus_spec.rb | 12 ++++++++++-- spec/shared | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/mongo.gemspec b/mongo.gemspec index 0226011936..2c58491b8b 100644 --- a/mongo.gemspec +++ b/mongo.gemspec @@ -41,11 +41,5 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 2.5" - # For testing driver against bson master we need to depend on bson < 6.0.0 - # but in release version we want to depend on bson < 5.0.0. - if %w(1 yes true).include?(ENV['MONGO_RUBY_DRIVER_BSON_MASTER']) - s.add_dependency 'bson', '>=4.13.0', '<6.0.0' - else - s.add_dependency 'bson', '>=4.14.1', '<5.0.0' - end + s.add_dependency 'bson', '>=4.14.1', '<6.0.0' end diff --git a/spec/integration/client_side_encryption/corpus_spec.rb b/spec/integration/client_side_encryption/corpus_spec.rb index 2ecc8e40df..aac6347694 100644 --- a/spec/integration/client_side_encryption/corpus_spec.rb +++ b/spec/integration/client_side_encryption/corpus_spec.rb @@ -188,6 +188,15 @@ key_vault_collection.insert_one(kmip_data_key) end + # This method compensates for an API change between BSON 4 and + # BSON 5. + def normalize_cse_value(a) + case a + when BSON::Decimal128 then a.to_d + else a + end + end + shared_context 'with jsonSchema collection validator' do let(:local_schema_map) { nil } @@ -228,12 +237,11 @@ .find(_id: corpus_encrypted_id) .first - corpus_encrypted_actual.each do |key, value| # If it was deterministically encrypted, test the encrypted values # for equality. if value['algo'] == 'det' - expect(value['value']).to eq(corpus_encrypted_expected[key]['value']) + expect(normalize_cse_value(value['value'])).to eq(normalize_cse_value(corpus_encrypted_expected[key]['value'])) else # If the document was randomly encrypted, the two encrypted values # will not be equal. Ensure that they are equal when decrypted. diff --git a/spec/shared b/spec/shared index bd968a969a..cee4bc0264 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit bd968a969aed7ae4d579855f2ce5b3e3201444a4 +Subproject commit cee4bc02649a573c8256b0505c1d23f503ac2609 From 74e4e381b21e7a8da176cbc39d374be4670e0e6c Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Thu, 28 Mar 2024 14:26:03 -0600 Subject: [PATCH 193/198] RUBY-3424 Prep for 2.20 (#2852) * prep for 2.20 * more CI stuff (e.g. DriverBench should run against latest stable, not latest) * let's see what happens when we enable jruby 9.4 * document added support for JRuby 9.4, and deprecation of 9.2 * flesh out the release notes more --- .evergreen/config.yml | 58 +++++++++---------------- .evergreen/config/axes.yml.erb | 13 ------ .evergreen/config/standard.yml.erb | 25 ++++------- .evergreen/update-evergreen-configs | 2 +- .github/workflows/test.yml | 16 +------ docs/reference/driver-compatibility.txt | 46 ++++++++++++++++++++ docs/release-notes.txt | 22 ++++++++++ lib/mongo/version.rb | 2 +- 8 files changed, 101 insertions(+), 83 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 4e2b208d4e..01261a3325 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1,5 +1,5 @@ # GENERATED FILE - DO NOT EDIT. -# Run ./.evergreen/update-evergreen-configs to regenerate this file. +# Run `rake eg` to regenerate this file. # When a task that used to pass starts to fail, go through all versions that # may have been skipped to detect when the task started failing. @@ -1008,22 +1008,10 @@ axes: display_name: ruby-2.7 variables: RVM_RUBY: "ruby-2.7" - - id: "ruby-2.6" - display_name: ruby-2.6 - variables: - RVM_RUBY: "ruby-2.6" - - id: "ruby-2.5" - display_name: ruby-2.5 - variables: - RVM_RUBY: "ruby-2.5" - id: "ruby-head" display_name: ruby-head variables: RVM_RUBY: "ruby-head" - - id: "jruby-9.2" - display_name: jruby-9.2 - variables: - RVM_RUBY: "jruby-9.2" - id: "jruby-9.3" display_name: jruby-9.3 variables: @@ -1257,12 +1245,11 @@ axes: - id: no display_name: No - buildvariants: - matrix_name: DriverBench matrix_spec: ruby: "ruby-3.2" - mongodb-version: latest + mongodb-version: "7.0" topology: standalone run_on: rhel80-large display_name: DriverBench @@ -1282,7 +1269,7 @@ buildvariants: - matrix_name: "mongo-recent" matrix_spec: - ruby: ["ruby-3.2", "ruby-3.1", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-3.1", "jruby-9.4"] mongodb-version: ["latest", "7.0", "6.0"] topology: ["standalone", "replica-set", "sharded-cluster"] os: ['rhel8'] @@ -1292,9 +1279,6 @@ buildvariants: tasks: - name: "test-mlaunch" - # Only JRuby-9.4 is built for arm in the current toolchain, but 9.4 has other - # issues. Either we fix those issues, or we build 9.3 for arm in the toolchain, - # and hope it fixes the issues... Until then, we can only test MRI ruby on arm. - matrix_name: "mongo-recent-arm" matrix_spec: ruby: "ruby-3.2" @@ -1307,7 +1291,7 @@ buildvariants: - matrix_name: "mongo-5.x" matrix_spec: - ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.4"] mongodb-version: ['5.3', '5.0'] topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1317,7 +1301,7 @@ buildvariants: - matrix_name: "mongo-4.x" matrix_spec: - ruby: ["ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] + ruby: ["ruby-3.0", "ruby-2.7"] mongodb-version: ['4.4', '4.2', '4.0'] topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1327,7 +1311,7 @@ buildvariants: - matrix_name: "mongo-3.6" matrix_spec: - ruby: "ruby-2.5" + ruby: "ruby-2.7" mongodb-version: ['3.6'] topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1392,7 +1376,7 @@ buildvariants: - matrix_name: mmapv1 matrix_spec: - ruby: "ruby-2.5" + ruby: "ruby-2.7" mongodb-version: ['3.6', '4.0'] topology: ["standalone", "replica-set", "sharded-cluster"] storage-engine: mmapv1 @@ -1426,7 +1410,7 @@ buildvariants: - matrix_name: "solo" matrix_spec: solo: on - ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "jruby-9.4", "jruby-9.3"] mongodb-version: "7.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1475,7 +1459,7 @@ buildvariants: - matrix_name: "jruby-auth" matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] - ruby: jruby-9.3 + ruby: jruby-9.4 mongodb-version: "7.0" topology: ["standalone", "replica-set", "sharded-cluster"] os: rhel8 @@ -1486,7 +1470,7 @@ buildvariants: - matrix_name: "zlib" matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] - ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.4"] mongodb-version: "7.0" topology: "replica-set" compressor: 'zlib' @@ -1498,7 +1482,7 @@ buildvariants: - matrix_name: "snappy" matrix_spec: auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] - ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.4"] mongodb-version: "7.0" topology: "replica-set" compressor: 'snappy' @@ -1525,7 +1509,7 @@ buildvariants: - matrix_name: "activesupport" matrix_spec: - ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.4"] mongodb-version: "7.0" topology: replica-set as: as @@ -1536,7 +1520,7 @@ buildvariants: - matrix_name: "bson" matrix_spec: - ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] + ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.4"] mongodb-version: "7.0" topology: replica-set bson: "*" @@ -1548,7 +1532,7 @@ buildvariants: # kerberos integration tests are broken (RUBY-3266) # - matrix_name: "kerberos-integration" # matrix_spec: - # ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.3"] + # ruby: ["ruby-3.2", "ruby-2.7", "jruby-9.4"] # os: rhel8 # display_name: "Kerberos integration ${os} ${ruby}" # tasks: @@ -1609,7 +1593,7 @@ buildvariants: matrix_spec: ocsp-verifier: true # No JRuby due to https://github.com/jruby/jruby-openssl/issues/210 - ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] + ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7"] topology: standalone mongodb-version: "7.0" os: rhel8 @@ -1783,7 +1767,7 @@ buildvariants: ocsp-status: [valid, unknown] ocsp-delegate: '*' ocsp-connectivity: pass - ruby: jruby-9.3 + ruby: jruby-9.4 topology: standalone mongodb-version: "7.0" os: rhel8 @@ -1825,7 +1809,7 @@ buildvariants: - matrix_name: "atlas" matrix_spec: - ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] + ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "jruby-9.4", "jruby-9.3"] os: rhel8 display_name: "Atlas tests ${ruby}" tasks: @@ -1835,8 +1819,8 @@ buildvariants: # - matrix_name: "serverless" # matrix_spec: # # https://jira.mongodb.org/browse/RUBY-3217 -# # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] -# ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] +# # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "jruby-9.4", "jruby-9.3"] +# ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7"] # fle: path # os: rhel8 # display_name: "Atlas serverless ${ruby}" @@ -1846,8 +1830,8 @@ buildvariants: # - matrix_name: "serverless-next" # matrix_spec: # # https://jira.mongodb.org/browse/RUBY-3217 -# # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5", "jruby-9.3", "jruby-9.2"] -# ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "ruby-2.6", "ruby-2.5"] +# # ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7", "jruby-9.4", "jruby-9.3"] +# ruby: ["ruby-3.2", "ruby-3.1", "ruby-3.0", "ruby-2.7"] # fle: path # os: rhel8 # display_name: "Atlas serverless-next ${ruby}" diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index 136b835ff8..c433cf9c22 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -162,22 +162,10 @@ axes: display_name: ruby-2.7 variables: RVM_RUBY: "ruby-2.7" - - id: "ruby-2.6" - display_name: ruby-2.6 - variables: - RVM_RUBY: "ruby-2.6" - - id: "ruby-2.5" - display_name: ruby-2.5 - variables: - RVM_RUBY: "ruby-2.5" - id: "ruby-head" display_name: ruby-head variables: RVM_RUBY: "ruby-head" - - id: "jruby-9.2" - display_name: jruby-9.2 - variables: - RVM_RUBY: "jruby-9.2" - id: "jruby-9.3" display_name: jruby-9.3 variables: @@ -405,4 +393,3 @@ axes: API_VERSION_REQUIRED: 1 - id: no display_name: No - diff --git a/.evergreen/config/standard.yml.erb b/.evergreen/config/standard.yml.erb index f37729211d..11c75425da 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -1,33 +1,27 @@ <% topologies = %w( standalone replica-set sharded-cluster ) - # jruby-9.4 is available in the toolchain, but tests break. It will - # require some investigation. Once available: - # 1. replace jruby-9.3 with jruby-9.4 in `recent_rubies` and - # `sample_rubies`. - # 2. add jruby-9.4 to the front of `jrubies`. - # latest_ruby = the most recently released, stable version of Ruby # (make sure this version is being built by 10gen/mongo-ruby-toolchain) latest_ruby = "ruby-3.2".inspect # so it gets quoted as a string # these are used for testing against a few recent ruby versions - recent_rubies = %w( ruby-3.2 ruby-3.1 jruby-9.3 ) + recent_rubies = %w( ruby-3.2 ruby-3.1 jruby-9.4 ) # this is a list of the most most recent 3.x and 2.x MRI ruby versions sample_mri_rubies = %w( ruby-3.2 ruby-2.7 ) # as above, but including the most recent JRuby release - sample_rubies = sample_mri_rubies + %w( jruby-9.3 ) + sample_rubies = sample_mri_rubies + %w( jruby-9.4 ) # older Ruby versions provided by 10gen/mongo-ruby-toolchain - older_rubies = %w( ruby-3.0 ruby-2.7 ruby-2.6 ruby-2.5 ) + older_rubies = %w( ruby-3.0 ruby-2.7 ) # all supported JRuby versions provided by 10gen/mongo-ruby-toolchain - jrubies = %w( jruby-9.3 jruby-9.2 ) + jrubies = %w( jruby-9.4 jruby-9.3 ) supported_mri_rubies = %w( ruby-3.2 ruby-3.1 ruby-3.0 - ruby-2.7 ruby-2.6 ruby-2.5 ) + ruby-2.7 ) supported_rubies = supported_mri_rubies + jrubies @@ -45,7 +39,7 @@ buildvariants: - matrix_name: DriverBench matrix_spec: ruby: <%= latest_ruby %> - mongodb-version: latest + mongodb-version: <%= latest_stable_mdb %> topology: standalone run_on: rhel80-large display_name: DriverBench @@ -75,9 +69,6 @@ buildvariants: tasks: - name: "test-mlaunch" - # Only JRuby-9.4 is built for arm in the current toolchain, but 9.4 has other - # issues. Either we fix those issues, or we build 9.3 for arm in the toolchain, - # and hope it fixes the issues... Until then, we can only test MRI ruby on arm. - matrix_name: "mongo-recent-arm" matrix_spec: ruby: <%= latest_ruby %> @@ -110,7 +101,7 @@ buildvariants: - matrix_name: "mongo-3.6" matrix_spec: - ruby: "ruby-2.5" + ruby: "ruby-2.7" mongodb-version: ['3.6'] topology: <%= topologies %> os: rhel8 @@ -175,7 +166,7 @@ buildvariants: - matrix_name: mmapv1 matrix_spec: - ruby: "ruby-2.5" + ruby: "ruby-2.7" mongodb-version: ['3.6', '4.0'] topology: <%= topologies %> storage-engine: mmapv1 diff --git a/.evergreen/update-evergreen-configs b/.evergreen/update-evergreen-configs index b1f4296649..e2f205ef55 100755 --- a/.evergreen/update-evergreen-configs +++ b/.evergreen/update-evergreen-configs @@ -13,7 +13,7 @@ class Runner def transform(output_file_name) contents = <<-EOT # GENERATED FILE - DO NOT EDIT. -# Run ./.evergreen/update-evergreen-configs to regenerate this file. +# Run `rake eg` to regenerate this file. EOT diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 44c7b6b2bb..e3f468fe8b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,14 +16,10 @@ jobs: fail-fast: false matrix: os: [ ubuntu-20.04 ] - ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2"] - mongodb: ["3.6", "4.4", "5.0"] + ruby: ["2.7", "3.0", "3.1", "3.2"] + mongodb: ["3.6", "4.4", "5.0", "6.0", "7.0"] topology: [replica_set, sharded_cluster] include: - - os: macos - ruby: "2.6" - mongodb: "5.0" - topology: server - os: macos ruby: "2.7" mongodb: "5.0" @@ -32,10 +28,6 @@ jobs: ruby: "3.0" mongodb: "5.0" topology: server - - os: ubuntu-latest - ruby: "2.6" - mongodb: "5.0" - topology: server - os: ubuntu-latest ruby: "2.7" mongodb: "5.0" @@ -48,10 +40,6 @@ jobs: ruby: "3.2" mongodb: "5.0" topology: server - - os: ubuntu-18.04 - ruby: "2.5" - mongodb: "3.6" - topology: replica_set - os: ubuntu-latest ruby: "3.2" mongodb: "6.0" diff --git a/docs/reference/driver-compatibility.txt b/docs/reference/driver-compatibility.txt index 664b19149d..a0aaea71af 100644 --- a/docs/reference/driver-compatibility.txt +++ b/docs/reference/driver-compatibility.txt @@ -57,6 +57,19 @@ version. - MongoDB 3.0 - MongoDB 2.6 + * - 2.20 + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - + - + - + - + * - 2.19 - |checkmark| - |checkmark| @@ -292,10 +305,29 @@ for that Ruby version is deprecated. - Ruby 2.1 - Ruby 2.0 - Ruby 1.9 + - JRuby 9.4 - JRuby 9.3 - JRuby 9.2 - JRuby 9.1 + * - 2.20 + - |checkmark| + - |checkmark| + - |checkmark| + - D + - + - + - + - + - + - + - + - + - |checkmark| + - |checkmark| + - D + - + * - 2.19 - |checkmark| - |checkmark| @@ -309,6 +341,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - |checkmark| - @@ -326,6 +359,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - |checkmark| - @@ -344,6 +378,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - @@ -361,6 +396,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - @@ -378,6 +414,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - @@ -395,6 +432,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - @@ -412,6 +450,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - @@ -429,6 +468,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - @@ -446,6 +486,7 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - @@ -463,6 +504,7 @@ for that Ruby version is deprecated. - D - D - + - - |checkmark| - |checkmark| @@ -480,6 +522,7 @@ for that Ruby version is deprecated. - D - D - + - - |checkmark| - |checkmark| @@ -497,6 +540,7 @@ for that Ruby version is deprecated. - |checkmark| - |checkmark| - + - - |checkmark| - |checkmark| @@ -514,6 +558,7 @@ for that Ruby version is deprecated. - |checkmark| - |checkmark| - + - - |checkmark| - |checkmark| @@ -531,6 +576,7 @@ for that Ruby version is deprecated. - |checkmark| - |checkmark| - + - - |checkmark| - |checkmark| diff --git a/docs/release-notes.txt b/docs/release-notes.txt index c681ca62e6..33f251ff6f 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -18,6 +18,28 @@ for the complete list of changes, including those internal to the driver and its test suite. +.. _release-notes-2.20: + +2.20 +==== + +This release includes the following new features: + +- Support for Ruby 2.5 and 2.6 has been discontinued. Support for Ruby 2.7 and + JRuby 9.2 has been deprecated, and will be discontinued in the next minor + driver version. Support for JRuby 9.4 has been added. +- Support for the newly-released Ruby-BSON version 5.0. +- Connection strings no longer require a slash between the hosts and the + options. E.g., "mongodb://example.com?w=1" and "mongodb://example.com/?w=1" + are both valid connection strings now. +- Container runtime and orchestration metadata for the client environment are + now sent to the server for analytics purposes. +- A warning message is now written to the log when the host is detected to be + a CosmosDB (Azure) or DocumentDB (Amazon) instance. +- When attempting a retry of a read or write operation in a sharded topology, + the retry will be attempted on a different mongos instance, if possible. + + .. _release-notes-2.19: 2.19 diff --git a/lib/mongo/version.rb b/lib/mongo/version.rb index eb0246b5af..7f52d504fd 100644 --- a/lib/mongo/version.rb +++ b/lib/mongo/version.rb @@ -20,5 +20,5 @@ module Mongo # The current version of the driver. # # @since 2.0.0 - VERSION = '2.19.1'.freeze + VERSION = '2.20.0'.freeze end From dd1adadedd99d2a5cf03ff5759c9f9c611f4d871 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Tue, 2 Apr 2024 08:09:57 -0600 Subject: [PATCH 194/198] need zlib1g-dev for libxml for nokogiri, etc, etc (#2854) Also bumping to debian:12 for grins --- release/mri/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release/mri/Dockerfile b/release/mri/Dockerfile index b6da2a23c7..0e86f2e019 100644 --- a/release/mri/Dockerfile +++ b/release/mri/Dockerfile @@ -1,9 +1,9 @@ -FROM debian:10 +FROM debian:12 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && \ - apt-get -y install git ruby-bundler make gcc ruby-dev + apt-get -y install git ruby-bundler make gcc ruby-dev zlib1g-dev WORKDIR /app From 4fb3eff5307adf88d782ddc4cb3fff7ab4ec08a3 Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Thu, 4 Apr 2024 22:17:44 +0900 Subject: [PATCH 195/198] Clarify documentation for forking servers --- docs/reference/create-client.txt | 116 ++++++++++++------------------- lib/mongo/config.rb | 4 +- 2 files changed, 48 insertions(+), 72 deletions(-) diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index 44630ee559..bc45830454 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -1641,89 +1641,68 @@ Usage with Forking Servers .. note:: - Applications using Mongoid should follow `Mongoid's forking guidance - `_. - The guidance and sample code below is provided for applications using the - Ruby driver directly. + Applications using Mongoid should follow `Mongoid's "Usage with Forking Servers" documentation + `_. + The guidance below is provided for applications using the Ruby driver directly. When using the Mongo Ruby driver in a Web application with a forking web server -such as Unicorn, Puma or Passenger, or when the application otherwise forks, -each process should generally each have their own ``Mongo::Client`` instances. -This is because: - -1. The background threads remain in the parent process and are not transferred - to the child process. -2. File descriptors like network sockets are shared between parent and - child processes. - -The driver attempts to detect client use from forked processes and -reestablish network connections when such use is detected, alleviating -the issue of file descriptor sharing. - -If both parent and child processes need to perform MongoDB operations, -it is recommended for each of the processes to create their own -``Mongo::Client`` instances. Specifically, the child process should create -its own client instance and not use any of the instances that were created -in the parent. - -If the parent continues to perform MongoDB operations using an already -established client instance after forking children, this client instance will -continue to operate normally as long as no child uses it in any way. -The child processes will not inherit any of the monitoring threads, and -will not perform background operations on the client instance. - -If the parent does not need to perform MongoDB operation after forking -children (which is what typically happens in web applications), the parent -should close all of the client instances it created to free up connections -and cease background monitoring: +such as Puma, or when the application otherwise forks, each process (parent and child) +must have its own client connections. This is because: -.. code-block:: ruby +1. Background threads such as those used to monitor connection state are + **not** transferred to the child process. +2. File descriptors like network sockets **are** shared between parent and + child processes, which can cause I/O conflicts. - client.reconnect +By default, the driver will automatically monitor for forking by detecting +a change in the Process ID (PID). When the driver detects that it is now +running in a newly forked process, it automatically closes and reconnects +all client connections. -.. note:: +When the Ruby driver is used in a web application, if possible, +we recommend to not create any ``Mongo::Client`` instances in the parent +process (prior to the workers being forked), and instead only create client +instances in the workers. - If the parent process performs operations on the Mongo client and does not - close it, the parent process will continue consuming a connection slot - in the cluster and will continue monitoring the cluster for as long as the - parent remains alive. +Manually Handling Process Forks +------------------------------- -Reconnecting Client Instances ------------------------------ +Certain advanced use cases, such as `Puma's fork_worker option `_, +require ``Mongo::Client`` instances to be open in both the parent +and child processes. In this case, you must handle client +reconnection manually. -When the Ruby driver is used in a web application, it is recommended to not -create any ``Mongo::Client`` instances in the management processes (prior to -the workers being forked), and instead only create client instances in the -workers. - -It is possible, although not recommended, to use the same ``Mongo::Client`` -instances in parent and child processes. In order to do so, the instance -must be closed and reconnected in the child process so that the background -threads can be recreated: +To do this, immediately before forking, close any existing client connections +on your parent process. This will prevent the parent process from experiencing +network and monitoring errors due to the child's reuse of the parent's +file descriptors. .. code-block:: ruby + # Immediately before fork client.close - client.reconnect .. note:: - This pattern should be used with Ruby driver version 2.6.2 or higher. - Previous driver versions did not recreate monitoring threads when - reconnecting. + Closing clients won't disrupt database commands currently in-flight. + Clients will automatically reconnect when new commands arrive. -.. note:: +Then, immediately after forking, reconnect your clients in the newly +forked child process: + +.. code-block:: ruby - When closing and reconnecting the client instance in the child, - due to file descriptor sharing, the parent process may experience network - and monitoring errors. + # Immediately after fork + client.reconnect -Web servers generally provide hooks that can be used by applications to -perform actions when the worker processes are forked. The recommended hooks -to use are: +Most web servers provide hooks that can be used by applications to +perform actions when the worker processes are forked. The recommended +hooks are: -- For `Puma `_, ``before_fork`` to close clients in the - parent process and ``on_worker_boot`` to reconnect in the child processes. +- For `Puma `_, + use ``before_fork`` and ``before_refork`` to close clients in + the parent process and ``on_worker_boot`` to reconnect in the + child processes. - For `Unicorn `_, ``before_fork`` to close clients in the parent process and ``after_fork`` to reconnect clients in the child processes. @@ -1731,12 +1710,9 @@ to use are: ``starting_worker_process`` to reconnect clients in the child processes (Passenger does not appear to have a pre-fork hook). -This documentation does not provide example code for using the aforementioned -hooks, because there is no standard for client instance management when -using the Ruby driver directly. `Mongoid documentation -`_ -however provides examples for closing clients in the parent process and -reconnecting clients in the child processes. +Refer to `Mongoid's "Usage with Forking Servers" documentation +`_ +for further examples. Troubleshooting --------------- diff --git a/lib/mongo/config.rb b/lib/mongo/config.rb index 123a937918..73aaedbe25 100644 --- a/lib/mongo/config.rb +++ b/lib/mongo/config.rb @@ -16,7 +16,7 @@ module Config # When this flag is off, an aggregation done on a view will be executed over # the documents included in that view, instead of all documents in the - # collection. When this flag is on, the view fiter is ignored. + # collection. When this flag is on, the view filter is ignored. option :broken_view_aggregate, default: true # When this flag is set to false, the view options will be correctly @@ -24,7 +24,7 @@ module Config option :broken_view_options, default: true # When this flag is set to true, the update and replace methods will - # validate the paramters and raise an error if they are invalid. + # validate the parameters and raise an error if they are invalid. option :validate_update_replace, default: false # Set the configuration options. From ead4cccf7523e65b3a200ba6349110a30445e246 Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Thu, 4 Apr 2024 22:36:24 +0900 Subject: [PATCH 196/198] Tweaks --- docs/reference/create-client.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index bc45830454..fe8e5692c2 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -1649,8 +1649,8 @@ When using the Mongo Ruby driver in a Web application with a forking web server such as Puma, or when the application otherwise forks, each process (parent and child) must have its own client connections. This is because: -1. Background threads such as those used to monitor connection state are - **not** transferred to the child process. +1. Background Ruby threads, such as those used to monitor connection state, + are **not** transferred to the child process. 2. File descriptors like network sockets **are** shared between parent and child processes, which can cause I/O conflicts. @@ -1684,8 +1684,8 @@ file descriptors. .. note:: - Closing clients won't disrupt database commands currently in-flight. - Clients will automatically reconnect when new commands arrive. + Calling ``Client#close`` does not disrupt database operations currently in-flight. + Clients will automatically reconnect when you perform new operations. Then, immediately after forking, reconnect your clients in the newly forked child process: From cb304141d998231333f3254fe20c2a6296a574e8 Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Thu, 4 Apr 2024 23:21:57 +0900 Subject: [PATCH 197/198] More tweaks --- docs/reference/create-client.txt | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index fe8e5692c2..dacac3fbb1 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -1649,15 +1649,20 @@ When using the Mongo Ruby driver in a Web application with a forking web server such as Puma, or when the application otherwise forks, each process (parent and child) must have its own client connections. This is because: -1. Background Ruby threads, such as those used to monitor connection state, - are **not** transferred to the child process. +1. Background Ruby threads, such as those used by the Ruby MongoDB driver to + monitor connection state, are **not** transferred to the child process. 2. File descriptors like network sockets **are** shared between parent and child processes, which can cause I/O conflicts. -By default, the driver will automatically monitor for forking by detecting -a change in the Process ID (PID). When the driver detects that it is now -running in a newly forked process, it automatically closes and reconnects -all client connections. +Regarding (1), if you do not restart the driver's monitoring threads +on the child process after forking, although your child may initially +appear to function correctly, you will eventually see +``Mongo::Error::NoServerAvailable`` exceptions if/when your MongoDB cluster +state changes, for example due to network errors or a maintenance event. + +Regarding (2), if a child process reuses the parent's file descriptors, you +will see ``Mongo::Error::SocketError`` errors with messages such as +``Errno::EPIPE: Broken pipe`` and ``EOFError: end of file reached``. When the Ruby driver is used in a web application, if possible, we recommend to not create any ``Mongo::Client`` instances in the parent @@ -1688,7 +1693,7 @@ file descriptors. Clients will automatically reconnect when you perform new operations. Then, immediately after forking, reconnect your clients in the newly -forked child process: +forked child process, which will respawn the driver's monitoring threads. .. code-block:: ruby From ae03f908c58e23416550abc3d784ca6e31b633fd Mon Sep 17 00:00:00 2001 From: johnnyshields <27655+johnnyshields@users.noreply.github.com> Date: Fri, 5 Apr 2024 01:07:35 +0900 Subject: [PATCH 198/198] Correct method name --- docs/reference/create-client.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/create-client.txt b/docs/reference/create-client.txt index dacac3fbb1..346aab93fd 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -1705,7 +1705,7 @@ perform actions when the worker processes are forked. The recommended hooks are: - For `Puma `_, - use ``before_fork`` and ``before_refork`` to close clients in + use ``before_fork`` and ``on_refork`` to close clients in the parent process and ``on_worker_boot`` to reconnect in the child processes. - For `Unicorn `_,