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/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-atlas.yml b/.evergreen/config-atlas.yml deleted file mode 100644 index 6b8dcf2e28..0000000000 --- a/.evergreen/config-atlas.yml +++ /dev/null @@ -1,859 +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: 1800 - - -# 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)/../drivers-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}" - 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}" .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: "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: 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"] - 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 ccf5cff9fd..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. @@ -14,8 +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) timeout: @@ -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 @@ -65,7 +50,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 @@ -106,6 +91,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} @@ -128,6 +114,18 @@ functions: export OCSP_MUST_STAPLE="${OCSP_MUST_STAPLE}" 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_ATLAS_USER="${SERVERLESS_ATLAS_USER}" + export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" EOT # See what we've done @@ -139,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 @@ -171,7 +158,14 @@ 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}" + 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": @@ -353,7 +347,20 @@ 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 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 @@ -363,7 +370,14 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests.sh + # 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": - command: shell.exec @@ -373,7 +387,14 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - ./egos stdbuf -i0 -o0 -e0 .evergreen/run-tests-docker.sh + # 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": - command: shell.exec @@ -383,7 +404,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 @@ -393,7 +414,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 @@ -403,7 +424,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 @@ -416,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: @@ -438,11 +444,18 @@ 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 + # 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: - func: "fetch source" - - func: "fetch egos" - func: "create expansions" post: @@ -453,14 +466,258 @@ post: #- func: "upload test results" - func: "upload test results to s3" -tasks: +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 + 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 + + echo "MONGODB_URI=${MONGODB_URI}" + - 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-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 + 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 + + - 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: + - 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" - name: "test-mlaunch" commands: - func: "run tests" + - name: "driver-bench" + commands: + - func: "run benchmarks" - name: "test-via-docker" commands: - func: "run tests via docker" @@ -483,6 +740,110 @@ 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 + 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 + - 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 + + - 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 @@ -502,6 +863,11 @@ axes: display_name: "latest" 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: @@ -510,18 +876,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: @@ -610,10 +980,22 @@ 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 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: @@ -626,64 +1008,49 @@ 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: 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: ubuntu2204 + display_name: "Ubuntu 22.04" + run_on: ubuntu2204-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: 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 + - id: debian11 + display_name: debian11 variables: - DOCKER_DISTRO: debian92 - - id: debian10 - display_name: debian10 + DOCKER_DISTRO: debian11 + - id: ubuntu2204 + display_name: ubuntu2204 variables: - DOCKER_DISTRO: debian10 - - id: ubuntu1804 - display_name: ubuntu1804 - variables: - DOCKER_DISTRO: ubuntu1804 + DOCKER_DISTRO: ubuntu2204 - id: "compressor" display_name: Compressor @@ -778,7 +1145,7 @@ axes: values: - id: mmapv1 display_name: MMAPv1 - run_on: ubuntu1604-small + run_on: rhel80-small variables: MMAPV1: 'true' @@ -879,148 +1246,108 @@ axes: display_name: No buildvariants: - - matrix_name: "docker" + - matrix_name: DriverBench 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" + ruby: "ruby-3.2" + mongodb-version: "7.0" topology: standalone - os: ubuntu1804 - preload: nopreload - display_name: "Docker ${os} ${mongodb-version} ${topology} ${ruby} ${preload}" + run_on: rhel80-large + display_name: DriverBench tasks: - - name: "test-docker" + - name: "driver-bench" - - 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.2" + mongodb-version: ["latest", "7.0", "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} db-${mongodb-version} ${topology} tasks: - name: "test-mlaunch" - - matrix_name: "mongo-6.0" + - matrix_name: "mongo-recent" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1"] - mongodb-version: ['6.0'] + ruby: ["ruby-3.2", "ruby-3.1", "jruby-9.4"] + mongodb-version: ["latest", "7.0", "6.0"] topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + 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" - - matrix_name: "mongo-5.3" + - matrix_name: "mongo-recent-arm" matrix_spec: - auth-and-ssl: noauth-and-nossl - ruby: ["ruby-3.1"] - mongodb-version: ['5.3'] + ruby: "ruby-3.2" + mongodb-version: ["latest", "7.0", "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.2", "ruby-2.7", "jruby-9.4"] + 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"] + 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" + ruby: "ruby-2.7" 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.2" + mongodb-version: "7.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.2" 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.2" + mongodb-version: "7.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" @@ -1028,10 +1355,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.2" + mongodb-version: "7.0" topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} ${retry-reads} ${ruby}" tasks: - name: "test-mlaunch" @@ -1039,32 +1366,32 @@ buildvariants: - matrix_name: "no-retry-writes" matrix_spec: retry-writes: no-retry-writes - ruby: ["ruby-2.7"] - mongodb-version: ["4.0"] + ruby: "ruby-3.2" + mongodb-version: "7.0" topology: [replica-set, sharded-cluster] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} ${retry-writes} ${ruby}" tasks: - name: "test-mlaunch" - 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 - 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.2" + mongodb-version: "7.0" topology: '*' - os: ubuntu2004 + os: rhel8 display_name: "${mongodb-version} ${topology} ${lint} ${ruby}" tasks: - name: "test-mlaunch" @@ -1072,10 +1399,10 @@ buildvariants: - matrix_name: "fork" matrix_spec: fork: on - ruby: ["ruby-3.0", "ruby-2.7"] - mongodb-version: ["4.4"] + ruby: "ruby-3.2" + mongodb-version: "7.0" topology: ["standalone", "replica-set", "sharded-cluster"] - os: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} fork ${ruby}" tasks: - name: "test-mlaunch" @@ -1083,10 +1410,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.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: ubuntu1804 + os: rhel8 display_name: "${mongodb-version} ${topology} solo ${ruby}" tasks: - name: "test-mlaunch" @@ -1097,7 +1424,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" @@ -1105,10 +1432,10 @@ buildvariants: - matrix_name: "stress" matrix_spec: stress: on - ruby: ["ruby-2.7"] - mongodb-version: ['5.0', '4.4'] + ruby: "ruby-3.2" + mongodb-version: ["6.0", "5.3"] topology: replica-set - os: ubuntu2004 + os: rhel8 display_name: "${mongodb-version} ${topology} stress ${ruby}" tasks: - name: "test-mlaunch" @@ -1116,363 +1443,173 @@ buildvariants: - matrix_name: "x509-tests" matrix_spec: auth-and-ssl: "x509" - ruby: "ruby-2.7" - mongodb-version: "4.2" + 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: ubuntu1804 + 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: ["standalone", "replica-set", "sharded-cluster"] - os: rhel80 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "jruby-noauth" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "jruby-9.2" - mongodb-version: "3.6" + auth-and-ssl: [ "auth-and-ssl", "noauth-and-nossl" ] + ruby: jruby-9.4 + mongodb-version: "7.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: "zlib-auth" + - matrix_name: "zlib" 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.2", "ruby-2.7", "jruby-9.4"] + mongodb-version: "7.0" topology: "replica-set" compressor: 'zlib' - os: ubuntu1804 + os: rhel8 display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "zlib-auth-3.0" + - matrix_name: "snappy" 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 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "snappy-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.2", "ruby-2.7", "jruby-9.4"] + mongodb-version: "7.0" topology: "replica-set" compressor: 'snappy' - os: ubuntu1804 - 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 + os: rhel8 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.2", "ruby-2.7"] + mongodb-version: "7.0" 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-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 - 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.2", "ruby-2.7", "jruby-9.4"] + mongodb-version: "7.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.2", "ruby-2.7", "jruby-9.4"] + mongodb-version: "7.0" topology: replica-set bson: "*" - os: rhel80 - display_name: "AS ${mongodb-version} ${topology} ${ruby} ${bson}" + os: rhel8 + display_name: "bson-${bson} ${mongodb-version} ${topology} ${ruby}" 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.2", "ruby-2.7", "jruby-9.4"] + # 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.2" + mongodb-version: "7.0" topology: standalone - os: ubuntu1804 + os: rhel8 auth-and-ssl: kerberos display_name: "Kerberos Tests" tasks: - name: "test-kerberos" - - matrix_name: "fle-6.0" + - matrix_name: "fle-latest" matrix_spec: auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1, ruby-3.0, ruby-2.7] + ruby: "ruby-3.2" topology: [replica-set, sharded-cluster] - mongodb-version: ['6.0'] - os: ubuntu2004 - fle: helper - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle" - - - matrix_name: "fle-4.4" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1] - topology: standalone - mongodb-version: ['4.4'] - os: ubuntu1804 + mongodb-version: [ 'latest', '7.0' ] + os: rhel8 fle: helper - display_name: "FLE: ${mongodb-version} ${ruby}" + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" tasks: - name: "test-fle" - - matrix_name: "fle-4.2" + - matrix_name: "fle" matrix_spec: auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-2.5] - topology: [standalone, replica-set, sharded-cluster] - mongodb-version: ["4.2"] - os: ubuntu1804 + ruby: ["ruby-3.2", "ruby-2.7"] + topology: [replica-set, sharded-cluster] + mongodb-version: [ '6.0', '4.4', '4.2', '4.0' ] + os: rhel8 fle: helper - display_name: "FLE: ${topology} ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle" - - - matrix_name: "fle-4.0" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-2.6] - topology: standalone - mongodb-version: ["4.0"] - os: ubuntu1804 - fle: path - display_name: "FLE: ${mongodb-version} ${ruby}" + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" 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" - - matrix_name: aws-auth-regular matrix_spec: - auth-and-ssl: aws-regular - ruby: [ruby-2.7] - topology: standalone - mongodb-version: '4.4' - os: ubuntu1804 - display_name: "AWS auth regular: ${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] - ruby: [ruby-3.1] + auth-and-ssl: [ aws-regular, aws-assume-role, aws-ec2, aws-ecs, aws-web-identity ] + ruby: "ruby-3.2" topology: standalone - mongodb-version: '4.4' - os: ubuntu1804 - display_name: "AWS auth temporary: ${auth-and-ssl} ${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: ubuntu2004 + display_name: "AWS ${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_spec: - ocsp-verifier: true - ruby: 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_name: ocsp-verifier matrix_spec: ocsp-verifier: true - ruby: ruby-2.7 + # 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"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 - docker-distro: debian10 - display_name: 'OCSP verifier: ${mongodb-version} ${ruby}' - tasks: *1 - + mongodb-version: "7.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.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 auth-and-ssl: noauth-and-ssl display_name: "OCSP integration - must staple: ${mongodb-version} ${ruby}" tasks: @@ -1482,10 +1619,10 @@ buildvariants: matrix_spec: ocsp-algorithm: rsa ocsp-status: unknown - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 auth-and-ssl: noauth-and-ssl display_name: "OCSP integration - unknown: ${mongodb-version} ${ruby}" tasks: @@ -1498,10 +1635,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "none" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1512,10 +1649,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "none" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1526,10 +1663,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: fail extra-uri-options: "none" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1540,10 +1677,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1554,10 +1691,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1568,10 +1705,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsInsecure=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1582,10 +1719,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1596,10 +1733,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1610,10 +1747,10 @@ buildvariants: ocsp-delegate: '*' ocsp-connectivity: pass extra-uri-options: "tlsAllowInvalidCertificates=true" - ruby: [ruby-3.0, ruby-2.7] + ruby: ["ruby-3.2", "ruby-2.7"] topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.0" + os: rhel8 display_name: "OCSP connectivity: ${ocsp-algorithm} ${ocsp-status} ${ocsp-delegate} ${extra-uri-options} ${mongodb-version} ${ruby}" tasks: - name: test-mlaunch @@ -1630,10 +1767,81 @@ buildvariants: ocsp-status: [valid, unknown] ocsp-delegate: '*' ocsp-connectivity: pass - ruby: jruby-9.2 + ruby: jruby-9.4 topology: standalone - mongodb-version: '4.4' - os: ubuntu2004 + mongodb-version: "7.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.2" + fle: helper + topology: standalone + os: rhel8 + mongodb-version: "7.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 + + - matrix_name: testazurekms-variant + matrix_spec: + ruby: ruby-3.0 + fle: helper + topology: standalone + os: debian11 # could eventually look at updating this to rhel80 + 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-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", "jruby-9.4", "jruby-9.3"] + os: rhel8 + display_name: "Atlas tests ${ruby}" + tasks: + - name: test-atlas + +# 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", "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}" +# 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", "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}" +# tasks: +# - name: serverless_next_task_group + + - 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/atlas.yml.erb b/.evergreen/config/atlas.yml.erb deleted file mode 100644 index bc7f63a1ac..0000000000 --- a/.evergreen/config/atlas.yml.erb +++ /dev/null @@ -1,18 +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"] - display_name: "Atlas serverless ${ruby} single mongos" - run_on: - - ubuntu1804-small - tasks: - - name: test-serverless diff --git a/.evergreen/config/axes.yml.erb b/.evergreen/config/axes.yml.erb index ad4d8b25b9..c433cf9c22 100644 --- a/.evergreen/config/axes.yml.erb +++ b/.evergreen/config/axes.yml.erb @@ -17,6 +17,11 @@ axes: display_name: "latest" 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: @@ -25,18 +30,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: @@ -125,10 +134,22 @@ 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 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: @@ -141,53 +162,42 @@ 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: 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: ubuntu2204 + display_name: "Ubuntu 22.04" + run_on: ubuntu2204-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: 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).each do |distro| %> + <% %w(debian11 ubuntu2204).each do |distro| %> - id: <%= distro %> display_name: <%= distro %> variables: @@ -287,7 +297,7 @@ axes: values: - id: mmapv1 display_name: MMAPv1 - run_on: ubuntu1604-small + run_on: rhel80-small variables: MMAPV1: 'true' diff --git a/.evergreen/config/common.yml.erb b/.evergreen/config/common.yml.erb index b3bde64c2c..1189b987eb 100644 --- a/.evergreen/config/common.yml.erb +++ b/.evergreen/config/common.yml.erb @@ -11,13 +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" %> +exec_timeout_secs: 3600 # What to do when evergreen hits the timeout (`post:` tasks are run automatically) timeout: @@ -40,21 +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 - - <% if @atlas %> "create expansions": # Make an evergreen expansion file with dynamic values - command: shell.exec @@ -68,71 +47,7 @@ functions: CURRENT_VERSION=latest fi - export DRIVERS_TOOLS="$(pwd)/../drivers-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}" - EOT - # See what we've done - cat expansion.yml - <% else %> - "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)/../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 @@ -173,6 +88,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} @@ -195,11 +111,22 @@ functions: export OCSP_MUST_STAPLE="${OCSP_MUST_STAPLE}" 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_ATLAS_USER="${SERVERLESS_ATLAS_USER}" + export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" EOT # See what we've done cat expansion.yml - <% end %> # Load the expansion file to make an evergreen variable with the current # unique version @@ -207,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 @@ -239,7 +155,14 @@ 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}" + 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": @@ -421,7 +344,20 @@ 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 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 @@ -431,7 +367,14 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - <%= egos_wrap %> .evergreen/run-tests.sh + # 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": - command: shell.exec @@ -441,7 +384,14 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - <%= egos_wrap %> .evergreen/run-tests-docker.sh + # 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": - command: shell.exec @@ -451,7 +401,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 @@ -461,7 +411,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 @@ -471,7 +421,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 @@ -484,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: @@ -506,11 +441,18 @@ 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 + # 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: - func: "fetch source" - - func: "fetch egos" - func: "create expansions" post: @@ -520,26 +462,259 @@ post: - func: "upload mo artifacts" #- func: "upload test results" - func: "upload test results to s3" - <% if @atlas %> - - func: "delete serverless instance" - <% end %> -tasks: +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 + 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 + + echo "MONGODB_URI=${MONGODB_URI}" + - 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-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 + 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 + + - 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 - <% if @atlas %> +tasks: - name: "test-atlas" commands: - func: "run Atlas tests" - name: "test-serverless" commands: + - func: "export FLE credentials" - func: "run serverless tests" - <% else %> - name: "test-docker" commands: - func: "build and test docker image" - name: "test-mlaunch" commands: - func: "run tests" + - name: "driver-bench" + commands: + - func: "run benchmarks" - name: "test-via-docker" commands: - func: "run tests via docker" @@ -562,4 +737,107 @@ tasks: commands: - func: "export AWS auth credentials" - func: "run AWS auth tests" - <% end %> + - 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 + 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 + - 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 + + - 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 e2e73448cb..11c75425da 100644 --- a/.evergreen/config/standard.yml.erb +++ b/.evergreen/config/standard.yml.erb @@ -1,148 +1,143 @@ -<% 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" + # 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.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.4 ) + + # older Ruby versions provided by 10gen/mongo-ruby-toolchain + older_rubies = %w( ruby-3.0 ruby-2.7 ) + + # all supported JRuby versions provided by 10gen/mongo-ruby-toolchain + jrubies = %w( jruby-9.4 jruby-9.3 ) + + supported_mri_rubies = %w( ruby-3.2 ruby-3.1 ruby-3.0 + ruby-2.7 ) - - matrix_name: "docker" + supported_rubies = supported_mri_rubies + jrubies + + # The latest stable version of MongoDB + 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 +%> + +buildvariants: + - matrix_name: DriverBench matrix_spec: - ruby: ["ruby-2.7"] - mongodb-version: "4.2" + ruby: <%= latest_ruby %> + mongodb-version: <%= latest_stable_mdb %> topology: standalone - os: ubuntu1804 - preload: nopreload - display_name: "Docker ${os} ${mongodb-version} ${topology} ${ruby} ${preload}" + run_on: rhel80-large + display_name: DriverBench tasks: - - name: "test-docker" + - name: "driver-bench" - - 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'] - topology: <%= tt %> - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + ruby: <%= latest_ruby %> + mongodb-version: <%= actual_and_upcoming_mdb %> + topology: <%= topologies %> + os: rhel8 + display_name: ${auth-and-ssl} ${ruby} db-${mongodb-version} ${topology} tasks: - name: "test-mlaunch" - - matrix_name: "mongo-6.0" + - matrix_name: "mongo-recent" matrix_spec: - auth-and-ssl: ["auth-and-ssl", "noauth-and-nossl"] - ruby: ["ruby-3.1"] - mongodb-version: ['6.0'] - topology: <%= tt %> - os: ubuntu2004 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" + ruby: <%= recent_rubies %> + mongodb-version: <%= actual_and_upcoming_mdb %> + topology: <%= topologies %> + 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" - - matrix_name: "mongo-5.3" + - 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: <%= actual_and_upcoming_mdb %> + 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" + ruby: "ruby-2.7" 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 +145,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,32 +156,32 @@ 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" - matrix_name: mmapv1 matrix_spec: - ruby: "ruby-2.5" + ruby: "ruby-2.7" 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 +189,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 +200,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 +214,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,10 +222,10 @@ 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" @@ -238,343 +233,173 @@ buildvariants: - matrix_name: "x509-tests" matrix_spec: auth-and-ssl: "x509" - ruby: "ruby-2.7" - mongodb-version: "4.2" + 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: ubuntu1804 + 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 - display_name: "${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "jruby-noauth" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: "jruby-9.2" - mongodb-version: "3.6" - topology: <%= tt %> - os: ubuntu1804 + 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: "zlib-auth" + - matrix_name: "zlib" 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 + os: rhel8 display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" tasks: - name: "test-mlaunch" - - matrix_name: "zlib-auth-3.0" + - matrix_name: "snappy" 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 - display_name: "${compressor} ${mongodb-version} ${topology} ${auth-and-ssl} ${ruby}" - tasks: - - name: "test-mlaunch" - - - matrix_name: "snappy-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: 'snappy' - os: ubuntu1804 - 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 + os: rhel8 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 - 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: <%= 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 - display_name: "AS ${mongodb-version} ${topology} ${ruby} ${bson}" + os: rhel8 + display_name: "bson-${bson} ${mongodb-version} ${topology} ${ruby}" 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-6.0" + - matrix_name: "fle-latest" matrix_spec: auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1, ruby-3.0, ruby-2.7] + ruby: <%= latest_ruby %> topology: [replica-set, sharded-cluster] - mongodb-version: ['6.0'] - os: ubuntu2004 + mongodb-version: [ 'latest', '7.0' ] + os: rhel8 fle: helper - display_name: "FLE: ${mongodb-version} ${ruby}" + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" tasks: - name: "test-fle" - - matrix_name: "fle-4.4" + - matrix_name: "fle" matrix_spec: auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-3.1] - topology: standalone - mongodb-version: ['4.4'] - os: ubuntu1804 - fle: helper - display_name: "FLE: ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle" - - - 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: ubuntu1804 + ruby: <%= sample_mri_rubies %> + topology: [replica-set, sharded-cluster] + mongodb-version: [ '6.0', '4.4', '4.2', '4.0' ] + os: rhel8 fle: helper - display_name: "FLE: ${topology} ${mongodb-version} ${ruby}" - tasks: - - name: "test-fle" - - - matrix_name: "fle-4.0" - matrix_spec: - auth-and-ssl: "noauth-and-nossl" - ruby: [ruby-2.6] - topology: standalone - mongodb-version: ["4.0"] - os: ubuntu1804 - fle: path - display_name: "FLE: ${mongodb-version} ${ruby}" + display_name: "FLE: ${mongodb-version} ${topology} ${ruby}" 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" - - matrix_name: aws-auth-regular matrix_spec: - auth-and-ssl: aws-regular - ruby: [ruby-2.7] - topology: standalone - mongodb-version: '4.4' - os: ubuntu1804 - display_name: "AWS auth regular: ${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] - ruby: [ruby-3.1] + 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 temporary: ${auth-and-ssl} ${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: ubuntu2004 + display_name: "AWS ${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: @@ -584,10 +409,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: @@ -614,10 +439,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 @@ -635,10 +460,81 @@ 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: <%= latest_ruby %> + fle: helper + topology: standalone + os: rhel8 + mongodb-version: <%= latest_stable_mdb %> + display_name: "GCP KMS" + tasks: + - 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 # could eventually look at updating this to rhel80 + 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-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 %> + os: rhel8 + display_name: "Atlas tests ${ruby}" + tasks: + - name: test-atlas + +# 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: + ruby: <%= latest_ruby %> + os: ubuntu2204 + display_name: "AWS Lambda" + tasks: + - name: test_aws_lambda_task_group 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/.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-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-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/.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-aws-auth.sh b/.evergreen/run-tests-aws-auth.sh index b2f7d52a0e..c9649e4a64 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,44 @@ 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 + + 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 + + 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 + + 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`" + 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-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-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/.evergreen/run-tests-docker.sh b/.evergreen/run-tests-docker.sh index f3605ebb96..b04c48de5d 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 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 new file mode 100755 index 0000000000..baccf6404b --- /dev/null +++ b/.evergreen/run-tests-gcp.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +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 +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_gcp_credentials_spec.rb + +exit ${test_status} 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 4f96a3ea33..cd71e41d6e 100755 --- a/.evergreen/run-tests-serverless.sh +++ b/.evergreen/run-tests-serverless.sh @@ -16,16 +16,91 @@ export MONGODB_URI=`echo ${SERVERLESS_URI} | sed -r 's/mongodb\+srv:\/\//mongodb export TOPOLOGY="load-balanced" +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 + 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 + 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 always use rhel80 +export LIBMONGOCRYPT_PATH=`pwd`/rhel-80-64-bit/nocrypto/lib64/libmongocrypt.so +cd - + +cd .evergreen/csfle +. ./activate-kmstlsvenv.sh + +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 & +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() { + 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 +pip3 install boto3 +PYTHON=python3 . ./set-temp-creds.sh +cd - + echo "Running specs" bundle exec rspec \ + spec/spec_tests/client_side_encryption_spec.rb \ 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 +# Terminate all kmip servers... and whatever else happens to be running +# that is a python script. +pkill python exit ${test_status} diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 0e07e1f6fd..a2203eb79a 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -44,7 +44,13 @@ set_env_ruby prepare_server $arch if test "$DOCKER_PRELOAD" != 1; then - install_mlaunch_virtualenv + 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 @@ -153,6 +159,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 @@ -160,14 +170,24 @@ 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=${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 # fetching libmongocrypt. if test "$DOCKER_PRELOAD" != 1; then @@ -179,13 +199,17 @@ 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 & 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 if test "$FLE" = helper; then echo "Using helper gem" @@ -238,6 +262,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/.evergreen/update-evergreen-configs b/.evergreen/update-evergreen-configs index 8a070e8e5f..e2f205ef55 100755 --- a/.evergreen/update-evergreen-configs +++ b/.evergreen/update-evergreen-configs @@ -8,15 +8,12 @@ autoload :YAML, 'yaml' class Runner def run transform('config.yml') - - @atlas = true - transform('config-atlas.yml') end 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 @@ -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| @@ -44,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/.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/.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/.github/workflows/test.yml b/.github/workflows/test.yml index 28c7dd342f..e3f468fe8b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,20 +10,16 @@ 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 ] - ruby: ["2.5", "2.6", "2.7", "3.0", "3.1"] - mongodb: ["3.6", "4.4", "5.0"] + os: [ ubuntu-20.04 ] + 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,28 +28,20 @@ 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" 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 - ruby: "2.5" - 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/.gitignore b/.gitignore index 2d4d2b4126..309742c33c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,16 @@ gem-private_key.pem nbproject tmp sandbox/* +data/* .byebug_history gemfiles/*.gemfile.lock .env.private* .env +build +profile/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/.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/.mod/drivers-evergreen-tools b/.mod/drivers-evergreen-tools index 36de96a1f6..8cb042bf7f 160000 --- a/.mod/drivers-evergreen-tools +++ b/.mod/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit 36de96a1f65c6471d150d69aef7ea8b7f1e9ddc9 +Subproject commit 8cb042bf7fda753ec19eda19f02c441d4220a8dc diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..fa7d04f8c6 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,112 @@ +require: + - rubocop-performance + - rubocop-rake + - rubocop-rspec + +AllCops: + TargetRubyVersion: 2.5 + NewCops: enable + Exclude: + - 'spec/shared/**/*' + - 'spec/faas/**/*' + - 'vendor/**/*' + +Bundler: + Enabled: true + +Gemspec: + Enabled: true + +Layout: + Enabled: true + +Lint: + Enabled: true + +Metrics: + Enabled: true + +Naming: + Enabled: true + +Security: + Enabled: true + +Style: + Enabled: true + +# -------------------------------------- +# Cops below this line set intentionally +# -------------------------------------- + +Bundler/OrderedGems: + Enabled: false + +Gemspec/OrderedDependencies: + Enabled: false + +Layout/SpaceInsideArrayLiteralBrackets: + EnforcedStyle: space + +Layout/SpaceInsidePercentLiteralDelimiters: + Enabled: false + +Metrics/ClassLength: + Max: 200 + +Metrics/ModuleLength: + Enabled: false + +Metrics/MethodLength: + Max: 20 + +Naming/MethodParameterName: + AllowedNames: [ id, op ] + +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 + +Style/FetchEnvVar: + Enabled: false + +RSpec/ImplicitExpect: + EnforcedStyle: is_expected + +RSpec/MultipleExpectations: + Enabled: false + +RSpec/MultipleMemoizedHelpers: + Enabled: false + +RSpec/NestedGroups: + Enabled: false + +Style/Documentation: + Exclude: + - 'spec/**/*' + +Style/FormatStringToken: + Enabled: false + +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/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/README.md b/README.md index dee1bf8bc9..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 @@ -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 diff --git a/Rakefile b/Rakefile index 88125f1be9..627e39389a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,5 @@ -# -*- mode: ruby -*- +# frozen_string_literal: true +# rubocop:todo all require 'bundler' require 'bundler/gem_tasks' @@ -61,13 +62,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! @@ -118,169 +119,42 @@ end task :release => ['release:check_private_key', 'release:do'] -desc "Generate all documentation" -task :docs => 'docs:yard' +desc 'Build and validate the evergreen config' +task eg: %w[ eg:build eg:validate ] -namespace :docs do - desc "Generate yard documention" - task :yard do - out = File.join('yard-docs', Mongo::VERSION) - FileUtils.rm_rf(out) - system "yardoc -o #{out} --title mongo-#{Mongo::VERSION}" +# '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 -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 driver benchmark tests." - - namespace :micro do - desc "Run the common driver micro benchmarking tests" - - 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) - 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) - 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 "MICRO BENCHMARK:: DEEP:: ENCODE" - Mongo::Benchmarking::Micro.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) - 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 "MICRO BENCHMARK:: FULL:: ENCODE" - Mongo::Benchmarking::Micro.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) - end - end + desc 'Validates the .evergreen/config.yml file' + task :validate do + system 'evergreen validate --project mongo-ruby-driver .evergreen/config.yml' 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 + desc 'Updates the evergreen executable to the latest available version' + task :update do + system 'evergreen get-update --install' 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 + 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 - 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 +desc "Generate all documentation" +task :docs => 'docs:yard' - # 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 +namespace :docs do + desc "Generate yard documention" + task :yard do + out = File.join('yard-docs', Mongo::VERSION) + FileUtils.rm_rf(out) + system "yardoc -o #{out} --title mongo-#{Mongo::VERSION}" end end + +load 'profile/driver_bench/rake/tasks.rake' 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/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/index.txt b/docs/index.txt index e126a4d50a..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 :doc:`/bson-tutorials`. +For reference on the Ruby BSON gem, see the :doc:`/tutorials/bson`. 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/authentication.txt b/docs/reference/authentication.txt index f6e7e33619..0c30c7056d 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. @@ -274,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 @@ -328,6 +335,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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -354,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 @@ -458,7 +479,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:: @@ -475,7 +496,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/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 b410430648..346aab93fd 100644 --- a/docs/reference/create-client.txt +++ b/docs/reference/create-client.txt @@ -327,7 +327,48 @@ 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 + +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: @@ -534,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. @@ -641,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. @@ -896,6 +942,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**. @@ -976,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`` @@ -1026,11 +1076,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`` @@ -1341,7 +1391,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 @@ -1509,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 @@ -1553,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: @@ -1575,89 +1641,73 @@ 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 transfered - 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: + +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. + +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 +process (prior to the workers being forked), and instead only create client +instances in the workers. + +Manually Handling Process Forks +------------------------------- + +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. + +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 - client.reconnect + # Immediately before fork + client.close .. note:: - 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. + Calling ``Client#close`` does not disrupt database operations currently in-flight. + Clients will automatically reconnect when you perform new operations. -Reconnecting Client Instances ------------------------------ - -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: +Then, immediately after forking, reconnect your clients in the newly +forked child process, which will respawn the driver's monitoring threads. .. code-block:: ruby - client.close + # Immediately after fork 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. - -.. note:: - - When closing and reconnecting the client instance in the child, - due to file descriptor sharing, the parent process may experience network - and monitoring errors. - -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 ``on_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. @@ -1665,12 +1715,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/docs/reference/crud-operations.txt b/docs/reference/crud-operations.txt index 73262aa70b..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 @@ -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 @@ -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/driver-compatibility.txt b/docs/reference/driver-compatibility.txt index ce0af48381..a0aaea71af 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. @@ -38,9 +45,8 @@ version. :class: compatibility-large no-padding * - Ruby Driver + - MongoDB 7.0 - MongoDB 6.0 - - MongoDB 5.2 - - MongoDB 5.1 - MongoDB 5.0 - MongoDB 4.4 - MongoDB 4.2 @@ -51,11 +57,36 @@ version. - MongoDB 3.0 - MongoDB 2.6 - * - 2.18 + * - 2.20 + - |checkmark| + - |checkmark| + - |checkmark| - |checkmark| - |checkmark| - |checkmark| - |checkmark| + - + - + - + - + + * - 2.19 + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - |checkmark| + - + - + - + - + + * - 2.18 + - + - |checkmark| + - |checkmark| - |checkmark| - |checkmark| - |checkmark| @@ -73,14 +104,12 @@ version. - |checkmark| - |checkmark| - |checkmark| - - |checkmark| - - - - * - 2.16 - - - - - |checkmark| @@ -97,7 +126,6 @@ version. - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -111,7 +139,6 @@ version. - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -125,7 +152,6 @@ version. - - - - - - |checkmark| [#ocsp]_ - |checkmark| - |checkmark| @@ -140,7 +166,6 @@ version. - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -154,7 +179,6 @@ version. - - - - - - |checkmark| [#client-side-encryption]_ - |checkmark| - |checkmark| @@ -168,7 +192,6 @@ version. - - - - - - |checkmark| [#srv-polling]_ [#client-side-encryption]_ - |checkmark| - |checkmark| @@ -183,7 +206,6 @@ version. - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -197,7 +219,6 @@ version. - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -211,7 +232,6 @@ version. - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -225,7 +245,6 @@ version. - - - - - - |checkmark| - |checkmark| - |checkmark| @@ -240,69 +259,12 @@ version. - - - - - - |checkmark| - |checkmark| - |checkmark| - |checkmark| - |checkmark| - * - 2.4 - - - - - - - - - - - - - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - |checkmark| - - * - 2.3 - - - - - - - - - - - - - - - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - * - 2.2 - - - - - - - - - - - - - - - - - - - - |checkmark| - - |checkmark| - - |checkmark| - - * - 2.0 - - - - - - - - - - - - - - - - - - - - - - |checkmark| - - |checkmark| - .. [#ocsp] OCSP verification is implemented as of driver version 2.14. .. [#srv-polling] Polling of SRV records in sharded topologies is @@ -331,6 +293,8 @@ 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 - Ruby 2.6 @@ -341,15 +305,55 @@ 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| + - |checkmark| + - |checkmark| + - D + - D + - + - + - + - + - + - + - + - |checkmark| + - |checkmark| + - + * - 2.18 - |checkmark| - |checkmark| - |checkmark| - |checkmark| + - |checkmark| + - |checkmark| + - - - - @@ -361,10 +365,13 @@ for that Ruby version is deprecated. - * - 2.17 + - - |checkmark| - |checkmark| - |checkmark| - |checkmark| + - |checkmark| + - - - - @@ -376,6 +383,8 @@ for that Ruby version is deprecated. - * - 2.16 + - + - - |checkmark| - |checkmark| - |checkmark| @@ -387,10 +396,13 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - * - 2.15 + - + - - |checkmark| - |checkmark| - |checkmark| @@ -402,10 +414,13 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - * - 2.14 + - + - - - |checkmark| - |checkmark| @@ -417,10 +432,13 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - * - 2.13 + - + - - - |checkmark| - |checkmark| @@ -432,10 +450,13 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - * - 2.12 + - + - - - |checkmark| - |checkmark| @@ -447,10 +468,13 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - * - 2.11 + - + - - - |checkmark| - |checkmark| @@ -462,10 +486,13 @@ for that Ruby version is deprecated. - - - + - - |checkmark| - * - 2.10 + - + - - - |checkmark| - |checkmark| @@ -477,10 +504,13 @@ for that Ruby version is deprecated. - D - D - + - - |checkmark| - |checkmark| * - 2.9 + - + - - - - |checkmark| @@ -492,10 +522,13 @@ for that Ruby version is deprecated. - D - D - + - - |checkmark| - |checkmark| * - 2.8 + - + - - - - |checkmark| @@ -507,10 +540,13 @@ for that Ruby version is deprecated. - |checkmark| - |checkmark| - + - - |checkmark| - |checkmark| * - 2.7 + - + - - - - |checkmark| @@ -522,10 +558,13 @@ for that Ruby version is deprecated. - |checkmark| - |checkmark| - + - - |checkmark| - |checkmark| * - 2.6 + - + - - - - |checkmark| @@ -537,6 +576,7 @@ for that Ruby version is deprecated. - |checkmark| - |checkmark| - + - - |checkmark| - |checkmark| @@ -633,7 +673,7 @@ the driver. * - Ruby Driver - mongo_kerberos |nbsp| 2.1 - * - 2.7 - 2.16 + * - 2.7 - 2.19 - |checkmark| 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/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/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 78% rename from docs/reference/client-side-encryption.txt rename to docs/reference/in-use-encryption/client-side-encryption.txt index 8191c226fb..78d1ca73c9 100644 --- a/docs/reference/client-side-encryption.txt +++ b/docs/reference/in-use-encryption/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,232 +372,10 @@ 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`_, - -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: - -- `Creating A Master Key`_ -- `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' +- :ref:`Creating A Master Key ` +- :ref:`Creating A Data Key ` +.. _creating-a-master-key: Creating a Master Key ===================== @@ -608,6 +386,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 +406,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 +463,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 +572,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 @@ -853,7 +637,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 +648,67 @@ 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. + +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`` ~~~~~~~~~~~~~~~~~~~~ @@ -974,7 +814,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: @@ -1012,6 +852,8 @@ decryption of any previously-encrypted data. } ) +.. _cse-extra-options: + ``:extra_options`` ~~~~~~~~~~~~~~~~~~ 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/monitoring.txt b/docs/reference/monitoring.txt index 1721feb6a6..06328456a4 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) @@ -287,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 @@ -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 @@ -432,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. diff --git a/docs/reference/schema-operations.txt b/docs/reference/schema-operations.txt index 713db0afc4..8d67d569fb 100644 --- a/docs/reference/schema-operations.txt +++ b/docs/reference/schema-operations.txt @@ -12,7 +12,8 @@ including managing databases, collections, indexes and users. .. toctree:: :titlesonly: - database-tasks - collection-tasks - indexing - collations + /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/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 ================ 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/reference/working-with-data.txt b/docs/reference/working-with-data.txt index d6d76f82ed..0f36c6be47 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/in-use-encryption diff --git a/docs/release-notes.txt b/docs/release-notes.txt index a4fa945f7b..33f251ff6f 100644 --- a/docs/release-notes.txt +++ b/docs/release-notes.txt @@ -18,6 +18,59 @@ 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 +==== + +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: + +- 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 + 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. +- Added support for creating and managing `Atlas search indexes `_ via the + Ruby driver. + .. _release-notes-2.18: 2.18 @@ -51,13 +104,13 @@ 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, 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 00d8ce7b1e..0972fba7f1 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 + diff --git a/docs/tutorials/common-errors.txt b/docs/tutorials/common-errors.txt index 4cb82b9fb6..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: @@ -109,12 +111,12 @@ 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`` file. See more on this issue here: -`RUBY-1281 `_. +`RUBY-1684 `_. 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. 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/gem-public_cert.pem b/gem-public_cert.pem index 40d6d3807b..68fa1da731 100644 --- a/gem-public_cert.pem +++ b/gem-public_cert.pem @@ -1,21 +1,26 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- 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 117366c1b3..f29b91cb4b 100644 --- a/gemfiles/standard.rb +++ b/gemfiles/standard.rb @@ -1,16 +1,14 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength def standard_dependencies - gem 'yard' + gem 'yard', '>= 0.9.35' gem 'ffi' 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' @@ -23,12 +21,22 @@ def standard_dependencies gem 'aws-sdk-ec2' gem 'aws-sdk-ecs' gem 'aws-sdk-iam' + gem 'aws-sdk-sts' gem 'paint' # for benchmark tests gem 'yajl-ruby', platforms: :mri, require: false gem 'celluloid', platforms: :mri, require: false + # 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. if !ENV['CI'] && !ENV['DOCKER'] && RUBY_VERSION < '3.0' @@ -43,8 +51,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 @@ -57,9 +63,10 @@ def standard_dependencies gem 'ruby-prof', platforms: :mri gem 'erubi' gem 'tilt' + # solargraph depends on rbs, which won't build on jruby for some reason + gem 'solargraph', platforms: :mri end - if ENV['FLE'] == 'helper' - gem 'libmongocrypt-helper', '~> 1.5.2' - end + gem 'libmongocrypt-helper', '~> 1.8.0' if ENV['FLE'] == 'helper' end +# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength 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 b32f4553d2..b90cd4a011 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. # @@ -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' @@ -63,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/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 eb46c98b90..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. # @@ -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/conversation.rb b/lib/mongo/auth/aws/conversation.rb index 8fce263ddb..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. # @@ -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/operation/drop_database/command.rb b/lib/mongo/auth/aws/credentials.rb similarity index 53% rename from lib/mongo/operation/drop_database/command.rb rename to lib/mongo/auth/aws/credentials.rb index 983090af79..e4b94a1f44 100644 --- a/lib/mongo/operation/drop_database/command.rb +++ b/lib/mongo/auth/aws/credentials.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all -# Copyright (C) 2018-2020 MongoDB Inc. +# 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. @@ -16,24 +16,21 @@ # limitations under the License. module Mongo - module Operation - class DropDatabase - - # A MongoDB dropdatabase operation sent as a command message. + module Auth + class Aws + # The AWS credential set. # # @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)) + 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 diff --git a/lib/mongo/auth/aws/credentials_cache.rb b/lib/mongo/auth/aws/credentials_cache.rb new file mode 100644 index 0000000000..75ccae794e --- /dev/null +++ b/lib/mongo/auth/aws/credentials_cache.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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 68092aff9c..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. # @@ -18,6 +18,14 @@ 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, and Web Identity)") + end + end # Retrieves AWS credentials from a variety of sources. # @@ -27,6 +35,7 @@ class Aws # - 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 # @@ -41,15 +50,17 @@ class Aws # # @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. @@ -60,49 +71,76 @@ 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 = ecs_metadata_credentials + raise Auth::Aws::CredentialsNotFound + end - if credentials && credentials_valid?(credentials, 'ECS task metadata') - return credentials - end + private - credentials = ec2_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, 'EC2 instance 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 - raise Auth::InvalidConfiguration, - "Could not locate AWS credentials (checked Client URI and Ruby options, environment variables, ECS and EC2 metadata)" + # 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 - private + # 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 + end # Returns credentials from the EC2 metadata endpoint. The credentials # could be empty, partial or invalid. @@ -143,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 @@ -175,11 +214,107 @@ 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 + # 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'], + Time.at(payload['Expiration']) + ) + rescue JSON::ParserError, TypeError + 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/lib/mongo/auth/aws/request.rb b/lib/mongo/auth/aws/request.rb index 47d8fc3113..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. # @@ -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/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 8f9e00a955..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. # @@ -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/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 6aae3aa520..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. # @@ -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/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 6953fe9588..e06231d4ad 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. # @@ -71,6 +71,7 @@ class Client :local_threshold, :logger, :log_prefix, + :max_connecting, :max_idle_time, :max_pool_size, :max_read_retries, @@ -80,6 +81,7 @@ class Client :monitoring_io, :password, :platform, + :populator_io, :read, :read_concern, :read_retry_interval, @@ -265,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 @@ -462,6 +470,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 @@ -590,9 +604,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 @@ -853,6 +867,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. @@ -860,6 +878,7 @@ def write_concern # @since 2.1.0 def close @connect_lock.synchronize do + @closed = true do_close end true @@ -893,6 +912,8 @@ def reconnect if @options[:auto_encryption_options] build_encrypter end + + @closed = false end true @@ -1127,6 +1148,9 @@ def get_session(options = {}) # # @api private def with_session(options = {}, &block) + # TODO: Add this back in RUBY-3174. + # assert_not_closed + session = get_session(options) yield session @@ -1189,7 +1213,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 @@ -1301,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) @@ -1563,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] @@ -1570,7 +1612,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] @@ -1582,5 +1624,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/client_encryption.rb b/lib/mongo/client_encryption.rb index 364c63fdf2..5390048edd 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. # @@ -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) @@ -205,5 +245,66 @@ 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. + # + # @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/lib/mongo/cluster.rb b/lib/mongo/cluster.rb index a543e22829..eac6d6229d 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. # @@ -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' @@ -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. @@ -263,10 +265,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 @@ -497,13 +499,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 +521,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 +536,7 @@ def disconnect! @connecting = @connected = false end end - true + nil end # Reconnect all servers. @@ -622,10 +627,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 = {}) @@ -636,7 +643,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 @@ -656,7 +665,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 @@ -697,12 +708,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) @@ -824,6 +839,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 @@ -927,6 +945,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, @@ -1065,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/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 1ff8b0e7df..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. # @@ -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..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. # @@ -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 62fcd9d47e..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. # @@ -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 @@ -365,7 +373,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) @@ -414,6 +421,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 @@ -574,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/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..999d14bf75 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. # @@ -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/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 8fe69fbbc0..a2b2076b7d 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. # @@ -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) @@ -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 = {}) @@ -133,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 @@ -145,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 @@ -157,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 @@ -169,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 @@ -182,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 @@ -204,17 +257,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 @@ -237,11 +310,13 @@ 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? - 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. @@ -251,30 +326,65 @@ 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 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 + # 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. # # @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) @@ -318,8 +428,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 +468,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 +514,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,13 +528,13 @@ 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. # @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 = {}) @@ -615,13 +727,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. @@ -642,7 +776,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. # @@ -650,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 @@ -684,12 +824,18 @@ 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 + # 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. # @@ -706,14 +852,14 @@ 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 + # @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 +948,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 +975,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 +1004,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 +1073,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 +1113,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. 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..8beb17c45f 100644 --- a/lib/mongo/collection/queryable_encryption.rb +++ b/lib/mongo/collection/queryable_encryption.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# encoding: utf-8 # 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,19 @@ 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? - 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) + 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| indexes.create_one(__safeContent__: 1) if result end @@ -73,31 +61,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 +94,69 @@ 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+). + # + # @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!(connection) + return unless connection.description.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/collection/view.rb b/lib/mongo/collection/view.rb index dd58ef89ca..dcc0f89752 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. # @@ -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/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 8aca8a99db..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. # @@ -164,7 +164,7 @@ def each document = try_next yield document if document end - rescue StopIteration => e + rescue StopIteration return self end @@ -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::MissingConnection # ignore end @cursor = nil 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..83ec0e458b 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. # @@ -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, @@ -185,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) @@ -200,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/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 388e448f45..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. # @@ -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/collection/view/writable.rb b/lib/mongo/collection/view/writable.rb index 22cf2d32f8..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. # @@ -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/lib/mongo/condition_variable.rb b/lib/mongo/condition_variable.rb new file mode 100644 index 0000000000..915e492f95 --- /dev/null +++ b/lib/mongo/condition_variable.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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/config.rb b/lib/mongo/config.rb index e3049a1695..73aaedbe25 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" @@ -15,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 @@ -23,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. 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 202def248b..5701824990 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. # @@ -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') @@ -35,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_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 31bdd68094..a98f7d93a8 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. # @@ -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 @@ -117,12 +118,13 @@ 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? || @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( @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] ) @@ -135,7 +137,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/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 de0746792b..906f76facc 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. # @@ -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.7.0") # @!method self.mongocrypt_version(len) # @api private @@ -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 @@ -717,6 +725,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 # @@ -775,13 +817,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 +1506,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 @@ -1543,6 +1696,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/context.rb b/lib/mongo/crypt/context.rb index 99d936f94b..5ace2b9dde 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. # @@ -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 @@ -47,6 +51,7 @@ def initialize(mongocrypt_handle, io) ) @encryption_io = io + @cached_azure_token = nil end attr_reader :ctx_p @@ -110,11 +115,13 @@ def run_state_machine end Binding.ctx_kms_done(self) + when :need_kms_credentials + Binding.ctx_provide_kms_providers( + self, + retrieve_kms_credentials.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 @@ -136,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/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 ef713a90fc..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. # @@ -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/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 e2256cb651..946f97d7bd 100644 --- a/lib/mongo/crypt/explicit_encrypter.rb +++ b/lib/mongo/crypt/explicit_encrypter.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# encoding: utf-8 # 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 @@ -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, @@ -44,7 +45,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 +109,66 @@ def encrypt(value, options) Crypt::ExplicitEncryptionContext.new( @crypt_handle, @encryption_io, - { 'v': value }, + { v: 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 @@ -120,10 +180,10 @@ def encrypt(value, 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 @@ -144,9 +204,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. # @@ -154,9 +212,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. # @@ -164,16 +220,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. # @@ -182,9 +234,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. @@ -198,11 +248,9 @@ def remove_key_alt_name(id, key_alt_name) # # @return [ Crypt::RewrapManyDataKeyResult ] Result of the operation. def rewrap_many_data_key(filter, opts = {}) - master_key_document = if opts[:provider] - options = opts.dup - provider = options.delete(:provider) - KMS::MasterKeyDocument.new(provider, options) - end + validate_rewrap_options!(opts) + + master_key_document = master_key_for_provider(opts) rewrap_result = Crypt::RewrapManyDataKeyContext.new( @crypt_handle, @@ -210,11 +258,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] }, @@ -228,9 +317,6 @@ def rewrap_many_data_key(filter, opts = {}) } } end - RewrapManyDataKeyResult.new( - @encryption_io.update_data_keys(updates) - ) end end end diff --git a/lib/mongo/crypt/explicit_encryption_context.rb b/lib/mongo/crypt/explicit_encryption_context.rb index 6dad1becc2..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. # @@ -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/operation/command/command.rb b/lib/mongo/crypt/explicit_encryption_expression_context.rb similarity index 54% rename from lib/mongo/operation/command/command.rb rename to lib/mongo/crypt/explicit_encryption_expression_context.rb index 88e0ea444b..5ca9a16510 100644 --- a/lib/mongo/operation/command/command.rb +++ b/lib/mongo/crypt/explicit_encryption_expression_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all -# Copyright (C) 2018-2020 MongoDB Inc. +# 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. @@ -16,25 +16,14 @@ # limitations under the License. module Mongo - module Operation - class Command + module Crypt - # 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 + # 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 diff --git a/lib/mongo/crypt/handle.rb b/lib/mongo/crypt/handle.rb index 47626184a8..b6cd0f7f67 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. # @@ -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? || @kms_providers.gcp&.empty? || @kms_providers.azure&.empty? + Binding.setopt_use_need_kms_credentials_state(self) + end initialize_mongocrypt 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 f871aa145b..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. # @@ -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 @@ -89,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/lib/mongo/crypt/kms/aws.rb b/lib/mongo/crypt/kms/aws.rb index a4f7fead88..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. # @@ -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..da15ed9eb6 --- /dev/null +++ b/lib/mongo/crypt/kms/aws/credentials.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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..f2110092af --- /dev/null +++ b/lib/mongo/crypt/kms/aws/master_document.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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..55e2eeb044 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. # @@ -15,130 +15,7 @@ # 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/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 new file mode 100644 index 0000000000..29609b1f77 --- /dev/null +++ b/lib/mongo/crypt/kms/azure/credentials.rb @@ -0,0 +1,103 @@ +# 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 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 + + # @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" }' + + # 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 + 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) + @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? + + 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 + end + 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..88787d4409 --- /dev/null +++ b/lib/mongo/crypt/kms/azure/credentials_retriever.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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/azure/master_document.rb b/lib/mongo/crypt/kms/azure/master_document.rb new file mode 100644 index 0000000000..a5b4edf274 --- /dev/null +++ b/lib/mongo/crypt/kms/azure/master_document.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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/credentials.rb b/lib/mongo/crypt/kms/credentials.rb index 4c3cae173f..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. # @@ -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/lib/mongo/crypt/kms/gcp.rb b/lib/mongo/crypt/kms/gcp.rb index 66461e69c9..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. # @@ -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..b39a878d97 --- /dev/null +++ b/lib/mongo/crypt/kms/gcp/credentials.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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..723cda43c4 --- /dev/null +++ b/lib/mongo/crypt/kms/gcp/credentials_retriever.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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.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) + 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}" + rescue ::Timeout::Error, IOError, SystemCallError, SocketError => e + raise KMS::CredentialsNotFound, + "Could not receive GCP metadata response; #{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..5d8af0cba5 --- /dev/null +++ b/lib/mongo/crypt/kms/gcp/master_document.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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..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. # @@ -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..41ce18cc3b --- /dev/null +++ b/lib/mongo/crypt/kms/kmip/credentials.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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..e63284fb1b --- /dev/null +++ b/lib/mongo/crypt/kms/kmip/master_document.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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..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. # @@ -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..c5d47777e1 --- /dev/null +++ b/lib/mongo/crypt/kms/local/credentials.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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..937ad07e5e --- /dev/null +++ b/lib/mongo/crypt/kms/local/master_document.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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/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 9254774ede..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. # @@ -76,13 +76,15 @@ 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 @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 @@ -276,9 +284,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,9 +299,14 @@ def close end nil + 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 @cursor_id = 0 + @lock.synchronize do + @explicitly_closed = true + end end # Get the parsed collection name. @@ -387,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 @@ -449,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 @@ -487,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/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 6ab9d2840f..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. # @@ -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..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. # @@ -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. # @@ -141,7 +144,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 @@ -172,7 +175,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 @@ -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/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 23e9cc0787..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. # @@ -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/error.rb b/lib/mongo/error.rb index c3fa0c89e2..92d6d5f4b3 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. # @@ -139,9 +139,11 @@ 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' +require 'mongo/error/connection_unavailable' require 'mongo/error/credential_check_error' require 'mongo/error/crypt_error' require 'mongo/error/extra_file_chunk' @@ -157,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' @@ -193,10 +196,13 @@ 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' +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' @@ -210,6 +216,8 @@ 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/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/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 new file mode 100644 index 0000000000..06a665bd49 --- /dev/null +++ b/lib/mongo/error/client_closed.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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/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 6ca8172944..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. # @@ -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/connection_unavailable.rb b/lib/mongo/error/connection_unavailable.rb new file mode 100644 index 0000000000..652fe33ae9 --- /dev/null +++ b/lib/mongo/error/connection_unavailable.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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/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/operation/drop/command.rb b/lib/mongo/error/invalid_max_connecting.rb similarity index 54% rename from lib/mongo/operation/drop/command.rb rename to lib/mongo/error/invalid_max_connecting.rb index 862bb2ff97..759d4c1732 100644 --- a/lib/mongo/operation/drop/command.rb +++ b/lib/mongo/error/invalid_max_connecting.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -# encoding: utf-8 -# Copyright (C) 2018-2020 MongoDB Inc. +# 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. @@ -16,25 +15,13 @@ # 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 + 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 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 7115022f23..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. # @@ -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/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 40c45b81f7..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. # @@ -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/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 a57d4d333b..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. # @@ -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/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/operation/create_user/command.rb b/lib/mongo/error/pool_cleared_error.rb similarity index 50% rename from lib/mongo/operation/create_user/command.rb rename to lib/mongo/error/pool_cleared_error.rb index 032cd6c629..6f8b493e1a 100644 --- a/lib/mongo/operation/create_user/command.rb +++ b/lib/mongo/error/pool_cleared_error.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all -# Copyright (C) 2018-2020 MongoDB Inc. +# 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. @@ -16,30 +16,24 @@ # limitations under the License. module Mongo - module Operation - class CreateUser + class Error - # A MongoDB createuser operation sent as a command message. + # 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. # - # @api private + # @example Instantiate the exception. + # Mongo::Error::PoolClearedError.new(address, pool) # - # @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 + # @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 diff --git a/lib/mongo/error/pool_closed_error.rb b/lib/mongo/error/pool_closed_error.rb index 71eca7568e..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. # @@ -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/operation/remove_user/command.rb b/lib/mongo/error/pool_error.rb similarity index 52% rename from lib/mongo/operation/remove_user/command.rb rename to lib/mongo/error/pool_error.rb index 121078223e..446dbd2e7a 100644 --- a/lib/mongo/operation/remove_user/command.rb +++ b/lib/mongo/error/pool_error.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all -# Copyright (C) 2018-2020 MongoDB Inc. +# 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. @@ -16,30 +16,29 @@ # limitations under the License. module Mongo - module Operation - class RemoveUser + class Error - # 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 + # Abstract base class for connection pool-related exceptions. + class PoolError < Error - private + # @return [ Mongo::Address ] address The address of the server the + # pool's connections connect to. + # + # @since 2.9.0 + attr_reader :address - def selector(connection) - { :dropUser => user_name } - end + # @return [ Mongo::Server::ConnectionPool ] pool The connection pool. + # + # @since 2.11.0 + attr_reader :pool - def message(connection) - Protocol::Query.new(db_name, Database::COMMAND, command(connection), options(connection)) - end + # Instantiate the new exception. + # + # @api private + def initialize(address, pool, message) + @address = address + @pool = pool + super(message) end end end diff --git a/lib/mongo/operation/drop_index/command.rb b/lib/mongo/error/pool_paused_error.rb similarity index 51% rename from lib/mongo/operation/drop_index/command.rb rename to lib/mongo/error/pool_paused_error.rb index f78c634f8b..6f4cd28024 100644 --- a/lib/mongo/operation/drop_index/command.rb +++ b/lib/mongo/error/pool_paused_error.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all -# Copyright (C) 2018-2020 MongoDB Inc. +# 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. @@ -16,29 +16,24 @@ # limitations under the License. module Mongo - module Operation - class DropIndex + class Error - # A MongoDB dropindex operation sent as a command message. + # Exception raised if an operation is attempted on a paused connection pool. + class PoolPausedError < PoolError + include WriteRetryable + include ChangeStreamResumable + + # Instantiate the new exception. # - # @api private + # @example Instantiate the exception. + # Mongo::Error::PoolClosedError.new(address, pool) # - # @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 + # @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 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/operation/get_more/legacy.rb b/lib/mongo/error/server_not_usable.rb similarity index 60% rename from lib/mongo/operation/get_more/legacy.rb rename to lib/mongo/error/server_not_usable.rb index e3ac61485f..77af027b1e 100644 --- a/lib/mongo/operation/get_more/legacy.rb +++ b/lib/mongo/error/server_not_usable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all -# Copyright (C) 2018-2020 MongoDB Inc. +# 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. @@ -16,23 +16,20 @@ # limitations under the License. module Mongo - module Operation - class GetMore + class Error + include WriteRetryable + include ChangeStreamResumable - # A MongoDB getMore operation sent as a legacy wire protocol message. + # Exception raised if an unknown server is attempted to be used for + # an operation. + class ServerNotUsable < Error + + # Instantiate the new exception. # # @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 + def initialize(address) + @address = address + super("Attempted to use an unknown server at #{address}") end end end 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/operation/shared/op_msg_or_command.rb b/lib/mongo/error/transactions_not_supported.rb similarity index 52% rename from lib/mongo/operation/shared/op_msg_or_command.rb rename to lib/mongo/error/transactions_not_supported.rb index 6716237e53..bbaa6c7c58 100644 --- a/lib/mongo/operation/shared/op_msg_or_command.rb +++ b/lib/mongo/error/transactions_not_supported.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -# encoding: utf-8 -# Copyright (C) 2018-2020 MongoDB Inc. +# 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. @@ -16,25 +15,19 @@ # limitations under the License. module Mongo - module Operation - - # Shared behavior of executing the operation as an OpMsg when supported - # or as a Command otherwise. + 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 # - # @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) + # @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 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 9ac8754f8b..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. # @@ -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..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. # @@ -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/grid/fs_bucket.rb b/lib/mongo/grid/fs_bucket.rb index 6007a5fa7a..8d723cdf55 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. # @@ -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/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 8115f42848..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. # @@ -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/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 c3465b46db..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. # @@ -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/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 82ce9dd252..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. # @@ -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/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 new file mode 100644 index 0000000000..b3cedde8b1 --- /dev/null +++ b/lib/mongo/monitoring/event/cmap/pool_ready.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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/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..0ad628e518 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. # @@ -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 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 1d345ad54c..8def25dbe3 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' @@ -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' @@ -53,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/aggregate.rb b/lib/mongo/operation/aggregate.rb index cef9575f3a..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. # @@ -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/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 371ace3177..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. # @@ -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/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 2939247266..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. # @@ -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/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 2ebc6c0e12..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. # @@ -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/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 e5371f940c..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. # @@ -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/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 0f4482118e..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. # @@ -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_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_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/create_user.rb b/lib/mongo/operation/create_user.rb index d426dc7324..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. # @@ -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/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 df18238836..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. # @@ -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/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/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/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 2653a74088..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. # @@ -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/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 b7467a5ba3..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. # @@ -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/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 be75a27fb5..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. # @@ -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/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 ae70369525..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. # @@ -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/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/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/explain.rb b/lib/mongo/operation/explain.rb index e4757e19cc..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. # @@ -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/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 a4528c015a..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. # @@ -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..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. # @@ -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/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/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/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/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/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 455f8f6394..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. # @@ -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/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 e1ead670f1..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. # @@ -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/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 e9df182ccd..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. # @@ -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/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/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/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 fef0cb0597..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. # @@ -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/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 8e50d8884d..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. # @@ -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/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 d77b37a076..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. # @@ -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/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 a435bdc922..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. # @@ -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/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 f17358c918..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. # @@ -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/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..9b61476631 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. # @@ -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/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/polymorphic_operation.rb b/lib/mongo/operation/shared/op_msg_executable.rb similarity index 81% rename from lib/mongo/operation/shared/polymorphic_operation.rb rename to lib/mongo/operation/shared/op_msg_executable.rb index 1786bd4c4d..a97b8fd48d 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 +# rubocop:todo all -# 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_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/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..799721a8de 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. # @@ -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/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..aed9bd23e6 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. # @@ -30,7 +30,6 @@ module SessionsSupported READ_COMMANDS = [ :aggregate, - :collStats, :count, :dbStats, :distinct, diff --git a/lib/mongo/operation/shared/specifiable.rb b/lib/mongo/operation/shared/specifiable.rb index 7c358d82ee..afc799f46e 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. # @@ -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/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 c97db17aa5..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. # @@ -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/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/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/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_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/operation/update_user.rb b/lib/mongo/operation/update_user.rb index 498db1a9dc..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. # @@ -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/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 71cae7f82a..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. # @@ -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/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 3d882f6157..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. # @@ -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/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 b09097f3cd..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' @@ -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/bit_vector.rb b/lib/mongo/protocol/bit_vector.rb index 3f32fb10ff..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. # @@ -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..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. # @@ -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/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/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/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/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/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 ed5674e0d9..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. # @@ -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..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. # @@ -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/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 e021b9a0c7..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. # @@ -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/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/lib/mongo/query_cache.rb b/lib/mongo/query_cache.rb index e50f12fe6c..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. # @@ -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/retryable.rb b/lib/mongo/retryable.rb index f53fc5d472..2508e13efd 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. # @@ -15,530 +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 - - # 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 + # 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 - # 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 Error::SocketError, Error::SocketTimeoutError, 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 Error::SocketError, Error::SocketTimeoutError => e - retry_message = options && options[:retry_message] - log_retry(e, message: retry_message) - yield + # @return [ Mongo::Server ] A server matching the server preference. + def select_server(cluster, server_selector, session, failed_server = nil) + server_selector.select_server(cluster, nil, session, deprioritized: [failed_server].compact) 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 Error::SocketError, Error::SocketTimeoutError, 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 !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 Error::SocketError, Error::SocketTimeoutError, 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 Error::SocketError, Error::SocketTimeoutError => 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 => 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 Error::SocketError, Error::SocketTimeoutError => 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 => 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 Error::SocketError, Error::SocketTimeoutError => e - e.add_note('modern retry') - e.add_note("attempt 2") - raise e - rescue Error::OperationFailure => 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 Error::SocketError, Error::SocketTimeoutError => e - e.add_note('modern retry') - e.add_note('attempt 2') - raise e - rescue Error::OperationFailure => e - e.add_note('modern retry') - if e.label?('RetryableWriteError') - 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 + # @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..d3f775abe8 --- /dev/null +++ b/lib/mongo/retryable/base_worker.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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..a82e67a051 --- /dev/null +++ b/lib/mongo/retryable/read_worker.rb @@ -0,0 +1,297 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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) + 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, failed_server: server, &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 [ 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, failed_server: nil, &block) + begin + 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 + 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..339a28b2f1 --- /dev/null +++ b/lib/mongo/retryable/write_worker.rb @@ -0,0 +1,367 @@ +# frozen_string_literal: true +# rubocop:todo all + +# 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) + options = session&.client&.options || {} + + if 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), failed_server: server, &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. + # @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:, failed_server: nil, &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, failed_server) + + 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/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/lib/mongo/semaphore.rb b/lib/mongo/semaphore.rb index 94372e3d52..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. # @@ -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.rb b/lib/mongo/server.rb index 54edba142a..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. # @@ -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. @@ -267,25 +276,41 @@ 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 + + # 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 + # Whether the server is connected. # # @return [ true|false ] Whether the server is connected. @@ -397,8 +422,29 @@ def summary # # @since 2.0.0 def pool + if unknown? + raise Error::ServerNotUsable, address + end + @pool_lock.synchronize do - @pool ||= ConnectionPool.new(self, options) + opts = connected? ? options : options.merge(populator_io: false) + @pool ||= ConnectionPool.new(self, opts).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 end end @@ -480,18 +526,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. @@ -547,6 +590,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 @@ -555,15 +600,18 @@ 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' + 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 @@ -595,17 +643,35 @@ def unknown!(options = {}) # @api private def update_description(description) + pool = pool_internal + if pool && !description.unknown? + pool.ready + end @description = description 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 # 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 - if @pool - @pool.disconnect!(service_id: service_id) + # 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, interrupt_in_use_connections: interrupt_in_use_connections) end end end diff --git a/lib/mongo/server/app_metadata.rb b/lib/mongo/server/app_metadata.rb index 8d81dd1f2d..81e595d9aa 100644 --- a/lib/mongo/server/app_metadata.rb +++ b/lib/mongo/server/app_metadata.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# encoding: utf-8 # Copyright (C) 2016-2020 MongoDB Inc. # @@ -15,46 +14,33 @@ # 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/platform' +require 'mongo/server/app_metadata/truncator' + module Mongo class Server # Application metadata that is sent to the server during a handshake, # when a new connection is established. # # @api private - # - # @since 2.4.0 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 - 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) # @@ -88,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 @@ -132,13 +116,26 @@ 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 end + # Get BSON::Document to be used as value for `client` key in + # handshake document. + # + # @return [BSON::Document] Document describing client for handshake. + 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_string + env_doc.tap { |env| doc[:env] = env if env } + end + end + private # Check whether it is possible to build a valid app metadata document @@ -149,23 +146,10 @@ 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 - # 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 + true 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,42 +157,22 @@ 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 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('|'), @@ -219,13 +183,26 @@ def os_doc { type: type, name: name, - architecture: architecture + architecture: architecture, } end + # Returns the environment doc describing the current execution + # environment. + # + # @return [ Hash | nil ] the environment doc (or nil if no relevant + # environment info was detected) + def env_doc + env = Environment.new + env.present? ? 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 @@ -236,31 +213,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/environment.rb b/lib/mongo/server/app_metadata/environment.rb new file mode 100644 index 0000000000..029257faec --- /dev/null +++ b/lib/mongo/server/app_metadata/environment.rb @@ -0,0 +1,314 @@ +# 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 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 + # 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 + + # 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 + + # 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_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 + @fields = {} + @error = nil + @name = detect_environment + populate_faas_fields + detect_container + 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 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 + # 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. + # + # @return [ Hash ] the detected environment information. + def to_h + name ? fields.merge(name: name) : fields + 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 + + # 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 + 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. + # + # @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_faas_fields + return unless name + + FIELDS[name].each_with_object(@fields) 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/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/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/lib/mongo/server/connection.rb b/lib/mongo/server/connection.rb index ce19d8143b..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. # @@ -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 @@ -144,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 @@ -156,6 +167,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 @@ -201,18 +227,11 @@ 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, @description, @compressor = do_connect + @socket = create_socket + @description, @compressor = do_connect if server.load_balancer? if Lint.enabled? @@ -232,27 +251,36 @@ 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 + raise_if_closed! 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 +296,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 +312,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 @@ -376,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_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 a0e303cb4b..dfbc718522 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. # @@ -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. @@ -75,11 +84,21 @@ 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. # # @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' @@ -112,6 +131,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 @@ -120,6 +140,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 @@ -127,30 +148,42 @@ 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) @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 + + # 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 + # set. + @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, DEFAULT_MAX_CONNECTING) + ObjectSpace.define_finalizer(self, self.class.finalize(@available_connections, @pending_connections, @populator)) 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 +229,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. # @@ -222,6 +268,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 + @connection_requests + end + # Number of available connections in the pool. # # @return [ Integer ] Number of available connections. @@ -244,14 +298,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 @@ -261,6 +331,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 @@ -286,157 +359,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 - - 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 + raise_if_pool_closed! + raise_if_pool_paused_locked! - if @server.load_balancer? && connection_global_id - # 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 - - "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), @@ -464,69 +390,114 @@ 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 + + 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 - # 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. + # 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. - @checked_out_connections.delete(connection) - publish_cmap_event( - Monitoring::Event::Cmap::ConnectionCheckedIn.new(@server.address, connection.id, self) - ) + @checked_out_connections.delete(connection) + @size_cv.signal - if connection.error? - connection.disconnect!(reason: :error) - return - end + 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 + + @max_connecting_cv.signal + end + 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 !@ready + + @ready = false + 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. + # 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. # @@ -536,56 +507,112 @@ 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) - publish_cmap_event( - Monitoring::Event::Cmap::PoolCleared.new( - @server.address, - service_id: service_id - ) - ) - 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_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 + + # Broadcast here to cause all of the threads waiting on the max + # 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 break out of the wait loop and error. + @size_cv.broadcast 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 end - # @since 2.1.0 - # @deprecated - alias :disconnect! :clear + # Instructs the pool to create and return connections. + def ready + raise_if_closed! + + # 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 + + @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) + ) + + 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 # schedules currently checked out connections to be closed when they are @@ -595,6 +622,8 @@ def clear(options = nil) # # @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. # @@ -620,9 +649,15 @@ 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 + 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 + + @max_connecting_cv.broadcast + @size_cv.broadcast end publish_cmap_event( @@ -644,6 +679,9 @@ def inspect if closed? "#" + elsif !ready? + "#" else "#" @@ -665,6 +703,8 @@ def with_connection(connection_global_id: nil) connection = check_out(connection_global_id: connection_global_id) yield(connection) + rescue Error::SocketError, Error::SocketTimeoutError, Error::ConnectionPerished => e + maybe_raise_pool_cleared!(connection, e) ensure if connection check_in(connection) @@ -713,17 +753,17 @@ 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 - # 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. # @@ -745,11 +785,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. @@ -782,7 +817,9 @@ 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 conn = @available_connections.detect do |conn| conn.global_id == connection_global_id @@ -797,18 +834,21 @@ 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. ) 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. + # 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 @@ -817,10 +857,15 @@ def create_and_add_connection connection = nil @lock.synchronize do - if !closed? && unsynchronized_size < min_size + if !closed? && @ready && + (unsynchronized_size + @connection_requests) < min_size && + @pending_connections.length < @max_connecting + then connection = create_connection @pending_connections << connection else + return true if remove_interrupted_connections + return true if remove_stale_connection return false end end @@ -830,6 +875,8 @@ def create_and_add_connection rescue Exception @lock.synchronize do @pending_connections.delete(connection) + @max_connecting_cv.signal + @size_cv.signal end raise end @@ -837,14 +884,65 @@ def create_and_add_connection @lock.synchronize do @available_connections << connection @pending_connections.delete(connection) + @max_connecting_cv.signal + @size_cv.signal + end + + 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 - # wake up one thread waiting for connections, since one was created - @available_semaphore.signal + # 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. + # + # @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. @@ -856,6 +954,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) @@ -895,6 +1012,350 @@ def check_invariants end end end + + # Close the available connections. + # + # @param [ Array ] connections A list of connections. + # @param [ Object ] service_id The service id. + def close_available_connections(service_id) + if @server.load_balancer? && service_id + loop do + conn = @available_connections.detect do |conn| + conn.service_id == service_id && + conn.generation < @generation_manager.generation(service_id: service_id) + end + if conn + @available_connections.delete(conn) + conn.disconnect!(reason: :stale, interrupted: true) + @populate_semaphore.signal + else + break + end + end + else + @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 + 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 + + # 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 + + # 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, " + + "#{@connection_requests} connections requests " + + "(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.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 [ 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(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? + @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 + ), + ) + # 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 + # 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 + + # 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 = 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 + connection = wait_for_connection(connection_global_id, deadline) + end + + connect_or_raise(connection) unless connection.connected? + + @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 + + 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.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 + + 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 + if @pending_connections.include?(connection) + @pending_connections.delete(connection) + end + @max_connecting_cv.signal + @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 + + + # 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 end diff --git a/lib/mongo/server/connection_pool/generation_manager.rb b/lib/mongo/server/connection_pool/generation_manager.rb index 3f62c5ca6e..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. # @@ -24,31 +24,52 @@ 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 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 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; @@ -60,11 +81,34 @@ 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 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 + + # 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/connection_pool/populator.rb b/lib/mongo/server/connection_pool/populator.rb index 00169e3fd4..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. # @@ -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/lib/mongo/server/description.rb b/lib/mongo/server/description.rb index b9c59139d5..3e21b6a635 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. # @@ -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'] @@ -872,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 0f1deafca4..97a222713b 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. # @@ -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, @@ -82,7 +83,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. # 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 4b06bfa459..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. # @@ -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/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 95f45f0e20..10eb478449 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. # @@ -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,7 +178,16 @@ def ==(other) # lint mode is enabled. # # @since 2.0.0 - def select_server(cluster, ping = nil, session = nil, write_aggregation: false) + 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 + end + end + + # Parameters and return values are the same as for select_server. + private def select_server_impl(cluster, ping, session, write_aggregation, deprioritized) if cluster.topology.is_a?(Cluster::Topology::LoadBalanced) return cluster.servers.first end @@ -245,7 +258,19 @@ def select_server(cluster, ping = nil, session = nil, write_aggregation: false) =end loop do - server = try_select_server(cluster, write_aggregation: write_aggregation) + if Lint.enabled? + cluster.servers.each do |server| + # 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 + end + end + + server = try_select_server(cluster, write_aggregation: write_aggregation, deprioritized: deprioritized) if server unless cluster.topology.compatible? @@ -300,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| @@ -326,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? @@ -397,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). # @@ -604,9 +651,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/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 6c58c27547..b22519efc1 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. # @@ -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. # @@ -522,7 +539,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* @@ -538,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]) @@ -1185,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/lib/mongo/session/server_session.rb b/lib/mongo/session/server_session.rb index f7323e6b02..6f7283c79e 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. # @@ -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 8211aa88a0..4fb832a813 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. # @@ -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/lib/mongo/socket.rb b/lib/mongo/socket.rb index 707a857022..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. # @@ -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 @@ -414,11 +438,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/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 56f06f0dba..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. # @@ -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/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 0d91e7cd6b..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. # @@ -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/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 0a08022958..0b891a858c 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. # @@ -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. # @@ -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) @@ -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 @@ -340,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}") @@ -382,15 +415,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 0af5c74db5..bc7e8f16dc 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. # @@ -102,11 +102,7 @@ def smc_to_ruby(opts) uri_options end - if key == 'readConcernLevel' - 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. @@ -119,7 +115,6 @@ def smc_to_ruby(opts) end end - #p uri_options uri_options end @@ -138,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 @@ -171,9 +209,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 +228,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 +252,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 @@ -247,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 @@ -290,14 +329,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 [ String | true | false ] value 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}") @@ -305,31 +344,64 @@ 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 + # 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 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 | true | false | nil ] value The repeated boolean to revert. + # + # @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. - # @param value [ String ] 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,20 +413,34 @@ 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. + # 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. # # @param [ String ] name Name of the URI option being processed. - # @param value [ String ] URI option value. + # @param [ String | Integer ] value 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 @@ -362,78 +448,143 @@ 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 + # 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. # # @param [ String ] name Name of the URI option being processed. - # @param [ Integer ] value The millisecond value. + # @param [ String | Integer | Float ] 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, Float + if value < 0 + log_warn("#{name} cannot be a negative number") + return nil + end + else + 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 + # 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. - # @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 + alias :stringify_symbol :revert_symbol # Extract values from the string and put them into an array. # # @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 + # 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. - # @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. + # + # @raise [ ArgumentError ] if its an invalid auth mechanism. def revert_auth_mech(value) found = AUTH_MECH_MAP.detect do |k, v| v == value @@ -445,12 +596,21 @@ 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. - # @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 @@ -463,62 +623,101 @@ 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 + # 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. # # @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 + # 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. - # @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 + alias :stringify_read_mode :revert_read_mode # 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 @@ -528,16 +727,30 @@ 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 + # 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. - # @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 @@ -549,7 +762,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) @@ -563,6 +776,11 @@ def convert_w(name, value) end end + # Reverts write concern. + # + # @param [ Integer | Symbol | String ] value The write concern. + # + # @return [ Integer | String ] The write concern as a string. def revert_w(value) case value when Symbol @@ -572,34 +790,59 @@ 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. - # @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 + # 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. - # @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) @@ -619,8 +862,6 @@ def hash_extractor(name, value) h end end - end - end end 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..7f52d504fd 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. # @@ -20,5 +20,5 @@ module Mongo # The current version of the driver. # # @since 2.0.0 - VERSION = '2.18.1'.freeze + VERSION = '2.20.0'.freeze end 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 30b9a3887b..2c58491b8b 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' @@ -6,8 +9,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' @@ -38,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/profile/benchmark_21.rb b/profile/benchmark_21.rb deleted file mode 100644 index 05dbad5279..0000000000 --- a/profile/benchmark_21.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -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 5c991ea926..0000000000 --- a/profile/benchmarking.rb +++ /dev/null @@ -1,131 +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. - -require 'benchmark' -require_relative 'benchmarking/helper' -require_relative 'benchmarking/micro' -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 - - # 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 - - # 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 - - # 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 - - # 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 - - # 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 - - # 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 - - # 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 - - # 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 - - # 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 - - # The default number of test repetitions. - # - # @return [ Integer ] The number of test repetitions. - # - # @since 2.2.3 - TEST_REPETITIONS = 100.freeze - - # The number of default warmup repetitions of the test to do before - # recording times. - # - # @return [ Integer ] The default number of warmup repetitions. - # - # @since 2.2.3 - WARMUP_REPETITIONS = 10.freeze - - def tweet_document - Benchmarking.load_file(TWEET_DOCUMENT_FILE).first - end - - def small_document - Benchmarking.load_file(SMALL_DOCUMENT_FILE).first - end - - def large_document - Benchmarking.load_file(LARGE_DOCUMENT_FILE).first - end - end -end diff --git a/profile/benchmarking/helper.rb b/profile/benchmarking/helper.rb deleted file mode 100644 index a9b31158be..0000000000 --- a/profile/benchmarking/helper.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -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| - if doc['_id'] && doc['_id']['$oid'] - doc['_id'] = BSON::ObjectId.from_string(doc['_id']['$oid']) - end - 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. - # - # @return [ Numeric ] The median of the list. - # - # @since 2.2.3 - def median(values) - values.sort![values.size/2-1] - end - end -end diff --git a/profile/benchmarking/micro.rb b/profile/benchmarking/micro.rb deleted file mode 100644 index f2aa0877a9..0000000000 --- a/profile/benchmarking/micro.rb +++ /dev/null @@ -1,107 +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 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. - # - # @example Run a test. - # Benchmarking::Micro.run(:flat) - # - # @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, 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)}" - end - - # Run an encoding micro benchmark test. - # - # @example Run an encoding test. - # Benchmarking::Micro.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) - 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 - end - Benchmarking.median(results) - end - - # Run a decoding micro benchmark test. - # - # @example Run an decoding test. - # Benchmarking::Micro.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) - 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 - end - end - Benchmarking.median(results) - end - end - end -end diff --git a/profile/benchmarking/multi_doc.rb b/profile/benchmarking/multi_doc.rb deleted file mode 100644 index 6d59fdcdb7..0000000000 --- a/profile/benchmarking/multi_doc.rb +++ /dev/null @@ -1,182 +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 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 c4dd71920b..0000000000 --- a/profile/benchmarking/parallel.rb +++ /dev/null @@ -1,244 +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. - -#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/single_doc.rb b/profile/benchmarking/single_doc.rb deleted file mode 100644 index 6f09558b40..0000000000 --- a/profile/benchmarking/single_doc.rb +++ /dev/null @@ -1,149 +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 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/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/driver_bench/percentiles.rb b/profile/driver_bench/percentiles.rb new file mode 100644 index 0000000000..3ec9eb7e5f --- /dev/null +++ b/profile/driver_bench/percentiles.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Mongo + module DriverBench + # 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/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 7d1056c7a7..0000000000 --- a/profile/profile.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -# encoding: utf-8 - -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) 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 diff --git a/spec/atlas/atlas_connectivity_spec.rb b/spec/atlas/atlas_connectivity_spec.rb index 78ae835b81..ecd511ffb3 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' @@ -7,21 +7,17 @@ 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 - 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 diff --git a/spec/atlas/operations_spec.rb b/spec/atlas/operations_spec.rb index 7a00d1ecd1..8a46ab3702 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' @@ -7,21 +7,12 @@ 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 - - describe 'list_collections' do - # Atlas free tier proxy enforces restrictions on list_collections - # arguments. This tests verifies that list_collections works on Atlas + require_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 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 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 new file mode 100644 index 0000000000..2756ab07d2 --- /dev/null +++ b/spec/integration/aws_auth_credentials_cache_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +# rubocop:todo all + +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_auth_request_spec.rb b/spec/integration/aws_auth_request_spec.rb index 25fb77764d..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' @@ -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 939e711f12..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' @@ -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) @@ -70,7 +74,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 @@ -102,5 +106,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/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 d627acafdb..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' @@ -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/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 170d5c4914..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' @@ -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 @@ -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/integration/client_construction_spec.rb b/spec/integration/client_construction_spec.rb index d7034d3c73..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' @@ -100,6 +100,28 @@ 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.delete(:replica_set) + 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 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 5de34dd5b4..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' @@ -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 } ) ) @@ -133,7 +134,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 +147,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 @@ -223,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_side_encryption/auto_encryption_spec.rb b/spec/integration/client_side_encryption/auto_encryption_spec.rb index 3f1c05ae1a..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' @@ -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 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..a151ece93b --- /dev/null +++ b/spec/integration/client_side_encryption/automatic_data_encryption_keys_prose_spec.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +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_version '7.0.0-rc0' + + 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 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 + let(:opts) do + { encrypted_fields: { fields: [ field ] } } + end + + context 'when insert unencrypted value' do + let(:field) do + { + path: 'ssn', + bsonType: 'string', + keyId: nil + } + 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 + ) + end.to raise_error(ArgumentError, /coll_opts must contain :encrypted_fields/) + end + + context 'when invalid keyId provided' do + let(:field) do + { + path: 'ssn', + bsonType: 'string', + keyId: false + } + 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 + + context 'when configured correctly' do + let(:field) do + { + path: 'ssn', + bsonType: 'string', + keyId: nil + } + 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 'with aws' do + let(:kms_provider) { 'aws' } + 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 'with local' do + let(:kms_provider) { 'local' } + let(:master_key) { { key: local_master_key } } + + it_behaves_like 'creates data keys automatically' + end + end +end 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..aac6347694 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' @@ -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/integration/client_side_encryption/custom_endpoint_spec.rb b/spec/integration/client_side_encryption/custom_endpoint_spec.rb index c6f4668978..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' @@ -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' 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..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 -# encoding: utf-8 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_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..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 -# encoding: utf-8 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/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 45b137c1b5..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' @@ -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 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 new file mode 100644 index 0000000000..41fe7ef46b --- /dev/null +++ b/spec/integration/client_side_encryption/mongocryptd_prose_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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) { 'mongodb://localhost:27777' } + + 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 + + before 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 + { + 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(27_777).accept + connect_attempt.done! + end + end + + after do + listener.exit + end + + it 'does not try to connect to mongocryptd' do + encryption_client[:users].insert_one(ssn: ssn) + expect(connect_attempt.done?).to be 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 + + let(:mongocryptd_client) { new_local_client(mongocryptd_uri) } + + it 'does not spawn mongocryptd' do + 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_aws_credentials_spec.rb b/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb new file mode 100644 index 0000000000..0aa2bd0b2a --- /dev/null +++ b/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true +# rubocop:todo all + +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/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..115cb457e2 --- /dev/null +++ b/spec/integration/client_side_encryption/on_demand_azure_credentials_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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 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..204d17c2f3 --- /dev/null +++ b/spec/integration/client_side_encryption/on_demand_gcp_credentials_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true +# rubocop:todo all + +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 + 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) + end.to raise_error(Mongo::Error::CryptError, /GCP credentials/) + end + end +end + 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..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 -# encoding: utf-8 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 new file mode 100644 index 0000000000..78266e8d79 --- /dev/null +++ b/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb @@ -0,0 +1,536 @@ +# frozen_string_literal: true + +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 '7.0.0-rc0' + require_libmongocrypt + include_context 'define shared FLE helpers' + + let(:key1_id) do + key1_document['_id'] + 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 + + 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) + 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 'when Int' do + let(:type) do + 'Int' + end + + let(:value_converter) do + proc 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 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 'when Long' do + let(:type) do + 'Long' + end + + let(:value_converter) do + proc 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 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 'when DoublePrecision' do + let(:type) do + 'DoublePrecision' + end + + let(:value_converter) do + proc 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 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 'when DoubleNoPrecision' do + let(:type) do + 'DoubleNoPrecision' + end + + let(:value_converter) do + proc 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 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 'when Date' do + let(:type) do + 'Date' + end + + let(:value_converter) do + proc 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 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 'when DecimalPrecision' do + require_topology :replica_set + + let(:type) do + 'DecimalPrecision' + end + + let(:value_converter) do + proc 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 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 'when DecimalNoPrecision' do + require_topology :replica_set + + let(:type) do + 'DecimalNoPrecision' + end + + let(:value_converter) do + proc 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 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 +# 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 new file mode 100644 index 0000000000..86958929f2 --- /dev/null +++ b/spec/integration/client_side_encryption/rewrap_prose_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'RewrapManyDataKey' do + require_libmongocrypt + min_server_version '7.0.0-rc0' + 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 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_encryption1) 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_encryption2) 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(:key_id) do + client_encryption1.create_data_key( + src_provider.to_s, + master_key: master_keys[src_provider] + ) + end + + let(:ciphertext) do + client_encryption1.encrypt( + 'test', + key_id: key_id, + algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' + ) + end + + before do + client_encryption2.rewrap_many_data_key( + {}, + provider: dst_provider.to_s, + master_key: master_keys[dst_provider] + ) + end + + it 'rewraps', :aggregate_failures do + 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 +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 9c5814f1c2..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 -# encoding: utf-8 -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/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 e004c5608f..6b9ca241b2 100644 --- a/spec/integration/client_spec.rb +++ b/spec/integration/client_spec.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all 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, RUBY-3174. context 'after client is disconnected' do let(:client) { authorized_client.with(server_selection_timeout: 1) } @@ -16,8 +19,10 @@ expect(resp).to be_a(Mongo::Operation::Result) 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 'is still usable for operations' do + client['collection'].insert_one(test: 1) + end end context 'after all servers are marked unknown' do diff --git a/spec/integration/client_update_spec.rb b/spec/integration/client_update_spec.rb index 460bc8844c..34fb0e269c 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' @@ -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, { @@ -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/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 9b3237df72..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' @@ -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/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/faas_env_spec.rb b/spec/integration/connection/faas_env_spec.rb new file mode 100644 index 0000000000..a9e613de7f --- /dev/null +++ b/spec/integration/connection/faas_env_spec.rb @@ -0,0 +1,62 @@ +# 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_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/integration/connection_pool_populator_spec.rb b/spec/integration/connection_pool_populator_spec.rb index eebdf4812f..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' @@ -77,24 +77,27 @@ 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 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 + expect(pool.instance_variable_get('@populator')).to be_running sleep 2 expect(pool.size).to eq(1) @@ -102,8 +105,21 @@ 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 sleep 2 expect(pool.size).to eq(1) expect(pool.available_count).to eq(1) @@ -258,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 @@ -268,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 @@ -282,6 +298,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/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 d10e9cbc27..b20c11d2a8 100644 --- a/spec/integration/cursor_pinning_spec.rb +++ b/spec/integration/cursor_pinning_spec.rb @@ -1,10 +1,14 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all 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] } 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..bf1ae9e1b0 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' @@ -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/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/find_options_spec.rb b/spec/integration/find_options_spec.rb new file mode 100644 index 0000000000..519a164b70 --- /dev/null +++ b/spec/integration/find_options_spec.rb @@ -0,0 +1,227 @@ +# 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_options) do + {} + end + + let(:collection_options) do + {} + end + + let(:client) do + ClientRegistry.instance.new_local_client( + seeds, + SpecConfig.instance.test_options + .merge(database: SpecConfig.instance.test_db) + .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 + + let(:should_create_collection) { true } + + before do + client['find_options'].drop + collection.create if should_create_collection + 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 + + 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) + 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 + + 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 diff --git a/spec/integration/fork_reconnect_spec.rb b/spec/integration/fork_reconnect_spec.rb index a7631d1693..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' @@ -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/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 634d9e9cae..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' @@ -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/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..7cdfaabc70 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' @@ -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/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 4b1f41907e..81e1a640a9 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' @@ -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) } @@ -193,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 @@ -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 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 3854b35601..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' @@ -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/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 new file mode 100644 index 0000000000..81402c8f4d --- /dev/null +++ b/spec/integration/retryable_reads_errors_spec.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true +# rubocop:todo all + +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 + + 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/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 895d06db48..25b5cba8aa 100644 --- a/spec/integration/retryable_writes_errors_spec.rb +++ b/spec/integration/retryable_writes_errors_spec.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' 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 @@ -31,4 +33,316 @@ end end end + + context "when encountering a NoWritesPerformed error after an error with a RetryableWriteError label" do + require_topology :replica_set + require_retry_writes + min_server_version '4.4' + + 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.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, **kwargs, &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 + + 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 + + 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 diff --git a/spec/integration/sdam_error_handling_spec.rb b/spec/integration/sdam_error_handling_spec.rb index 5c756d2653..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' @@ -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) @@ -92,7 +93,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 +104,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 +249,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/) @@ -423,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 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 8f85fd869c..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' @@ -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/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/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 80e68dc4a7..f853f30c86 100644 --- a/spec/integration/server_selector_spec.rb +++ b/spec/integration/server_selector_spec.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' describe 'Server selector' do + require_no_linting + let(:selector) { Mongo::ServerSelector::Primary.new } let(:client) { authorized_client } let(:cluster) { client.cluster } @@ -29,11 +31,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/server_spec.rb b/spec/integration/server_spec.rb index 66d77bbda1..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' @@ -12,28 +12,37 @@ 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 + after do + server.close + end + it 'can be used for reads' do + # See also RUBY-3102. 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 'unknown server in disconnected cluster' do require_topology :single, :replica_set, :sharded + require_no_linting before do client.close @@ -41,29 +50,17 @@ 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) + after do + server.close 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' + 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 end end 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 6f1a6a93dc..761a59052d 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' @@ -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, @@ -71,11 +71,13 @@ end context "Snapshot Query Example 2" do + retry_test + 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/integration/srv_monitoring_spec.rb b/spec/integration/srv_monitoring_spec.rb index edc72a0bb2..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' @@ -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/srv_spec.rb b/spec/integration/srv_spec.rb index af4edcec20..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' @@ -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/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 9c69efb222..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' @@ -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/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 1863c3e510..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' @@ -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 @@ -31,6 +32,12 @@ end end + after do + if pool = server.pool_internal + pool.close + end + end + it 'works' do sessions = [] connections = [] @@ -65,13 +72,15 @@ # 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 - it 'raises ConnectionCheckOutTimeout' do + it 'raises MissingConnection' do session = client.start_session session.start_transaction client["tx_pin"].insert_one({test: 1}, session: session) @@ -79,17 +88,22 @@ 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 client["tx_pin"].insert_one({test: 2}, session: session) - end.should raise_error(Mongo::Error::ConnectionCheckOutTimeout) + end.should raise_error(Mongo::Error::MissingConnection) end end 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/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 8071926dc6..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' @@ -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 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..f7db224690 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")) @@ -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 @@ -102,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) @@ -110,6 +139,21 @@ 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 + + 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')) @@ -128,16 +172,7 @@ class ExampleTimeout < StandardError; end # 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/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 826dacb869..d637f6eb9c 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' @@ -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 @@ -305,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/mongo/auth/aws/credential_cache_spec.rb b/spec/mongo/auth/aws/credential_cache_spec.rb new file mode 100644 index 0000000000..415b1a6a3c --- /dev/null +++ b/spec/mongo/auth/aws/credential_cache_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true +# rubocop:todo all + +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..5a83adeb9c --- /dev/null +++ b/spec/mongo/auth/aws/credentials_retriever_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true +# rubocop:todo all + +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..3c0f2cb87a --- /dev/null +++ b/spec/mongo/auth/aws/credentials_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true +# rubocop:todo all + +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/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 cbee7fdf9c..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' @@ -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/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 8a43e20c45..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' @@ -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 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 5bc278beb4..442c0480b3 100644 --- a/spec/mongo/client_construction_spec.rb +++ b/spec/mongo/client_construction_spec.rb @@ -1,8 +1,19 @@ # frozen_string_literal: true -# encoding: utf-8 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 @@ -10,13 +21,15 @@ 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!) - start_time = Time.now + # 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 @@ -24,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( @@ -49,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 @@ -73,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 @@ -88,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 @@ -126,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 @@ -178,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 } @@ -203,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 @@ -219,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 @@ -227,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 @@ -265,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) @@ -298,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) @@ -324,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 @@ -345,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 @@ -404,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 @@ -423,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 @@ -442,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 @@ -462,7 +476,6 @@ end context 'when retry_writes is true' do - let(:options) do { retry_writes: true } end @@ -473,7 +486,6 @@ end context 'when retry_writes is false' do - let(:options) do { retry_writes: false } end @@ -484,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 @@ -496,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 @@ -523,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 @@ -541,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 @@ -554,7 +564,7 @@ require_zlib_compression let(:options) do - { compressors: ['zlib'] } + { compressors: %w[ zlib ] } end it 'sets the compressor' do @@ -576,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 @@ -586,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 @@ -612,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 @@ -661,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 @@ -670,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 @@ -742,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 @@ -753,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 @@ -764,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 @@ -776,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 @@ -791,38 +797,47 @@ end end - context 'when min_pool_size is provided' do - + context 'when max_connecting is provided' do let(:client) do - new_local_client_nmio(['127.0.0.1:27017'], options) + new_local_client_nmio(SINGLE_CLIENT, options) end - context 'when max_pool_size is provided' do + context 'when max_connecting is a positive integer' do + let(:options) do + { max_connecting: 5 } + end - context 'when the min_pool_size is greater than the max_pool_size' do + it 'sets the max connecting' do + expect(client.options[:max_connecting]).to eq(options[:max_connecting]) + end + end - let(:options) do - { - :min_pool_size => 20, - :max_pool_size => 10 - } - 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) } + + context 'when max_pool_size is provided' do + context 'when the min_pool_size is greater than the max_pool_size' do + 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]) @@ -831,13 +846,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]) @@ -846,12 +855,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]) @@ -861,29 +865,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]) @@ -891,10 +883,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 @@ -905,36 +896,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 @@ -943,28 +916,26 @@ 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 - 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 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 @@ -980,7 +951,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 @@ -999,24 +970,18 @@ 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 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') @@ -1024,29 +989,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') @@ -1054,44 +1007,59 @@ 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 expect(client.options).to eq(expected_options) end - context 'when min_pool_size is provided' do + 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 - context 'when max_pool_size is provided' do + it 'sets the max connecting' do + expect(client.options[:max_connecting]).to eq(10) + end + end - context 'when the min_pool_size is greater than the max_pool_size' do + 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 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 @@ -1103,7 +1071,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 @@ -1127,25 +1094,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) @@ -1153,10 +1112,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) @@ -1166,11 +1122,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 @@ -1178,9 +1131,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 @@ -1189,11 +1140,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 @@ -1201,9 +1149,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 @@ -1213,19 +1159,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 @@ -1234,19 +1181,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 @@ -1269,14 +1217,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) @@ -1286,295 +1228,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 @@ -1586,55 +1495,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 @@ -1644,7 +1543,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 @@ -1655,79 +1555,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 @@ -1735,32 +1633,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 @@ -1771,30 +1667,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 option: {"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 option: {"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 option: primary: must be a hash') + 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 option: primary: must be a hash') + 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 @@ -1808,7 +1715,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 @@ -1834,15 +1741,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 @@ -1861,9 +1770,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) } @@ -1881,52 +1789,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 @@ -1937,7 +1845,7 @@ end it 'works' do - client.options[:wrapping_libraries].should == [] + expect(client.options[:wrapping_libraries]).to be == [] end end @@ -1947,7 +1855,7 @@ end it 'works' do - client.options[:wrapping_libraries].should be nil + expect(client.options[:wrapping_libraries]).to be_nil end end end @@ -1955,37 +1863,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 @@ -1993,12 +1898,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 @@ -2007,15 +1910,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, @@ -2025,41 +1930,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 @@ -2068,16 +1966,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 @@ -2087,9 +1987,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 @@ -2099,9 +1998,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 @@ -2109,55 +2008,112 @@ 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 + 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 + 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 + c + end + + it 'closes all clients after block' do + 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 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 @@ -2247,13 +2203,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 @@ -2264,7 +2218,6 @@ end context 'when provided a string' do - let(:database) do client.use('testdb') end @@ -2273,7 +2226,6 @@ end context 'when provided a symbol' do - let(:database) do client.use(:testdb) end @@ -2282,45 +2234,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) @@ -2346,27 +2282,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 @@ -2374,10 +2308,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 @@ -2385,21 +2318,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 @@ -2407,28 +2338,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 @@ -2449,21 +2389,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 @@ -2475,10 +2413,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 @@ -2491,9 +2428,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 @@ -2501,7 +2437,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 @@ -2511,9 +2447,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 @@ -2521,20 +2456,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 @@ -2552,9 +2486,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 @@ -2566,43 +2499,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 @@ -2623,24 +2548,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) @@ -2650,11 +2569,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) + 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) @@ -2678,26 +2600,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 @@ -2706,14 +2630,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 @@ -2735,34 +2659,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 @@ -2770,19 +2694,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 @@ -2795,3 +2717,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/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 0544820176..ac8bcc9148 100644 --- a/spec/mongo/client_spec.rb +++ b/spec/mongo/client_spec.rb @@ -1,13 +1,15 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all 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,32 +713,31 @@ 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 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 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,20 +1181,22 @@ 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 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 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/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 c241fe95fa..b87733f261 100644 --- a/spec/mongo/cluster_spec.rb +++ b/spec/mongo/cluster_spec.rb @@ -1,39 +1,55 @@ # frozen_string_literal: true -# encoding: utf-8 require 'spec_helper' - +require 'support/recording_logger' + +# 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 +68,69 @@ 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 + + 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 - 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 +139,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 +205,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 +224,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 +239,6 @@ end describe '#scan!' do - let(:preference) do Mongo::ServerSelector.primary end @@ -224,9 +271,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 +292,6 @@ end context 'when the cluster has no servers' do - let(:servers) do [] end @@ -258,7 +302,6 @@ end context 'when topology is Single' do - let(:topology) do Mongo::Cluster::Topology::Single.new({}, monitoring, cluster_without_io) end @@ -269,9 +312,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 +322,6 @@ end context 'when topology is Sharded' do - let(:topology) do Mongo::Cluster::Topology::Sharded.new({}, monitoring, cluster_without_io) end @@ -291,7 +332,6 @@ end context 'when topology is Unknown' do - let(:topology) do Mongo::Cluster::Topology::Unknown.new({}, monitoring, cluster_without_io) end @@ -304,7 +344,6 @@ end describe '#add' do - context 'topology is Sharded' do require_topology :sharded @@ -317,19 +356,19 @@ 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 - describe '#disconnect!' do + describe '#close' do let(:cluster) { cluster_with_semaphore } let(:known_servers) do @@ -340,24 +379,20 @@ 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 - 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 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 - end + expect(known_servers).to all receive(:close).and_call_original expect(periodic_executor).to receive(:stop!).and_call_original end @@ -366,17 +401,16 @@ it 'publishes server closed event once' do monitoring.subscribe(Mongo::Monitoring::SERVER_CLOSED, subscriber) - expect(cluster.disconnect!).to be(true) - 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.disconnect!).to be(true) - 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 +419,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 +429,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 +471,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 +487,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,36 +500,32 @@ 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 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 context 'when the client does not have an app_name set' do - let(:cluster) do authorized_client.cluster 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 describe '#cluster_time' do - let(:operation) do client.command(ping: 1) end @@ -514,10 +542,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 +557,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 +573,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 +584,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 +620,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 +653,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 +682,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 +731,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 +761,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 +774,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 +798,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 +821,5 @@ end end end +# rubocop:enable RSpec/ContextWording, RSpec/VerifiedDoubles, RSpec/MessageSpies +# rubocop:enable RSpec/ExpectInHook, RSpec/ExampleLength 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..da363d9992 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' @@ -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' 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..7ca3a5c30b 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' @@ -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 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 1f6815ef25..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' @@ -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/collection/view/readable_spec.rb b/spec/mongo/collection/view/readable_spec.rb index cf5b2385b7..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' @@ -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 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 829e6f0452..7a5651d1d6 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' @@ -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 @@ -2455,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 @@ -4412,4 +4448,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_ddl_spec.rb b/spec/mongo/collection_ddl_spec.rb index b3e31a2569..1c644cfabd 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' @@ -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,10 +119,51 @@ end it_behaves_like 'a capped collection command' + it_behaves_like 'a validated collection command' + 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 + collection.aggregate([ {'$collStats' => { 'storageStats' => {} }} ]).first + end + + let(:storage_stats) do + collstats.fetch('storageStats', {}) + end + + let(:options) do + { :capped => true, :size => 4096, :max => 512 } + end - context 'when validators can be set' do - min_server_fcv '3.2' - it_behaves_like 'a validated collection command' + 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(storage_stats["capped"]).to be true + expect(storage_stats["max"]).to eq(512) + expect(storage_stats["maxSize"]).to eq(4096) end end end @@ -148,7 +185,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 @@ -159,7 +195,6 @@ end context 'when write concern passed in as an option' do - min_server_fcv '3.4' require_topology :replica_set before do @@ -189,14 +224,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 @@ -219,45 +246,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 @@ -278,6 +278,43 @@ 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 + + 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 context 'when a session is provided' do @@ -312,7 +349,6 @@ end context 'when collation has a strength' do - min_server_fcv '3.4' let(:band_collection) do described_class.new(database, :bands) @@ -398,6 +434,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) @@ -418,7 +455,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 @@ -429,7 +465,6 @@ end context 'when write concern passed in as an option' do - min_server_fcv '3.4' require_set_write_concern let(:events) do @@ -455,19 +490,12 @@ 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 context 'when the collection does not exist' do require_set_write_concern + max_server_fcv '6.99.99' before do begin @@ -480,6 +508,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 diff --git a/spec/mongo/collection_spec.rb b/spec/mongo/collection_spec.rb index a4c1b774df..ef58e44c84 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' @@ -647,7 +647,15 @@ 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 + collection.aggregate([ {'$collStats' => { 'storageStats' => {} }} ]).first + end + + let(:storage_stats) do + collstats.fetch('storageStats', {}) end before do @@ -658,6 +666,12 @@ it 'returns true' do expect(collection).to be_capped end + + it "applies the options" do + expect(storage_stats["capped"]).to be true + expect(storage_stats["max"]).to eq(512) + expect(storage_stats["maxSize"]).to eq(4096) + end end context 'when the collection is not capped' do @@ -824,9 +838,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/condition_variable_spec.rb b/spec/mongo/condition_variable_spec.rb new file mode 100644 index 0000000000..c6f8e8f11b --- /dev/null +++ b/spec/mongo/condition_variable_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true +# rubocop:todo all + +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/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 3282396d44..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' @@ -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 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..51fe649288 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' @@ -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/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 479b27457d..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' @@ -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/mongo/crypt/handle_spec.rb b/spec/mongo/crypt/handle_spec.rb index 6a2d31dde6..a7af8e3c83 100644 --- a/spec/mongo/crypt/handle_spec.rb +++ b/spec/mongo/crypt/handle_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all 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,21 @@ 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.and_call_original + handle + end + end end context 'Azure' do 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/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/mongo/crypt/kms/credentials_spec.rb b/spec/mongo/crypt/kms/credentials_spec.rb index 608c779c3c..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' @@ -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/mongo/crypt/kms_spec.rb b/spec/mongo/crypt/kms_spec.rb index 89f749af1d..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' @@ -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 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/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 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 64dedbde7e..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' @@ -39,14 +39,23 @@ # 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 + + 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 include_context 'with initialized pool' + require_no_linting let(:view) do Mongo::Collection::View.new(authorized_collection) @@ -64,6 +73,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 +94,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 +103,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 @@ -697,7 +709,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,25 +728,56 @@ 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 + 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 + require_no_linting before do + reset_pool(server) + end + + after do + server.pool.close + end + + 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 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 41c4dbca90..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' @@ -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/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 01627c61ce..1bab5470f5 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' @@ -956,11 +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 - expect(indexes[:wildcardProjection]).to eq({ '_id' => false, 'rating' => true }) + expect(indexes[:wildcardProjection]).to eq({ 'rating' => 1 }) end end end diff --git a/spec/mongo/lint_spec.rb b/spec/mongo/lint_spec.rb index 0ccdc5b489..807d1dbc8d 100644 --- a/spec/mongo/lint_spec.rb +++ b/spec/mongo/lint_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all -require 'lite_spec_helper' +require 'spec_helper' describe Mongo::Lint do 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/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/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/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/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/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/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..2a01fa6e17 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' @@ -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/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/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/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 cbf717faca..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' @@ -34,49 +34,4 @@ end end end - - describe "#needs_validation?" do - - 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/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/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/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/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/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 09391685ce..23a4f41d1b 100644 --- a/spec/mongo/protocol/msg_spec.rb +++ b/spec/mongo/protocol/msg_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# 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,31 +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 - let(:sequences) do [ section ] end @@ -403,10 +343,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 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/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 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/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 diff --git a/spec/mongo/retryable_spec.rb b/spec/mongo/retryable_spec.rb index bac23e3971..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' @@ -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 diff --git a/spec/mongo/semaphore_spec.rb b/spec/mongo/semaphore_spec.rb index 0c98e9173c..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' @@ -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/app_metadata/environment_spec.rb b/spec/mongo/server/app_metadata/environment_spec.rb new file mode 100644 index 0000000000..0b0b844b59 --- /dev/null +++ b/spec/mongo/server/app_metadata/environment_spec.rb @@ -0,0 +1,344 @@ +# 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 + 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 + + 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 + + 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 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', + '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_REGION' => 'cdg1' + ) + + it_behaves_like 'running in a FaaS environment' + + it 'recognizes Vercel' do + expect(env.name).to be == 'vercel' + 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 + + 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/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 72ea2a989b..a3b8619b63 100644 --- a/spec/mongo/server/app_metadata_spec.rb +++ b/spec/mongo/server/app_metadata_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -# encoding: utf-8 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,18 +13,8 @@ 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 - let(:client) do authorized_client.with(app_name: :app_metadata_test) end @@ -34,13 +24,12 @@ 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 - let(:client) do - authorized_client.with(app_name: "\u3042"*43) + authorized_client.with(app_name: "\u3042" * 43) end let(:cluster) do @@ -48,87 +37,88 @@ 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.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) + 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) + 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) + 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 + it_behaves_like 'a truncated document' + end + end + + context 'when run outside of a FaaS environment' do + 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 the driver info is too long' do - require_no_compression + 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 - 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 'excludes 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 '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' end end end @@ -154,17 +144,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 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 7d8784622b..3b6e88363a 100644 --- a/spec/mongo/server/connection_pool/populator_spec.rb +++ b/spec/mongo/server/connection_pool/populator_spec.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' -describe Mongo::Server::Populator do +describe Mongo::Server::ConnectionPool::Populator do + require_no_linting + let(:options) { {} } let(:client) do @@ -27,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.disconnect! pool.stop_populator - pool.clear end describe '#log_warn' do @@ -63,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 @@ -89,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 235761e07a..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' @@ -57,7 +57,17 @@ 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 + + let(:populate_semaphore) do + pool.instance_variable_get(:@populate_semaphore) + end + + let(:populator) do + pool.instance_variable_get(:@populator) end describe '#initialize' do @@ -107,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 @@ -295,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 @@ -304,7 +322,7 @@ end after do - server.disconnect! + server.close end let(:options) do @@ -403,7 +421,6 @@ it_behaves_like 'adds connection to the pool' end - end context 'connection of later generation than pool' do @@ -426,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 @@ -666,7 +709,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 @@ -683,13 +730,347 @@ 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 + 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 + + 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 + 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| + pool.ready # kill background thread to test disconnect behavior pool.stop_populator expect(pool.instance_variable_get('@populator').running?).to be false @@ -711,6 +1092,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 = [] @@ -722,10 +1104,16 @@ 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 new_connection = pool.check_out expect(old_connections).not_to include(new_connection) @@ -756,10 +1144,54 @@ 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 + + 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 + + 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 @@ -795,8 +1227,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 +1504,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 7c22e8ae12..a1acf2f69c 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' @@ -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, @@ -410,7 +414,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 @@ -886,13 +890,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 @@ -967,6 +971,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 +1236,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 +1250,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/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 f248ced6b2..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' @@ -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..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' @@ -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 @@ -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/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 c7c3b712e1..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' @@ -121,16 +121,43 @@ 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(: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.close + 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/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..caac0117be 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' @@ -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/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 55a7f4b5a3..37fcc92f08 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' @@ -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 @@ -152,10 +166,11 @@ 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) + allow(session).to receive('check_transactions_supported!').and_return true exc = Mongo::Error::OperationFailure.new('timeout test') exc.add_label(label) diff --git a/spec/mongo/socket/ssl_spec.rb b/spec/mongo/socket/ssl_spec.rb index aa02a428d3..ecdb74e06a 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' @@ -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 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 new file mode 100644 index 0000000000..4fa2ce236b --- /dev/null +++ b/spec/mongo/uri/options_mapper_spec.rb @@ -0,0 +1,1605 @@ +# frozen_string_literal: true +# rubocop:todo all + +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 "#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 } + 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 "#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 } + + 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 "#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 } + + 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 "#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 } + + 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 "#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 } + + 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 "#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 } + + 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 "#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 } + + 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 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 "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 "#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 } + + 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 "#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 } + + 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 "#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 } + + 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 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 + + 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 + + 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 the passed value" do + expect(reverted).to eq(value) + end + end + + context "when including multiple items" do + let(:value) { [ { k1: "v1", k2: "v2" } ] } + + it "returns the passed value" 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 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 "#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 } + + 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 "#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 } + + 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 + + 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..3efc41f910 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' @@ -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,20 @@ 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 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 @@ -926,6 +1038,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 +1050,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 +1062,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 +1073,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 +1084,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 +1095,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 +1108,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 +1121,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 +1134,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 +1146,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 +1159,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 +1177,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 +1193,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 +1216,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 +1227,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 +1237,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_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 36b79d10ee..0fffa6ef62 100644 --- a/spec/mongo/uri_spec.rb +++ b/spec/mongo/uri_spec.rb @@ -1,10 +1,16 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'lite_spec_helper' 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 @@ -258,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' } @@ -363,6 +362,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 +380,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 +390,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 +400,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 +410,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 +420,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 +430,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 +440,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 +469,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 +484,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 +501,8 @@ it 'returns the password' do expect(uri.credentials[:password]).to eq(password) end + + include_examples "roundtrips string" end end @@ -485,6 +515,8 @@ it 'returns the database name' do expect(uri.database).to eq(db) end + + include_examples "roundtrips string" end end @@ -498,6 +530,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 +548,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 +564,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 +580,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 +596,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 +613,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 +635,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 +651,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 +667,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 +683,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 +699,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end end @@ -667,6 +723,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 +744,8 @@ client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end + + include_examples "roundtrips string" end end @@ -734,6 +794,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 +814,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 +843,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 +874,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 +907,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 +926,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 +946,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 +972,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 +1004,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 +1024,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 +1055,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 +1067,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 +1101,8 @@ client = new_local_client_nmio(string) expect(client.options[:auth_source]).to eq(source) end + + include_examples "roundtrips string" end end @@ -1047,6 +1139,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'canonicalize_host_name' do @@ -1069,6 +1162,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'service_realm' do @@ -1091,6 +1185,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end context 'multiple properties' do @@ -1121,6 +1216,7 @@ end include_examples 'sets options in the expected manner' + include_examples "roundtrips string" end end @@ -1130,6 +1226,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 +1236,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 +1247,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 +1258,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 +1270,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 +1282,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 +1319,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 +1332,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 +1344,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 +1357,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 +1397,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 +1410,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 +1425,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 +1436,8 @@ client = new_local_client_nmio(string) expect(client.options[:zlib_compression_level]).to eq(6) end + + include_examples "roundtrips string" end end end 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 85c0715877..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' @@ -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 @@ -43,44 +40,50 @@ 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'] @expected_events = @test['events'] @ignore_events = @test['ignore'] || [] + @fail_point_command = @test['failPoint'] + @threads = Set.new - preprocess + process_run_on end - def setup(server, subscriber) - @subscriber = subscriber - @pool = server.pool + attr_reader :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 + 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) + server.instance_variable_set(:@pool, @pool) + + configure_fail_point end def run state = {} {}.tap do |result| - processed_ops.each do |op| + spec_ops.each do |op| err = op.run(pool, state) if err result['error'] = err break + elsif op.name == 'start' + @threads << state[op.target] end 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 @@ -141,13 +144,54 @@ def run { 'type' => 'ConnectionPoolCleared', 'address' => event.address, + 'interruptInUseConnections' => event.options[:interrupt_in_use_connections] } + 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 + 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? + 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 + if @oses + ok &&= @oses.any? { |os| SpecConfig.instance.send("#{os.to_s}?")} + end + ok end private @@ -177,7 +221,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 @@ -189,33 +233,67 @@ 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. + when 'maxConnecting' + opts[:max_connecting] = kv.last + when 'appName' + opts[:app_name] = kv.last else raise "Unknown option #{kv.first}" end - - opts 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 + 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' } + (topologies['topology'] || {}).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 + end - 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 + @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 + + 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. class Operation + include RSpec::Mocks::ExampleMethods # @return [ String ] command The name of the operation to run. attr_reader :name @@ -227,15 +305,16 @@ 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 # @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 @@ -250,21 +329,24 @@ def initialize(spec, operation) @spec = spec @name = operation['name'] @thread = operation['thread'] - @thread_ops = [] @target = operation['target'] @ms = operation['ms'] @label = operation['label'] @connection = operation['connection'] @event = operation['event'] @count = operation['count'] + @interrupt_in_use_connections = !!operation['interruptInUseConnections'] 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) + when 'ready' + run_ready_op(state) when 'wait' run_wait_op(state) when 'waitForThread' @@ -306,10 +388,30 @@ 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 { |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 # 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. @@ -323,13 +425,19 @@ 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) 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') @@ -340,7 +448,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 @@ -362,12 +470,47 @@ 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, interrupt_in_use_connections: interrupt_in_use_connections) + end end def run_close_op(state) pool.close end + + 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/runners/cmap/verifier.rb b/spec/runners/cmap/verifier.rb index 491308b3d8..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. # @@ -41,7 +41,7 @@ def verify_hashes(actual, expected) end end - expect(actual_modified).to eq(expected) + expect(actual_modified.slice(*expected.keys)).to eq(expected) end end end 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..b0a76b5aca 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. # @@ -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/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 a702a6120c..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. # @@ -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/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 d8e98169b2..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 @@ -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']) diff --git a/spec/runners/crud/spec.rb b/spec/runners/crud/spec.rb index 5a3b53b576..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 @@ -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/test.rb b/spec/runners/crud/test.rb index a049ecf413..ae210b584f 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 @@ -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 diff --git a/spec/runners/crud/test_base.rb b/spec/runners/crud/test_base.rb index e4f9a87ca8..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 @@ -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/runners/crud/verifier.rb b/spec/runners/crud/verifier.rb index 27483b444c..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. # @@ -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/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 d06f6d4b85..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. # @@ -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, @@ -178,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 @@ -312,17 +309,26 @@ 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 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/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 ba2a138714..8364b1713c 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. # @@ -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,15 +77,19 @@ def initialize(crud_spec, data, test, expectations_bson_types: true) Operation.new(self, op) end + 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'] - @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 @@ -122,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) @@ -132,6 +141,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( @@ -280,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 @@ -317,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.rb b/spec/runners/unified.rb index c82101c11c..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' @@ -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 @@ -61,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 @@ -69,11 +72,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 @@ -91,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..908fe80fbb 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 @@ -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') @@ -118,7 +122,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}" @@ -156,7 +160,7 @@ def assert_events else false end - actual_events = subscriber.wanted_events(@observe_sensitive) + actual_events = subscriber.wanted_events(@observe_sensitive[client_id]) case spec.use('eventType') when nil, 'command' actual_events.select! do |event| @@ -205,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 @@ -216,6 +223,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"] @@ -238,9 +251,13 @@ 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 = get_actual_value(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 @@ -253,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 @@ -273,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/runners/unified/change_stream_operations.rb b/spec/runners/unified/change_stream_operations.rb index 6fa98d5971..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 @@ -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 @@ -37,5 +38,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/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 99d2196452..69e35513a8 100644 --- a/spec/runners/unified/crud_operations.rb +++ b/spec/runners/unified/crud_operations.rb @@ -1,11 +1,19 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all 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 = { @@ -24,6 +32,18 @@ def find(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) @@ -37,7 +57,7 @@ def find(op) if projection = args.use('projection') req = req.projection(projection) end - result = req.to_a + req end end @@ -93,6 +113,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 a8fcfa795c..185a251b7a 100644 --- a/spec/runners/unified/ddl_operations.rb +++ b/spec/runners/unified/ddl_operations.rb @@ -1,18 +1,26 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all 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 @@ -39,11 +47,22 @@ 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 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 = {} @@ -53,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 @@ -122,10 +141,13 @@ 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'), - name: args.use!('name'), + name: args.use('name'), **opts, ) end @@ -175,5 +197,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/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 42490b4a6e..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' @@ -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/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/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/support_operations.rb b/spec/runners/unified/support_operations.rb index 0f2d3fdafc..a99d310958 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 @@ -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 @@ -26,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 @@ -61,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 @@ -70,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 @@ -83,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 @@ -247,6 +254,61 @@ 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) + 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 + + 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 + + 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 c2f7353bee..114088f693 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' @@ -8,6 +8,8 @@ require 'runners/unified/ddl_operations' 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' @@ -21,6 +23,8 @@ class Test include DdlOperations include ChangeStreamOperations include SupportOperations + include ThreadOperations + include SearchIndexOperations include Assertions include RSpec::Core::Pending @@ -46,6 +50,7 @@ def initialize(spec, **opts) @multiple_mongoses = mongoses.any? { |v| v } @test_spec.freeze @subscribers = {} + @observe_sensitive = {} @options = opts end @@ -56,6 +61,10 @@ def initialize(spec, **opts) attr_reader :reqs, :group_reqs attr_reader :options + def retry? + @description =~ /KMS/i + end + def skip? !!@skip_reason end @@ -70,8 +79,13 @@ def require_single_mongos? attr_reader :entities - def create_entities - @spec['createEntities'].each do |entity_spec| + def create_spec_entities + return if @entities_created + 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 @@ -88,6 +102,13 @@ def create_entities 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 @@ -101,7 +122,7 @@ def create_entities # 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') @@ -109,8 +130,6 @@ def create_entities 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 @@ -134,10 +153,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(client) if current_proc + if oe = observe_events oe.each do |event| case event when 'commandStartedEvent', 'commandSucceededEvent', 'commandFailedEvent' @@ -155,12 +176,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[id] = spec.use('observeSensitiveCommands') + @subscribers[client] ||= subscriber + end when 'database' client = entities.get(:client, spec.use!('client')) opts = Utils.snakeize_hash(spec.use('databaseOptions') || {}) @@ -195,7 +227,9 @@ def create_entities 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']) @@ -251,6 +285,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 0.1 + end + end + end + class << thread + attr_accessor :context + end + thread.context = thread_context + thread else raise NotImplementedError, "Unknown type #{type}" end @@ -259,6 +315,7 @@ def create_entities end entities.set(type.to_sym, id, entity) end + @entities_created = true end def set_initial_data @@ -267,26 +324,31 @@ 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 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 @@ -337,7 +399,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 @@ -348,7 +410,7 @@ def execute_operation(op) end public_send(method_name, op) - rescue Mongo::Error, BSON::String::IllegalKey, 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 @@ -385,9 +447,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. @@ -398,6 +462,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}" @@ -507,5 +581,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/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 new file mode 100644 index 0000000000..ef63cf5fb0 --- /dev/null +++ b/spec/runners/unified/thread_operations.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true +# rubocop:todo all + +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/shared b/spec/shared index a8eadfb319..cee4bc0264 160000 --- a/spec/shared +++ b/spec/shared @@ -1 +1 @@ -Subproject commit a8eadfb31900d8fa5d4fa03fc4869ed5b119914b +Subproject commit cee4bc02649a573c8256b0505c1d23f503ac2609 diff --git a/spec/solo/clean_exit_spec.rb b/spec/solo/clean_exit_spec.rb index 12d5d66886..80e80fd030 100644 --- a/spec/solo/clean_exit_spec.rb +++ b/spec/solo/clean_exit_spec.rb @@ -1,19 +1,12 @@ # frozen_string_literal: true -# encoding: utf-8 +# 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/spec_helper.rb b/spec/spec_helper.rb index 6e2d32f432..f3ffc785e2 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' @@ -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/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 f9da7dd06c..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' @@ -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/client_side_encryption_spec.rb b/spec/spec_tests/client_side_encryption_spec.rb index d96007ab70..164151c7db 100644 --- a/spec/spec_tests/client_side_encryption_spec.rb +++ b/spec/spec_tests/client_side_encryption_spec.rb @@ -1,14 +1,37 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' 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: EXPECTATIONS_BSON_TYPES) + 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 - define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: false) + SpecConfig.instance.require_crypt_shared do + define_transactions_spec_tests(CLIENT_SIDE_ENCRYPTION_TESTS, expectations_bson_types: EXPECTATIONS_BSON_TYPES) + 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..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' @@ -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/spec_tests/cmap_spec.rb b/spec/spec_tests/cmap_spec.rb index b164866558..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' @@ -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) @@ -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 @@ -54,28 +59,37 @@ 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 ) - spec.setup(@server, subscriber) + + 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) 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/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/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..b1d9f20e01 --- /dev/null +++ b/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml @@ -0,0 +1,41 @@ +description: "change-streams-clusterTime" +schemaVersion: "1.4" +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, load-balanced, sharded ] + serverless: forbid + +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 } 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..9ca9abf2e3 --- /dev/null +++ b/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml @@ -0,0 +1,103 @@ +description: "disambiguatedPaths" +schemaVersion: "1.4" +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, load-balanced, sharded ] + serverless: forbid + +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'] } 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..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,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, 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, 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 6a883443c8..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,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, 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..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,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, 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 fa4360c074..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,8 @@ description: "change-streams-showExpandedEvents" schemaVersion: "1.7" runOnRequirements: - minServerVersion: "6.0.0" - topologies: [ replicaset, sharded-replicaset, sharded ] + topologies: [ replicaset, sharded ] + serverless: forbid createEntities: - client: id: &client0 client0 @@ -160,7 +161,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 +268,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: @@ -269,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 @@ -292,6 +300,7 @@ tests: shardCollection: shardedDb.shardedCollection key: _id: 1 + commandName: shardCollection - name: iterateUntilDocumentOrError object: *changeStream0 expectResult: 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/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/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 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 51% 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 f653527d36..51e7a56758 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,14 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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 +24,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 +33,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 +47,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 +83,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 85% 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 b5e788f369..2398a5cd44 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,13 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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..5cc6ead0f6 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection-OldServer.yml @@ -0,0 +1,61 @@ +# 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." + # 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/fle2-CreateCollection.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml similarity index 54% 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 160da07ff9..43dbccfc37 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-CreateCollection.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-CreateCollection.yml @@ -1,8 +1,9 @@ -# This test requires libmongocrypt 1.5.0-alpha2. +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" @@ -14,10 +15,7 @@ tests: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: &encrypted_fields0 { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", + default.encryptedCollection: &encrypted_fields { "fields": [ { "path": "firstName", @@ -32,58 +30,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: "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: "encryptedCollection.ecc" + collection: &ecc_collection_name "enxcol_.encryptedCollection.ecc" - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "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: "encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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 +85,35 @@ tests: # State collections are created first. - command_started_event: command: - create: "encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "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 { + "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 +126,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 +188,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 +221,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" - 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 @@ -354,33 +305,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 +335,27 @@ tests: # 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 - - 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": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } - default.encryptedCollection.esc: { - "escCollection": "encryptedCollection", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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: "encryptedCollection.esc" - - name: assertCollectionExists - object: testRunner - arguments: - database: *database_name - collection: "encryptedCollection.ecc" - - name: assertCollectionExists - object: testRunner - arguments: - database: *database_name - collection: "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: "encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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 - # State collections are created first. - - command_started_event: - command: - create: "encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "encryptedCollection.ecc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "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": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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 +398,60 @@ tests: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: &encrypted_fields4 { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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: "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: "encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "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: "encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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 @@ -673,33 +459,27 @@ tests: # State collections are created first. - command_started_event: command: - create: "encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "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 +496,56 @@ tests: - name: dropCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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: "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: "encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "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: "encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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 +553,34 @@ tests: # State collections are created first. - command_started_event: command: - create: "encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "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 +594,27 @@ tests: kmsProviders: aws: {} # Credentials filled in from environment. encryptedFieldsMap: - default.encryptedCollection: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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: "encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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 @@ -897,156 +629,111 @@ tests: - name: dropCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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: "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: "encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "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": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": { "$binary": { "subType": "04", "base64": "AAAAAAAAAAAAAAAAAAAAAA==" }} - } - ] - } - - name: assertCollectionNotExists - object: testRunner - arguments: - database: *database_name - collection: "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: "encryptedCollection.ecc" + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "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: "encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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: "encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "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 +743,17 @@ tests: # events from dropCollection ... begin - command_started_event: command: - drop: "encryptedCollection.esc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "encryptedCollection.ecc" + drop: *ecoc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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 +770,111 @@ tests: - name: dropCollection object: database arguments: - collection: "encryptedCollection" - encryptedFields: { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "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: "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: "encryptedCollection.ecc" + collection: *ecc_collection_name - name: assertCollectionExists object: testRunner arguments: database: *database_name - collection: "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: "encryptedCollection.esc" + collection: *esc_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "encryptedCollection.ecc" + collection: *ecoc_collection_name + # ecc collection is no longer created for QEv2 - name: assertCollectionNotExists object: testRunner arguments: database: *database_name - collection: "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: "encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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: "encryptedCollection.esc" - clusteredIndex: {key: {_id: 1}, unique: true} - command_name: create - database_name: *database_name - - command_started_event: - command: - create: "encryptedCollection.ecc" + create: *esc_collection_name clusteredIndex: {key: {_id: 1}, unique: true} command_name: create database_name: *database_name - command_started_event: command: - create: "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 +885,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: "encryptedCollection.esc" - command_name: drop - database_name: *database_name - - command_started_event: - command: - drop: "encryptedCollection.ecc" + drop: *esc_collection_name command_name: drop database_name: *database_name - command_started_event: command: - drop: "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 + - 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" 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 91% 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 c799eae21d..952551ba3a 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-DecryptExistingData.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-DecryptExistingData.yml @@ -1,7 +1,9 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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/fle2v2-Delete.yml similarity index 67% 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 6b6714233a..4e1a59c93f 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,13 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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 +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: @@ -71,7 +77,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -79,28 +85,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 5d26e2e8b1..8767132e62 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,13 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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 +17,6 @@ tests: local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} encryptedFieldsMap: { "default.default": { - "escCollection": "esc", - "eccCollection": "ecc", - "ecocCollection": "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 71% 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 6b822ff724..119da443fe 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,7 +1,9 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] @@ -9,7 +11,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 +65,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 +79,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVjACAAAAAAWuidNu47c9A4Clic3DvFhn1AQJVC+FJtoE5bGZuz6PsFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVsACAAAAAApJtKPW4+o9B7gAynNLL26jtlB4+hq5TXResijcYet8USY20AAAAAAAAAAAAA", "subType": "06" } } @@ -81,7 +88,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 86% 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 395b8c27bd..ec91ebf1ca 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,7 +1,9 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [] @@ -42,10 +44,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 71% 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 96ce954bec..7849b6032a 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,13 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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 +63,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 +77,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -80,24 +87,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 +157,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 +172,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -182,25 +182,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 71% 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 5b785d8cf2..2acd97585b 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,13 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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 +61,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 +74,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVjACAAAAAAWuidNu47c9A4Clic3DvFhn1AQJVC+FJtoE5bGZuz6PsFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPGmZcUzdE/FPILvRSyAScGvZparGI2y9rJ/vSBxgCujBXMAIAAAAACi1RjmndKqgnXy7xb22RzUbnZl1sOZRXPOC0KcJkAxmQVsACAAAAAApJtKPW4+o9B7gAynNLL26jtlB4+hq5TXResijcYet8USY20AAAAAAAAAAAAA", "subType": "06" } } @@ -77,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: 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 74% 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 26071fff60..ddba6c7439 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,13 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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" @@ -80,4 +82,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 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 69% 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 94c624538b..2d9eb6e580 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-MissingKey.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-MissingKey.yml @@ -1,7 +1,9 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" data: [ @@ -14,7 +16,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 82% 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 7a547eee0f..afb79d526b 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-NoEncryption.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-NoEncryption.yml @@ -1,7 +1,9 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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/fle2v2-Range-Date-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml new file mode 100644 index 0000000000..93ad199511 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml @@ -0,0 +1,241 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + 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: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "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" + } + } + } + } + } + ] + cursor: {} + 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: 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/fle2v2-Range-Date-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml new file mode 100644 index 0000000000..d60b7b6201 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-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.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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/fle2v2-Range-Date-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml new file mode 100644 index 0000000000..0b969fd48c --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml @@ -0,0 +1,182 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + 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: + delete: *collection_name + deletes: [ + { + "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" + } + } + } + }, + "limit": 1 + } + ] + 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: 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/fle2v2-Range-Date-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml new file mode 100644 index 0000000000..76bfe7ea25 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml @@ -0,0 +1,239 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + 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: + findAndModify: *collection_name + query: { + "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" + } + } + } + } + update: { "$set": {"encryptedDate": { $$type: "binData" }} } + 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: 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/fle2v2-Range-Date-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml new file mode 100644 index 0000000000..e978b9de59 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml @@ -0,0 +1,235 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + 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: + find: *collection_name + filter: + "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" + } + } + } + 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: 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/fle2v2-Range-Date-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml new file mode 100644 index 0000000000..fe7d050f1b --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml @@ -0,0 +1,252 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + 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_name: update + command: + "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": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$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/fle2v2-Range-Decimal-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml new file mode 100644 index 0000000000..0926988d27 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml @@ -0,0 +1,1687 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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 {'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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDecimalNoPrecision": { $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, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + 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: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$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" + }, + "encryptedDecimalNoPrecision": { $$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/fle2v2-Range-Decimal-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml new file mode 100644 index 0000000000..1961ed3e5f --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml @@ -0,0 +1,293 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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 {'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" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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, encryptedDecimalNoPrecision: { $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: { encryptedDecimalNoPrecision: { $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/fle2v2-Range-Decimal-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml new file mode 100644 index 0000000000..bdb10e2d88 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml @@ -0,0 +1,905 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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 {'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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDecimalNoPrecision": { $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, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + 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: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$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/fle2v2-Range-Decimal-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml new file mode 100644 index 0000000000..defccea0aa --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml @@ -0,0 +1,1684 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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 {'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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDecimalNoPrecision: { $gt: {$numberDecimal: "0"}} } + update: { "$set": { "encryptedDecimalNoPrecision": {$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, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + findAndModify: *collection_name + query: { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDecimalNoPrecision": { $$type: "binData" }} } + 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: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$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" + }, + "encryptedDecimalNoPrecision": { $$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/fle2v2-Range-Decimal-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml new file mode 100644 index 0000000000..51abaa6423 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml @@ -0,0 +1,1680 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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 {'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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $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, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + find: *collection_name + filter: + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "DeFiAAADcGF5bG9hZACxYgAABGcAnWIAAAMwAH0AAAAFZAAgAAAAAJu2KgiI8vM+kz9qD3ZQzFQY5qbgYqCqHG5R4jAlnlwXBXMAIAAAAAAAUXxFXsz764T79sGCdhxvNd5b6E/9p61FonsHyEIhogVsACAAAAAAt19RL3Oo5ni5L8kcvgOJYLgVYyXJExwP8pkuzLG7f/kAAzEAfQAAAAVkACAAAAAAPQPvL0ARjujSv2Rkm8r7spVsgeC1K3FWcskGGZ3OdDIFcwAgAAAAACgNn660GmefR8jLqzgR1u5O+Uocx9GyEHiBqVGko5FZBWwAIAAAAADflr+fsnZngm6KRWYgHa9JzK+bXogWl9evBU9sQUHPHQADMgB9AAAABWQAIAAAAAD2Zi6kcxmaD2mY3VWrP+wYJMPg6cSBIYPapxaFQxYFdQVzACAAAAAAM/cV36BLBY3xFBXsXJY8M9EHHOc/qrmdc2CJmj3M89gFbAAgAAAAAOpydOrKxx6m2gquSDV2Vv3w10GocmNCFeOo/fRhRH9JAAMzAH0AAAAFZAAgAAAAAOaNqI9srQ/mI9gwbk+VkizGBBH/PPWOVusgnfPk3tY1BXMAIAAAAAAc96O/pwKCmHCagT6T/QV/wz4vqO+R22GsZ1dse2Vg6QVsACAAAAAAgzIak+Q3UFLTHXPmJ+MuEklFtR3eLtvM+jdKkmGCV/YAAzQAfQAAAAVkACAAAAAA0XlQgy/Yu97EQOjronl9b3dcR1DFn3deuVhtTLbJZHkFcwAgAAAAACoMnpVl6EFJak8A+t5N4RFnQhkQEBnNAx8wDqmq5U/dBWwAIAAAAACR26FJif673qpwF1J1FEkQGJ1Ywcr/ZW6JQ7meGqzt1QADNQB9AAAABWQAIAAAAAAOtpNexRxfv0yRFvZO9DhlkpU4mDuAb8ykdLnE5Vf1VAVzACAAAAAAeblFKm/30orP16uQpZslvsoS8s0xfNPIBlw3VkHeekYFbAAgAAAAAPEoHj87sYE+nBut52/LPvleWQBzB/uaJFnosxp4NRO2AAM2AH0AAAAFZAAgAAAAAIr8xAFm1zPmrvW4Vy5Ct0W8FxMmyPmFzdWVzesBhAJFBXMAIAAAAABYeeXjJEzTHwxab6pUiCRiZjxgtN59a1y8Szy3hfkg+gVsACAAAAAAJuoY4rF8mbI+nKb+5XbZShJ8191o/e8ZCRHE0O4Ey8MAAzcAfQAAAAVkACAAAAAAl+ibLk0/+EwoqeC8S8cGgAtjtpQWGEZDsybMPnrrkwEFcwAgAAAAAHPPBudWgQ+HUorLDpJMqhS9VBF2VF5aLcxgrM1s+yU7BWwAIAAAAAAcCcBR2Vyv5pAFbaOU97yovuOi1+ATDnLLcAUqHecXcAADOAB9AAAABWQAIAAAAACR9erwLTb+tcWFZgJ2MEfM0PKI9uuwIjDTHADRFgD+SQVzACAAAAAAcOop8TXsGUVQoKhzUllMYWxL93xCOkwtIpV8Q6hiSYYFbAAgAAAAAKXKmh4V8veYwob1H03Q3p3PN8SRAaQwDT34KlNVUjiDAAM5AH0AAAAFZAAgAAAAALv0vCPgh7QpmM8Ug6ad5ioZJCh7pLMdT8FYyQioBQ6KBXMAIAAAAADsCPyIG8t6ApQkRk1fX/sfc1kpuWCWP8gAEpnYoBSHrQVsACAAAAAAJe/r67N6d8uTiogvfoR9rEXbIDjyLb9EVdqkayFFGaYAAzEwAH0AAAAFZAAgAAAAAIW4AxJgYoM0pcNTwk1RSbyjZGIqgKL1hcTJmNrnZmoPBXMAIAAAAAAZpfx3EFO0vY0f1eHnE0PazgqeNDTaj+pPJMUNW8lFrAVsACAAAAAAP+Um2vwW6Bj6vuz9DKz6+6aWkoKoEmFNoiz/xXm7lOsAAzExAH0AAAAFZAAgAAAAAKliO6L9zgeuufjj174hvmQGNRbmYYs9yAirL7OxwEW3BXMAIAAAAAAqU7vs3DWUQ95Eq8OejwWnD0GuXd+ASi/uD6S0l8MM1QVsACAAAAAAb9legYzsfctBPpHyl7YWpPmLr5QiNZFND/50N1vv2MUAAzEyAH0AAAAFZAAgAAAAAOGQcCBkk+j/Kzjt/Cs6g3BZPJG81wIHBS8JewHGpgk+BXMAIAAAAABjrxZXWCkdzrExwCgyHaafuPSQ4V4x2k9kUCAqUaYKDQVsACAAAAAADBU6KefT0v8zSmseaMNmQxKjJar72y7MojLFhkEHqrUAAzEzAH0AAAAFZAAgAAAAAPmCNEt4t97waOSd5hNi2fNCdWEkmcFJ37LI9k4Az4/5BXMAIAAAAABX7DuDPNg+duvELf3NbLWkPMFw2HGLgWGHyVWcPvSNCAVsACAAAAAAS7El1FtZ5STh8Q1FguvieyYX9b2DF1DFVsb9hzxXYRsAAzE0AH0AAAAFZAAgAAAAAD4vtVUYRNB+FD9yoQ2FVJH3nMeJeKbi6eZfth638YqbBXMAIAAAAAANCuUB4OdmuD6LaDK2f3vaqfgYYvg40wDXOBbcFjTqLwVsACAAAAAA9hqC2VoJBjwR7hcQ45xO8ZVojwC83jiRacCaDj6Px2gAAzE1AH0AAAAFZAAgAAAAAJPIRzjmTjbdIvshG6UslbEOd797ZSIdjGAhGWxVQvK1BXMAIAAAAABgmJ0Jh8WLs9IYs/a7DBjDWd8J3thW/AGJK7zDnMeYOAVsACAAAAAAi9zAsyAuou2oiCUHGc6QefLUkACa9IgeBhGu9W/r0X8AAzE2AH0AAAAFZAAgAAAAAABQyKQPoW8wGPIqnsTv69+DzIdRkohRhOhDmyVHkw9WBXMAIAAAAAAqWA2X4tB/h3O1Xlawtz6ndI6WaTwgU1QYflL35opu5gVsACAAAAAAWI/Gj5aZMwDIxztqmVL0g5LBcI8EdKEc2UA28pnekQoAAzE3AH0AAAAFZAAgAAAAACB7NOyGQ1Id3MYnxtBXqyZ5Ul/lHH6p1b10U63DfT6bBXMAIAAAAADpOryIcndxztkHSfLN3Kzq29sD8djS0PspDSqERMqokQVsACAAAAAADatsMW4ezgnyi1PiP7xk+gA4AFIN/fb5uJqfVkjg4UoAAzE4AH0AAAAFZAAgAAAAAKVfXLfs8XA14CRTB56oZwV+bFJN5BHraTXbqEXZDmTkBXMAIAAAAAASRWTsfGOpqdffiOodoqIgBzG/yzFyjR5CfUsIUIWGpgVsACAAAAAAkgCHbCwyX640/0Ni8+MoYxeHUiC+FSU4Mn9jTLYtgZgAAzE5AH0AAAAFZAAgAAAAAH/aZr4EuS0/noQR9rcF8vwoaxnxrwgOsSJ0ys8PkHhGBXMAIAAAAACd7ObGQW7qfddcvyxRTkPuvq/PHu7+6I5dxwS1Lzy5XAVsACAAAAAA3q0eKdV7KeU3pc+CtfypKR7BPxwaf30yu0j9FXeOOboAAzIwAH0AAAAFZAAgAAAAAKvlcpFFNq0oA+urq3w6d80PK1HHHw0H0yVWvU9aHijXBXMAIAAAAADWnAHQ5Fhlcjawki7kWzdqjM2f6IdGJblojrYElWjsZgVsACAAAAAAO0wvY66l24gx8nRxyVGC0QcTztIi81Kx3ndRhuZr6W4AAzIxAH0AAAAFZAAgAAAAAH/2aMezEOddrq+dNOkDrdqf13h2ttOnexZsJxG1G6PNBXMAIAAAAABNtgnibjC4VKy5poYjvdsBBnVvDTF/4mmEAxsXVgZVKgVsACAAAAAAqvadzJFLqQbs8WxgZ2D2X+XnaPSDMLCVVgWxx5jnLcYAAzIyAH0AAAAFZAAgAAAAAF2wZoDL6/V59QqO8vdRZWDpXpkV4h4KOCSn5e7x7nmzBXMAIAAAAADLZBu7LCYjbThaVUqMK14H/elrVOYIKJQCx4C9Yjw37gVsACAAAAAAEh6Vs81jLU204aGpL90fmYTm5i5R8/RT1uIbg6VU3HwAAzIzAH0AAAAFZAAgAAAAAH27yYaLn9zh2CpvaoomUPercSfJRUmBY6XFqmhcXi9QBXMAIAAAAAAUwumVlIYIs9JhDhSj0R0+59psCMsFk94E62VxkPt42QVsACAAAAAAT5x2hCCd2bpmpnyWaxas8nSxTc8e4C9DfKaqr0ABEysAAzI0AH0AAAAFZAAgAAAAALMg2kNAO4AFFs/mW3In04yFeN4AP6Vo0klyUoT06RquBXMAIAAAAAAgGWJbeIdwlpqXCyVIYSs0dt54Rfc8JF4b8uYc+YUj0AVsACAAAAAAWHeWxIkyvXTOWvfZzqtPXjfGaWWKjGSIQENTU3zBCrsAAzI1AH0AAAAFZAAgAAAAALas/i1T2DFCEmrrLEi7O2ngJZyFHialOoedVXS+OjenBXMAIAAAAAA1kK0QxY4REcGxHeMkgumyF7iwlsRFtw9MlbSSoQY7uAVsACAAAAAAUNlpMJZs1p4HfsD4Q4WZ4TBEi6Oc2fX34rzyynqWCdwAAzI2AH0AAAAFZAAgAAAAAP1TejmWg1CEuNSMt6NUgeQ5lT+oBoeyF7d2l5xQrbXWBXMAIAAAAABPX0kj6obggdJShmqtVfueKHplH4ZrXusiwrRDHMOKeQVsACAAAAAAIYOsNwC3DA7fLcOzqdr0bOFdHCfmK8tLwPoaE9uKOosAAzI3AH0AAAAFZAAgAAAAAMrKn+QPa/NxYezNhlOX9nyEkN1kE/gW7EuZkVqYl0b8BXMAIAAAAABUoZMSPUywRGfX2EEencJEKH5x/P9ySUVrhStAwgR/LgVsACAAAAAAMgZFH6lQIIDrgHnFeslv3ld20ynwQjQJt3cAp4GgrFkAAzI4AH0AAAAFZAAgAAAAAMmD1+a+oVbiUZd1HuZqdgtdVsVKwuWAn3/M1B6QGBM3BXMAIAAAAACLyytOYuZ9WEsIrrtJbXUx4QgipbaAbmlJvSZVkGi0CAVsACAAAAAA4v1lSp5H9BB+HYJ4bH43tC8aeuPZMf78Ng1JOhJh190AAzI5AH0AAAAFZAAgAAAAAOVKV7IuFwmYP1qVv8h0NvJmfPICu8yQhzjG7oJdTLDoBXMAIAAAAABL70XLfQLKRsw1deJ2MUvxSWKxpF/Ez73jqtbLvqbuogVsACAAAAAAvfgzIorXxE91dDt4nQxYfntTsx0M8Gzdsao5naQqcRUAAzMwAH0AAAAFZAAgAAAAAKS/1RSAQma+xV9rz04IcdzmavtrBDjOKPM+Z2NEyYfPBXMAIAAAAAAOJDWGORDgfRv8+w5nunh41wXb2hCA0MRzwnLnQtIqPgVsACAAAAAAf42C1+T7xdHEFF83+c2mF5S8PuuL22ogXXELnRAZ4boAAzMxAH0AAAAFZAAgAAAAAFeq8o82uNY1X8cH6OhdTzHNBUnCChsEDs5tm0kPBz3qBXMAIAAAAABaxMBbsaeEj/EDtr8nZfrhhhirBRPJwVamDo5WwbgvTQVsACAAAAAAMbH453A+BYAaDOTo5kdhV1VdND1avNwvshEG/4MIJjQAAzMyAH0AAAAFZAAgAAAAAI8IKIfDrohHh2cjspJHCovqroSr5N3QyVtNzFvT5+FzBXMAIAAAAABXHXteKG0DoOMmECKp6ro1MZNQvXGzqTDdZ0DUc8QfFAVsACAAAAAA/w5s++XYmO+9TWTbtGc3n3ndV4T9JUribIbF4jmDLSMAAzMzAH0AAAAFZAAgAAAAAJkHvm15kIu1OtAiaByj5ieWqzxiu/epK6c/9+KYIrB0BXMAIAAAAACzg5TcyANk0nes/wCJudd1BwlkWWF6zw3nGclq5v3SJQVsACAAAAAAvruXHTT3irPJLyWpI1j/Xwf2FeIE/IV+6Z49pqRzISoAAzM0AH0AAAAFZAAgAAAAAAYSOvEWWuSg1Aym7EssNLR+xsY7e9BcwsX4JKlnSHJcBXMAIAAAAABT48eY3PXVDOjw7JpNjOe1j2JyI3LjDnQoqZ8Je5B2KgVsACAAAAAAU2815RR57TQ9uDg0XjWjBkAKvf8yssxDMzrM4+FqP6AAAzM1AH0AAAAFZAAgAAAAAGQxC9L1e9DfO5XZvX1yvc3hTLtQEdKO9FPMkyg0Y9ZABXMAIAAAAADtmcMNJwdWLxQEArMGZQyzpnu+Z5yMmPAkvgq4eAKwNQVsACAAAAAAJ88zt4Y/Hoqh+zrf6KCOiUwHbOzCxSfp6k/qsZaYGEgAAzM2AH0AAAAFZAAgAAAAADLHK2LNCNRO0pv8n4fAsxwtUqCNnVK8rRgNiQfXpHSdBXMAIAAAAACf16EBIHRKD3SzjRW+LMOl+47QXA3CJhMzlcqyFRW22AVsACAAAAAAMGz4fAOa0EoVv90fUffwLjBrQhHATf+NdlgCR65vujAAAzM3AH0AAAAFZAAgAAAAAHiZJiXKNF8bbukQGsdYkEi95I+FSBHy1I5/hK2uEZruBXMAIAAAAADE+lZBa8HDUJPN+bF6xI9x4N7GF9pj3vBR7y0BcfFhBAVsACAAAAAAGIEN6sfqq30nyxW4dxDgXr/jz5HmvA9T1jx/pKCn4zgAAzM4AH0AAAAFZAAgAAAAAI1oa2OIw5TvhT14tYCGmhanUoYcCZtNbrVbeoMldHNZBXMAIAAAAAAx2nS0Ipblf2XOgBiUOuJFBupBhe7nb6QPLZlA4aMPCgVsACAAAAAA9xu828hugIgo0E3de9dZD+gTpVUGlwtDba+tw/WcbUoAAzM5AH0AAAAFZAAgAAAAABgTWS3Yap7Q59hii/uPPimHWXsr+DUmsqfwt/X73qsOBXMAIAAAAACKK05liW5KrmEAvtpCB1WUltruzUylDDpjea//UlWoOAVsACAAAAAAcgN4P/wakJ5aJK5c1bvJBqpVGND221dli2YicPFfuAYAAzQwAH0AAAAFZAAgAAAAABOAnBPXDp6i9TISQXvcNKwGDLepZTu3cKrB4vKnSCjBBXMAIAAAAADjjzZO7UowAAvpwyG8BNOVqLCccMFk3aDK4unUeft5ywVsACAAAAAA4zkCd4k9gvfXoD1C7vwTjNcdVJwEARh8h/cxZ4PNMfgAAzQxAH0AAAAFZAAgAAAAAHN8hyvT1lYrAsdiV5GBdd5jhtrAYE/KnSjw2Ka9hjz9BXMAIAAAAAD794JK7EeXBs+D7yOVK7nWF8SbZ/7U8gZ7nnT9JFNwTAVsACAAAAAAg8Wt1HO3NhByq2ggux2a4Lo6Gryr24rEFIqh2acrwWMAAzQyAH0AAAAFZAAgAAAAAO93bPrq8bsnp1AtNd9ETnXIz0lH/2HYN/vuw9wA3fyFBXMAIAAAAABHlls5fbaF2oAGqptC481XQ4eYxInTC29aElfmVZgDUgVsACAAAAAANoQXEWpXJpgrSNK/cKi/m7oYhuSRlp1IZBF0bqTEATcAAzQzAH0AAAAFZAAgAAAAAL1YsAZm1SA0ztU6ySIrQgCCA74V6rr0/4iIygCcaJL6BXMAIAAAAADTXWTHWovGmUR1Zg9l/Aqq9H5mOCJQQrb/Dfae7e3wKAVsACAAAAAA5dunyJK6/SVfDD0t9QlNBcFqoZnf9legRjHaLSKAoQMAAzQ0AH0AAAAFZAAgAAAAAEoFAeHk0RZ9kD+cJRD3j7PcE5gzWKnyBrF1I/MDNp5mBXMAIAAAAACgHtc2hMBRSZjKw8RAdDHK+Pi1HeyjiBuAslGVNcW5tAVsACAAAAAAXzBLfq+GxRtX4Wa9fazA49DBLG6AjZm2XODStJKH8D0AAzQ1AH0AAAAFZAAgAAAAAAW+7DmSN/LX+/0uBVJDHIc2dhxAGz4+ehyyz8fAnNGoBXMAIAAAAAA6Ilw42EvvfLJ3Eq8Afd+FjPoPcQutZO6ltmCLEr8kxQVsACAAAAAAbbZalyo07BbFjPFlYmbmv0z023eT9eLkHqeVUnfUAUAAAzQ2AH0AAAAFZAAgAAAAANBdV7M7kuYO3EMoQItAbXv4t2cIhfaT9V6+s4cg9djlBXMAIAAAAABvz4MIvZWxxrcJCL5qxLfFhXiUYB1OLHdKEjco94SgDgVsACAAAAAAK2GVGvyPIKolF/ECcmfmkVcf1/IZNcaTv96N92yGrkEAAzQ3AH0AAAAFZAAgAAAAAMoAoiAn1kc79j5oPZtlMWHMhhgwNhLUnvqkqIFvcH1NBXMAIAAAAADcJTW7WiCyW0Z9YDUYwppXhLj4Ac1povpJvcAq+i48MQVsACAAAAAAIGxGDzoeB3PTmudl4+j6piQB++e33EEzuzAiXcqGxvUAAzQ4AH0AAAAFZAAgAAAAACI3j5QP7dWHpcT6WO/OhsWwRJNASBYqIBDNzW8IorEyBXMAIAAAAABxUpBSjXwCKDdGP9hYU+RvyR+96kChfvyyRC4jZmztqAVsACAAAAAAvBCHguWswb4X0xdcAryCvZgQuthXzt7597bJ5VxAMdgAAzQ5AH0AAAAFZAAgAAAAAKsbycEuQSeNrF8Qnxqw3x3og8JmQabwGqnDbqzFRVrrBXMAIAAAAACno/3ef2JZJS93SVVzmOZSN+jjJHT8s0XYq2M46d2sLAVsACAAAAAAAt5zLJG+/j4K8rnkFtAn8IvdUVNefe6utJ3rdzgwudIAAzUwAH0AAAAFZAAgAAAAAPXIcoO8TiULqlxzb74NFg+I8kWX5uXIDUPnh2DobIoMBXMAIAAAAADR6/drkdTpnr9g1XNvKDwtBRBdKn7c2c4ZNUVK5CThdQVsACAAAAAAJqOA1c6KVog3F4Hb/GfDb3jCxXDRTqpXWSbMH4ePIJsAAzUxAH0AAAAFZAAgAAAAAEa03ZOJmfHT6/nVadvIw71jVxEuIloyvxXraYEW7u7pBXMAIAAAAADzRlBJK75FLiKjz3djqcgjCLo/e3yntI3MnPS48OORhgVsACAAAAAAnQhx4Rnyj081XrLRLD5NLpWmRWCsd0M9Hl7Jl19R0h8AAzUyAH0AAAAFZAAgAAAAAKx8NLSZUU04pSSGmHa5fh2oLHsEN5mmNMNHL95/tuC9BXMAIAAAAAA59hcXVaN3MNdHoo11OcH1aPRzHCwpVjO9mGfMz4xh3QVsACAAAAAAYIPdjV2XbPj7dBeHPwnwhVU7zMuJ+xtMUW5mIOYtmdAAAzUzAH0AAAAFZAAgAAAAAHNKAUxUqBFNS9Ea9NgCZoXMWgwhP4x0/OvoaPRWMquXBXMAIAAAAABUZ551mnP4ZjX+PXU9ttomzuOpo427MVynpkyq+nsYCQVsACAAAAAALnVK5p2tTTeZEh1zYt4iqKIQT9Z0si//Hy1L85oF+5IAAzU0AH0AAAAFZAAgAAAAALfGXDlyDVcGaqtyHkLT0qpuRhJQLgCxtznazhFtuyn/BXMAIAAAAABipxlXDq14C62pXhwAeen5+syA+/C6bN4rtZYcO4zKwAVsACAAAAAAXUf0pzUq0NhLYagWDap4uEiwq5rLpcx29rWbt1NYMsMAAzU1AH0AAAAFZAAgAAAAANoEr8sheJjg4UCfBkuUzarU9NFoy1xwbXjs5ifVDeA9BXMAIAAAAABPoyTf6M+xeZVGES4aNzVlq7LgjqZXJ/QunjYVusGUEAVsACAAAAAA1hA2gMeZZPUNytk9K+lB1RCqWRudRr7GtadJlExJf8oAAzU2AH0AAAAFZAAgAAAAAKvDiK+xjlBe1uQ3SZTNQl2lClIIvpP/5CHwY6Kb3WlgBXMAIAAAAAANnxImq5MFbWaRBHdJp+yD09bVlcFtiFDYsy1eDZj+iQVsACAAAAAAWtsyO+FxMPSIezwsV1TJD8ZrXAdRnQM6DJ+f+1V3qEkAAzU3AH0AAAAFZAAgAAAAAF49IlFH9RmSUSvUQpEPUedEksrQUcjsOv44nMkwXhjzBXMAIAAAAADJtWGbk0bZzmk20obz+mNsp86UCu/nLLlbg7ppxYn7PgVsACAAAAAA3k0Tj/XgPQtcYijH8cIlQoe/VXf15q1nrZNmg7yWYEgAAzU4AH0AAAAFZAAgAAAAAOuSJyuvz50lp3BzXlFKnq62QkN2quNU1Gq1IDsnFoJCBXMAIAAAAAAqavH1d93XV3IzshWlMnzznucadBF0ND092/2ApI1AcAVsACAAAAAAzUrK4kpoKCmcpdZlZNI13fddjdoAseVe67jaX1LobIIAAzU5AH0AAAAFZAAgAAAAALtgC4Whb4ZdkCiI30zY6fwlsxSa7lEaOAU3SfUXr02XBXMAIAAAAACgdZ6U1ZVgUaZZwbIaCdlANpCw6TZV0bwg3DS1NC/mnAVsACAAAAAAzI49hdpp0PbO7S2KexISxC16sE73EUAEyuqUFAC/J48AAzYwAH0AAAAFZAAgAAAAAF6PfplcGp6vek1ThwenMHVkbZgrc/dHgdsgx1VdPqZ5BXMAIAAAAACha3qhWkqmuwJSEXPozDO8y1ZdRLyzt9Crt2vjGnT7AAVsACAAAAAA7nvcU59+LwxGupSF21jAeAE0x7JE94tjRkJfgM1yKU8AAzYxAH0AAAAFZAAgAAAAAKoLEhLvLjKc7lhOJfx+VrGJCx9tXlOSa9bxQzGR6rfbBXMAIAAAAAAIDK5wNnjRMBzET7x/KAMExL/zi1IumJM92XTgXfoPoAVsACAAAAAAFkUYWFwNr815dEdFqp+TiIozDcq5IBNVkyMoDjharDQAAzYyAH0AAAAFZAAgAAAAADoQv6lutRmh5scQFvIW6K5JBquLxszuygM1tzBiGknIBXMAIAAAAADAD+JjW7FoBQ76/rsECmmcL76bmyfXpUU/awqIsZdO+wVsACAAAAAAPFHdLw3jssmEXsgtvl/RBNaUCRA1kgSwsofG364VOvQAAzYzAH0AAAAFZAAgAAAAAJNHUGAgn56KekghO19d11nai3lAh0JAlWfeP+6w4lJBBXMAIAAAAAD9XGJlvz59msJvA6St9fKW9CG4JoHV61rlWWnkdBRLzwVsACAAAAAAxwP/X/InJJHmrjznvahIMgj6pQR30B62UtHCthSjrP0AAzY0AH0AAAAFZAAgAAAAAHgYoMGjEE6fAlAhICv0+doHcVX8CmMVxyq7+jlyGrvmBXMAIAAAAAC/5MQZgTHuIr/O5Z3mXPvqrom5JTQ8IeSpQGhO9sB+8gVsACAAAAAAuPSXVmJUAUpTQg/A9Bu1hYczZF58KEhVofakygbsvJQAAzY1AH0AAAAFZAAgAAAAANpIljbxHOM7pydY877gpRQvYY2TGK7igqgGsavqGPBABXMAIAAAAAAqHyEu9gpurPOulApPnr0x9wrygY/7mXe9rAC+tPK80wVsACAAAAAA7gkPzNsS3gCxdFBWbSW9tkBjoR5ib+saDvpGSB3A3ogAAzY2AH0AAAAFZAAgAAAAAGR+gEaZTeGNgG9BuM1bX2R9ed4FCxBA9F9QvdQDAjZwBXMAIAAAAABSkrYFQ6pf8MZ1flgmeIRkxaSh/Eep4Btdx4QYnGGnwAVsACAAAAAApRovMiV00hm/pEcT4XBsyPNw0eo8RLAX/fuabjdU+uwAAzY3AH0AAAAFZAAgAAAAAFNprhQ3ZwIcYbuzLolAT5n/vc14P9kUUQComDu6eFyKBXMAIAAAAAAcx9z9pk32YbPV/sfPZl9ALIEVsqoLXgqWLVK/tP+heAVsACAAAAAA/qxvuvJbAHwwhfrPVpmCFzNvg2cU/NXaWgqgYUZpgXwAAzY4AH0AAAAFZAAgAAAAADgyPqQdqQrgfmJjRFAILTHzXbdw5kpKyfeoEcy6YYG/BXMAIAAAAAAE+3XsBQ8VAxAkN81au+f3FDeCD/s7KoZD+fnM1MJSSAVsACAAAAAAhRnjrXecwV0yeCWKJ5J/x12Xx4qVJahsCEVHB/1U2rcAAzY5AH0AAAAFZAAgAAAAAI0CT7JNngTCTUSei1Arw7eHWCD0jumv2rb7imjWIlWABXMAIAAAAABSP8t6ya0SyCphXMwnru6ZUDXWElN0NfBvEOhDvW9bJQVsACAAAAAAGWeGmBNDRaMtvm7Rv+8TJ2sJ4WNXKcp3tqpv5Se9Ut4AAzcwAH0AAAAFZAAgAAAAAD/FIrGYFDjyYmVb7oTMVwweWP7A6F9LnyIuNO4MjBnXBXMAIAAAAACIZgJCQRZu7NhuNMyOqCn1tf+DfU1qm10TPCfj5JYV3wVsACAAAAAA5hmY4ptuNxULGf87SUFXQWGAONsL9U29duh8xqsHtxoAAzcxAH0AAAAFZAAgAAAAAHIkVuNDkSS1cHIThKc/O0r2/ubaABTOi8Q1r/dvBAsEBXMAIAAAAADdHYqchEiJLM340c3Q4vJABmmth3+MKzwLYlsG6GS7sQVsACAAAAAADa+KP/pdTiG22l+ZWd30P1iHjnBF4zSNRdFm0oEK82kAAzcyAH0AAAAFZAAgAAAAAJmoDILNhC6kn3masElfnjIjP1VjsjRavGk1gSUIjh1NBXMAIAAAAAD97Ilvp3XF8T6MmVVcxMPcdL80RgQ09UoC6PnoOvZ1IQVsACAAAAAA2RK3Xng6v8kpvfVW9tkVXjpE+BSnx9/+Fw85Evs+kUEAAzczAH0AAAAFZAAgAAAAAI5bm3YO0Xgf0VT+qjVTTfvckecM3Cwqj7DTKZXf8/NXBXMAIAAAAAD/m+h8fBhWaHm6Ykuz0WX1xL4Eme3ErLObyEVJf8NCywVsACAAAAAAfb1VZZCqs2ivYbRzX4p5CtaCkKW+g20Pr57FWXzEZi8AAzc0AH0AAAAFZAAgAAAAANqo4+p6qdtCzcB4BX1wQ6llU7eFBnuu4MtZwp4B6mDlBXMAIAAAAAAGiz+VaukMZ+6IH4jtn4KWWdKK4/W+O+gRioQDrfzpMgVsACAAAAAAG4YYkTp80EKo59mlHExDodRQFR7njhR5dmISwUJ6ukAAAzc1AH0AAAAFZAAgAAAAAPrFXmHP2Y4YAm7b/aqsdn/DPoDkv7B8egWkfe23XsM1BXMAIAAAAAAGhwpKAr7skeqHm3oseSbO7qKNhmYsuUrECBxJ5k+D2AVsACAAAAAAAqPQi9luYAu3GrFCEsVjd9z2zIDcp6SPTR2w6KQEr+IAAzc2AH0AAAAFZAAgAAAAABzjYxwAjXxXc0Uxv18rH8I3my0Aguow0kTwKyxbrm+cBXMAIAAAAADVbqJVr6IdokuhXkEtXF0C2gINLiAjMVN20lE20Vmp2QVsACAAAAAAD7K1Fx4gFaaizkIUrf+EGXQeG7QX1jadhGc6Ji471H8AAzc3AH0AAAAFZAAgAAAAAFMm2feF2fFCm/UC6AfIyepX/xJDSmnnolQIBnHcPmb5BXMAIAAAAABLI11kFrQoaNVZFmq/38aRNImPOjdJh0Lo6irI8M/AaAVsACAAAAAAOWul0oVqJ9CejD2RqphhTC98DJeRQy5EwbNerU2+4l8AAzc4AH0AAAAFZAAgAAAAAJvXB3KyNiNtQko4SSzo/9b2qmM2zU9CQTTDfLSBWMgRBXMAIAAAAAAvjuVP7KsLRDeqVqRziTKpBrjVyqKiIbO9Gw8Wl2wFTAVsACAAAAAADlE+oc1ins+paNcaOZJhBlKlObDJ4VQORWjFYocM4LgAAzc5AH0AAAAFZAAgAAAAAPGdcxDiid8z8XYnfdDivNMYVPgBKdGOUw6UStU+48CdBXMAIAAAAAARj6g1Ap0eEfuCZ4X2TsEw+Djrhto3fA5nLwPaY0vCTgVsACAAAAAAoHqiwGOUkBu8SX5U1yHho+UIFdSN2MdQN5s6bQ0EsJYAAzgwAH0AAAAFZAAgAAAAAP5rGPrYGt3aKob5f/ldP0qrW7bmWvqnKY4QwdDWz400BXMAIAAAAADTQkW2ymaaf/bhteOOGmSrIR97bAnJx+yN3yMj1bTeewVsACAAAAAADyQnHGH2gF4w4L8axUsSTf6Ubk7L5/eoFOJk12MtZAoAAzgxAH0AAAAFZAAgAAAAAAlz6wJze5UkIxKpJOZFGCOf3v2KByWyI6NB6JM9wNcBBXMAIAAAAABUC7P/neUIHHoZtq0jFVBHY75tSFYr1Y5S16YN5XxC1QVsACAAAAAAgvxRbXDisNnLY3pfsjDdnFLtkvYUC4lhA68eBXc7KAwAAzgyAH0AAAAFZAAgAAAAAFJ8AtHcjia/9Y5pLEc3qVgH5xKiXw12G9Kn2A1EY8McBXMAIAAAAAAxe7Bdw7eUSBk/oAawa7uicTEDgXLymRNhBy1LAxhDvwVsACAAAAAAxKPaIBKVx3jTA+R/el7P7AZ7efrmTGjJs3Hj/YdMddwAAzgzAH0AAAAFZAAgAAAAAO8uwQUaKFb6vqR3Sv3Wn4QAonC2exOC9lGG1juqP5DtBXMAIAAAAABZf1KyJgQg8/Rf5c02DgDK2aQu0rNCOvaL60ohDHyY+gVsACAAAAAAqyEjfKC8lYoIfoXYHUqHZPoaA6EK5BAZy5dxXZmay4kAAzg0AH0AAAAFZAAgAAAAAE8YtqyRsGCeiR6hhiyisR/hccmK4nZqIMzO4lUBmEFzBXMAIAAAAAC1UYOSKqAeG1UJiKjWFVskRhuFKpj9Ezy+lICZvFlN5AVsACAAAAAA6Ct9nNMKyRazn1OKnRKagm746CGu+jyhbL1qJnZxGi0AAzg1AH0AAAAFZAAgAAAAAPhCrMausDx1QUIEqp9rUdRKyM6a9AAx7jQ3ILIu8wNIBXMAIAAAAACmH8lotGCiF2q9VQxhsS+7LAZv79VUAsOUALaGxE/EpAVsACAAAAAAnc1xCKfdvbUEc8F7XZqlNn1C+hZTtC0I9I3LL06iaNkAAzg2AH0AAAAFZAAgAAAAAOBi/GAYFcstMSJPgp3VkMiuuUUCrZytvqYaU8dwm8v2BXMAIAAAAACEZSZVyD3pKzGlbdwlYmWQhHHTV5SnNLknl2Gw8IaUTQVsACAAAAAAfsLZsEDcWSuNsIo/TD1ReyQW75HPMgmuKZuWFOLKRLoAAzg3AH0AAAAFZAAgAAAAAIQuup+YGfH3mflzWopN8J1X8o8a0d9CSGIvrA5HOzraBXMAIAAAAADYvNLURXsC2ITMqK14LABQBI+hZZ5wNf24JMcKLW+84AVsACAAAAAACzfjbTBH7IwDU91OqLAz94RFkoqBOkzKAqQb55gT4/MAAzg4AH0AAAAFZAAgAAAAAKsh0ADyOnVocFrOrf6MpTrNvAj8iaiE923DPryu124gBXMAIAAAAADg24a8NVE1GyScc6tmnTbmu5ulzO+896fE92lN08MeswVsACAAAAAAaPxcOIxnU7But88/yadOuDJDMcCywwrRitaxMODT4msAAzg5AH0AAAAFZAAgAAAAAKkVC2Y6HtRmv72tDnPUSjJBvse7SxLqnr09/Uuj9sVVBXMAIAAAAABYNFUkH7ylPMN+Bc3HWX1e0flGYNbtJNCY9SltJCW/UAVsACAAAAAAZYK/f9H4OeihmpiFMH7Wm7uLvs2s92zNA8wyrNZTsuMAAzkwAH0AAAAFZAAgAAAAADDggcwcb/Yn1Kk39sOHsv7BO/MfP3m/AJzjGH506Wf9BXMAIAAAAAAYZIsdjICS0+BDyRUPnrSAZfPrwtuMaEDEn0/ijLNQmAVsACAAAAAAGPnYVvo2ulO9z4LGd/69NAklfIcZqZvFX2KK0s+FcTUAAzkxAH0AAAAFZAAgAAAAAEWY7dEUOJBgjOoWVht1wLehsWAzB3rSOBtLgTuM2HC8BXMAIAAAAAAAoswiHRROurjwUW8u8D5EUT+67yvrgpB/j6PzBDAfVwVsACAAAAAA6NhRTYFL/Sz4tao7vpPjLNgAJ0FX6P/IyMW65qT6YsMAAzkyAH0AAAAFZAAgAAAAAPZaapeAUUFPA7JTCMOWHJa9lnPFh0/gXfAPjA1ezm4ZBXMAIAAAAACmJvLY2nivw7/b3DOKH/X7bBXjJwoowqb1GtEFO3OYgAVsACAAAAAA/JcUoyKacCB1NfmH8vYqC1f7rd13KShrQqV2r9QBP44AAzkzAH0AAAAFZAAgAAAAAK00u6jadxCZAiA+fTsPVDsnW5p5LCr4+kZZZOTDuZlfBXMAIAAAAAAote4zTEYMDgaaQbAdN8Dzv93ljPLdGjJzvnRn3KXgtQVsACAAAAAAxXd9Mh6R3mnJy8m7UfqMKi6oD5DlZpkaOz6bEjMOdiwAAzk0AH0AAAAFZAAgAAAAAFbgabdyymiEVYYwtJSWa7lfl/oYuj/SukzJeDOR6wPVBXMAIAAAAADAFGFjS1vPbN6mQEhkDYTD6V2V23Ys9gUEUMGNvMPkaAVsACAAAAAAL/D5Sze/ZoEanZLK0IeEkhgVkxEjMWVCfmJaD3a8uNIAAzk1AH0AAAAFZAAgAAAAABNMR6UBv2E627CqLtQ/eDYx7OEwQ7JrR4mSHFa1N8tLBXMAIAAAAAAxH4gucI4UmNVB7625C6hFSVCuIpJO3lusJlPuL8H5EQVsACAAAAAAVLHNg0OUVqZ7WGOP53BkTap9FOw9dr1P4J8HxqFqU04AAzk2AH0AAAAFZAAgAAAAAG8cd6WBneNunlqrQ2EmNf35W7OGObGq9WL4ePX+LUDmBXMAIAAAAAAjJ2+sX87NSis9hBsgb1QprVRnO7Bf+GObCGoUqyPE4wVsACAAAAAAs9c9SM49/pWmyUQKslpt3RTMBNSRppfNO0JBvUqHPg0AAzk3AH0AAAAFZAAgAAAAAFWOUGkUpy8yf6gB3dio/aOfRKh7XuhvsUj48iESFJrGBXMAIAAAAAAY7sCDMcrUXvNuL6dO0m11WyijzXZvPIcOKob6IpC4PQVsACAAAAAAJOP+EHz6awDb1qK2bZQ3kTV7wsj5Daj/IGAWh4g7omAAAzk4AH0AAAAFZAAgAAAAAGUrIdKxOihwNmo6B+aG+Ag1qa0+iqdksHOjQj+Oy9bZBXMAIAAAAABwa5dbI2KmzBDNBTQBEkjZv4sPaeRkRNejcjdVymRFKQVsACAAAAAA4ml/nm0gJNTcJ4vuD+T2Qfq2fQZlibJp/j6MOGDrbHMAAzk5AH0AAAAFZAAgAAAAAOx89xV/hRk64/CkM9N2EMK6aldII0c8smdcsZ46NbP8BXMAIAAAAADBF6tfQ+7q9kTuLyuyrSnDgmrdmrXkdhl980i1KHuGHgVsACAAAAAACUqiFqHZdGbwAA+hN0YUE5zFg+H+dabIB4dj5/75W/YAAzEwMAB9AAAABWQAIAAAAADJDdC9aEFl4Y8J/awHbnXGHjfP+VXQilPHJg7ewaJI7AVzACAAAAAAE+tqRl6EcBMXvbr4GDiNIYObTsYpa1n6BJk9EjIJVicFbAAgAAAAAJVc+HYYqa0m1Hq6OiRX8c0iRnJYOt6AJAJoG0sG3GMSAAMxMDEAfQAAAAVkACAAAAAA3F9rjEKhpoHuTULVGgfUsGGwJs3bISrXkFP1v6KoQLgFcwAgAAAAAIBf0tXw96Z/Ds0XSIHX/zk3MzUR/7WZR/J6FpxRWChtBWwAIAAAAABWrjGlvKYuTS2s8L9rYy8Hf0juFGJfwQmxVIjkTmFIGQADMTAyAH0AAAAFZAAgAAAAAOYIYoWkX7dGuyKfi3XssUlc7u/gWzqrR9KMkikKVdmSBXMAIAAAAABVF2OYjRTGi9Tw8XCAwZWLpX35Yl271TlNWp6N/nROhAVsACAAAAAA0nWwYzXQ1+EkDvnGq+SMlq20z+j32Su+i/A95SggPb4AAzEwMwB9AAAABWQAIAAAAACMtPm12YtdEAvqu6Eji1yuRXnu1RJP6h0l7pH3lSH4MwVzACAAAAAAENyCFfyUAh1veQBGx+cxiB7Sasrj41jzCGflZkB5cRMFbAAgAAAAAKdI2LMqISr/T5vuJPg6ZRBm5fVi2aQCc4ra3A4+AjbDAAMxMDQAfQAAAAVkACAAAAAAvlI4lDcs6GB1cnm/Tzo014CXWqidCdyE5t2lknWQd4QFcwAgAAAAAD60SpNc4O2KT7J0llKdSpcX1/Xxs97N715a1HsTFkmBBWwAIAAAAABuuRkJWAH1CynggBt1/5sPh9PoGiqTlS24D/OE2uHXLQADMTA1AH0AAAAFZAAgAAAAAKl8zcHJRDjSjJeV/WvMxulW1zrTFtaeBy/aKKhadc6UBXMAIAAAAADBdWQl5SBIvtZZLIHszePwkO14W1mQ0izUk2Ov21cPNAVsACAAAAAAHErCYycpqiIcCZHdmPL1hi+ovLQk4TAvENpfLdTRamQAAzEwNgB9AAAABWQAIAAAAABb6LXDWqCp1beQgQjj8I3sRTtFhlrmiBi+h/+ikmrvugVzACAAAAAA9stpgTecT7uTyaGNs3K9Bp0A7R0QaIAOfscyMXHBPX8FbAAgAAAAAHUt+McyXrJ1H8SwnHNVO181Ki8vDAM1f7XI26mg95ZDAAMxMDcAfQAAAAVkACAAAAAA97NTT+81PhDhgptNtp4epzA0tP4iNb9j1AWkiiiKGM8FcwAgAAAAAKPbHg7ise16vxmdPCzksA/2Mn/qST0L9Xe8vnQugVkcBWwAIAAAAABB0EMXfvju4JU/mUH/OvxWbPEl9NJkcEp4iCbkXI41fAADMTA4AH0AAAAFZAAgAAAAAMqpayM2XotEFmm0gwQd9rIzApy0X+7HfOhNk6VU7F5lBXMAIAAAAACJR9+q5T9qFHXFNgGbZnPubG8rkO6cwWhzITQTmd6VgwVsACAAAAAAOZLQ6o7e4mVfDzbpQioa4d3RoTvqwgnbmc5Qh2wsZuoAAzEwOQB9AAAABWQAIAAAAADQnslvt6Hm2kJPmqsTVYQHE/wWeZ4bE1XSkt7TKy0r1gVzACAAAAAA8URTA4ZMrhHPvlp53TH6FDCzS+0+61qHm5XK6UiOrKEFbAAgAAAAAHQbgTCdZcbdA0avaTmZXUKnIS7Nwf1tNrcXDCw+PdBRAAMxMTAAfQAAAAVkACAAAAAAhujlgFPFczsdCGXtQ/002Ck8YWQHHzvWvUHrkbjv4rwFcwAgAAAAALbV0lLGcSGfE7mDM3n/fgEvi+ifjl7WZ5b3aqjDNvx9BWwAIAAAAACbceTZy8E3QA1pHmPN5kTlOx3EO8kJM5PUjTVftw1VpgADMTExAH0AAAAFZAAgAAAAABm/6pF96j26Jm7z5KkY1y33zcAEXLx2n0DwC03bs/ixBXMAIAAAAAD01OMvTZI/mqMgxIhA5nLs068mW+GKl3OW3ilf2D8+LgVsACAAAAAAaLvJDrqBESTNZSdcXsd+8GXPl8ZkUsGpeYuyYVv/kygAAzExMgB9AAAABWQAIAAAAACfw9/te4GkHZAapC9sDMHHHZgmlTrccyJDPFciOMSOcwVzACAAAAAAIIC1ZpHObvmMwUfqDRPl4C1aeuHwujM1G/yJbvybMNAFbAAgAAAAAAs9x1SnVpMfNv5Bm1aXGwHmbbI9keWa9HRD35XuCBK5AAMxMTMAfQAAAAVkACAAAAAAkxHJRbnShpPOylLoDdNShfILeA1hChKFQY9qQyZ5VmsFcwAgAAAAAKidrY+rC3hTY+YWu2a7fuMH2RD/XaiTIBW1hrxNCQOJBWwAIAAAAACW0kkqMIzIFMn7g+R0MI8l15fr3k/w/mHtY5n6SYTEwAADMTE0AH0AAAAFZAAgAAAAAByuYl8dBvfaZ0LO/81JW4hYypeNmvLMaxsIdvqMPrWoBXMAIAAAAABNddwobOUJzm9HOUD8BMZJqkNCUCqstHZkC76FIdNg9AVsACAAAAAAQQOkIQtkyNavqCnhQbNg3HfqrJdsAGaoxSJePJl1qXsAAzExNQB9AAAABWQAIAAAAABxMy7X5hf7AXGDz3Y/POu1ZpkMlNcSvSP92NOO/Gs7wAVzACAAAAAAHJshWo2T5wU2zvqCyJzcJQKQaHFHpCpMc9oWBXkpUPoFbAAgAAAAAGeiJKzlUXAvL0gOlW+Hz1mSa2HsV4RGmyLmCHlzbAkoAAMxMTYAfQAAAAVkACAAAAAAlqbslixl7Zw3bRlibZbe/WmKw23k8uKeIzPKYEtbIy0FcwAgAAAAAHEKwpUxkxOfef5HYvulXPmdbzTivwdwrSYIHDeNRcpcBWwAIAAAAADuPckac21Hrg/h0kt5ShJwVEZ9rx6SOHd2+HDjqxEWTQADMTE3AH0AAAAFZAAgAAAAAMXrXx0saZ+5gORmwM2FLuZG6iuO2YS+1IGPoAtDKoKBBXMAIAAAAADIQsxCr8CfFKaBcx8kIeSywnGh7JHjKRJ9vJd9x79y7wVsACAAAAAAcvBV+SykDYhmRFyVYwFYB9oBKBSHr55Jdz2cXeowsUQAAzExOAB9AAAABWQAIAAAAAAm83FA9yDUpwkbKTihe7m53u+DivS9BU2b4vQMtCVQ2AVzACAAAAAAz3m1UB/AbZPa4QSKFDnUgHaT78+6iGOFAtouiBorEgEFbAAgAAAAAIgbpyYtJj5513Z5XYqviH/HXG/5+mqR52iBbfqMmDtZAAMxMTkAfQAAAAVkACAAAAAAJRzYK0PUwr9RPG2/7yID0WgcTJPB2Xjccp5LAPDYunkFcwAgAAAAAIIh24h3DrltAzNFhF+MEmPrZtzr1PhCofhChZqfCW+jBWwAIAAAAAAzRNXtL5o9VXMk5D5ylI0odPDJDSZZry1wfN+TedH70gADMTIwAH0AAAAFZAAgAAAAAHSaHWs/dnmI9sc7nB50VB2Bzs0kHapMHCQdyVEYY30TBXMAIAAAAACkV22lhEjWv/9/DubfHBAcwJggKI5mIbSK5L2nyqloqQVsACAAAAAAS19m7DccQxgryOsBJ3GsCs37yfQqNi1G+S6fCXpEhn4AAzEyMQB9AAAABWQAIAAAAAAC/I4TQRtCl12YZmdGz17X4GqSQgfwCPgRBwdHmdwu+QVzACAAAAAAx8f3z2ut/RAZhleari4vCEE+tNIn4ikjoUwzitfQ588FbAAgAAAAAJci0w1ZB8W2spJQ+kMpod6HSCtSR2jrabOH+B0fj3A4AAMxMjIAfQAAAAVkACAAAAAADGB5yU2XT0fse/MPWgvBvZikVxrl5pf3S5K1hceKWooFcwAgAAAAAIxTmlLHMjNaVDEfJbXvRez0SEPWFREBJCT6qTHsrljoBWwAIAAAAAAlswzAl81+0DteibwHD+CG5mZJrfHXa9NnEFRtXybzzwADMTIzAH0AAAAFZAAgAAAAABmO7QD9vxWMmFjIHz13lyOeV6vHT6mYCsWxF7hb/yOjBXMAIAAAAACT9lmgkiqzuWG24afuzYiCeK9gmJqacmxAruIukd0xEAVsACAAAAAAZa0/FI/GkZR7CtX18Xg9Tn9zfxkD0UoaSt+pIO5t1t4AAzEyNAB9AAAABWQAIAAAAAAfPUoy7QyZKhIIURso+mkP9qr1izbjETqF5s22GwjCjAVzACAAAAAAvLMsIDQ/go4VUxeh50UHmsvMvfx51cwyONnRD2odvC0FbAAgAAAAAKMb+1CodEalAFnDrEL1Ndt8ztamZ+9134m9Kp3GQgd+AAMxMjUAfQAAAAVkACAAAAAAE3ZqUar0Bq2zWbARE0bAv98jBlK9UJ73/xcwdMWWlSkFcwAgAAAAAK4M+MmC+9sFiFsumMyJZQKxWmmJiuG9H7IzKw083xxkBWwAIAAAAAAqkAONzhvMhkyL1D/6h7QQxEkdhC3p2WjXH+VGq5qCqQADMTI2AH0AAAAFZAAgAAAAAMo8FJiOq63cAmyk2O7eI7GcbQh/1j4RrMTqly3rexftBXMAIAAAAADjVmpd0WiRGTw/gAqEgGolt2EI7Csv14vKdmYoMD0aAgVsACAAAAAA07XQBzBUQMNw7F2/YxJjZNuPVpHTTgbLd1oGk77+bygAAzEyNwB9AAAABWQAIAAAAACu5IGaIx7A3Jvly/kzlCsSA4s3iJwuIl8jEdRH0k93NwVzACAAAAAA9NRUyxYE+t0Xyosyt6vIfMFW/vBoYg6sR+jBNs4JAxIFbAAgAAAAAAzyZ91dx+0oMlOVAjRGiMrPySikY/U9eMEB4WJb3uWtAAMxMjgAfQAAAAVkACAAAAAALkRy0GJInXYLA+cgjs6Myb0a+Gu9hgXhHvhLNoGWfckFcwAgAAAAANbALyt9zCSvwnLaWCd2/y2eoB7qkWTvv1Ldu8r40JPuBWwAIAAAAAD4Fl5bV5sz4isIE9bX+lmAp+aAKaZgVYVZeVfrItkCZAADMTI5AH0AAAAFZAAgAAAAAGoUK/DSWhT8LZhszSUqDbTrp8cSA7rdqmADKL+MILtTBXMAIAAAAABHnEE9bVa6lvhfhEMkkV2kzSSxH/sMW/FIJuw3CzWs6wVsACAAAAAAanavcBdqZxgRGKvEK95wTmeL1K1CeDSXZsXUAs81uOgAAzEzMAB9AAAABWQAIAAAAAC922ZDQE3h2fQKibGMZ9hV0WNlmrPYYSdtaSyYxsWYqgVzACAAAAAAagMovciKK6WVjIc2cCj8nK5O/gVOFFVeVAJpRp89tmQFbAAgAAAAAKcTFfPQzaFiAtSFhqbN02sCE1BKWJSrRfGN5L6oZwzkAAMxMzEAfQAAAAVkACAAAAAAtK+JqX3K/z2txjAU15DgX4y90DS2YLfIJFolCOkJJJwFcwAgAAAAAMnR5V7gfX7MNqqUdL5AkWlkhyFXaBRVNej+Rcn8lrQkBWwAIAAAAAA2cDNRXZuiC241TGRvdFyctJnrNcdbZOP9zHio81tkngADMTMyAH0AAAAFZAAgAAAAAAeGrIMK/bac6kPczxbvRYqKMkcpeI2FjdMpD91FDWIvBXMAIAAAAAAix62z1LeS8yvSXCl5gHSIomjyx76fF3S1lp9k900hygVsACAAAAAAiYwzf2m71aWFD5ajcXyW2JX2EzQOkBroTGMg29nLPYIAAzEzMwB9AAAABWQAIAAAAACphf298InM0Us4HT8o1W1MGw0D/02vd7Jh+U0h7qaFaQVzACAAAAAAFXtk7YpqsOJxsqGWSIL+YcBE96G3Zz9D31gPqDW94y8FbAAgAAAAAAOrS1KVA94rjB1jZ1pPocpCeBG+B14RzWoHqVDpp7JbAAMxMzQAfQAAAAVkACAAAAAATLDS2cuDVM3yDMuWNgk2iGKBTzPpfJMbvxVOSY39ZfcFcwAgAAAAAPT5wRi2cLHIUflXzm6EQB/m7xdThP80ir1VV/JBBqvxBWwAIAAAAAB9lEtZS0aXCFbCtSbhnis27S5IPcfWGygHW8AHn3QqzwADMTM1AH0AAAAFZAAgAAAAAJNjExiZVX7jfFGfYpQu16qxLN0YPqVU/5CQ/Y67YSinBXMAIAAAAABMpm2+6KrkRUlXzQoMPHrQmIO6dkQz66tYdfTeA3dKqQVsACAAAAAAFXobHiMLvNZuEPr8jtewCX2J93EZG3JNeyVg92fue6YAAzEzNgB9AAAABWQAIAAAAABlFkYtLCx901X6QVVMkSn6Z7k30UF4xHaA0OZJJ9bdyQVzACAAAAAATez+F9GHcGzTp7jjv4feboUNb8JCkIp4EqcPFisnq7MFbAAgAAAAACE7JvOpBgMoZ7kRd4QbxIhxukPTUxXpzhjnBHiR7XoRAAMxMzcAfQAAAAVkACAAAAAA8NJKN0IxZnruhswGQkiruv8Ih0EMwDcSZx/Xasup9dkFcwAgAAAAAKaJZRxzA+Igeydvuk6cSwUHXcrmT4PjhuPu//FslpdnBWwAIAAAAAD53Rok1Vq/PMAnXmarqoHJ0PEyYUBmVESa9hIpCv/G9QADMTM4AH0AAAAFZAAgAAAAABHxHdEClz7hbSSgE58+dWLlSMJnoPz+jFxp4bB1GmLQBXMAIAAAAAD3nSvT6aGD+A110J/NwEfp0nPutlmuB5B+wA3CC3noGAVsACAAAAAA3Apjd+TapONB7k5wBVwTWgn8t+Sq2oyyU5/+as109RcAAzEzOQB9AAAABWQAIAAAAAC/o8qW/ifk3KuJ01VFkyNLgQafxB5/bGs2G5VyyVafOwVzACAAAAAA1bMqAFGDHSl6BYNLbxApvkAv2K1/oafywiX0MDz1dGUFbAAgAAAAAHJXLlId3edFoniLD/9K2A5973MeP2Ro31flDyqm3l5QAAMxNDAAfQAAAAVkACAAAAAAY2V8I1bz3a1AxTtmED6UhdhA09huFkuuEX8R+d/WDPUFcwAgAAAAAPTVoNRiI76tcRKqd+JBBVyy4+YcKST42p0QX2BtmQ2VBWwAIAAAAACcxt9hg14WqPNiDv1MkqVljM2e2KJEv53lA17LhV6ZigADMTQxAH0AAAAFZAAgAAAAAO2kSsW0WGN9AOtK4xK2SHrGhWiaAbMEKT4iZkRpaDN/BXMAIAAAAABKGzQcPM8LT2dwOggxoWjv/1imYWabbG/G4kBw8OWaxAVsACAAAAAAC9hLK1dScQTAqg+YAG3ObdPzg2Xet57HmOFpGmyUR9UAAzE0MgB9AAAABWQAIAAAAAAiCwzNEEaH/mDam68IdDftnhthyUFdb+ZCNSBQ91WlHQVzACAAAAAA7tHyHcxCzmbJeFYZyPm4mEgkTGKOvwY4MX82OvH0Jn8FbAAgAAAAAAb5IAbZ1hXCNegQ+S+C9i/Z8y6sS8KeU04V6hXa2ml6AAMxNDMAfQAAAAVkACAAAAAAGuCHVNJSuoVkpPOnS5s89GuA+BLi2IPBUr2Bg1sWEPIFcwAgAAAAAEl1gncS5/xO7bQ/KQSstRV3rOT2SW6nV92ZANeG2SR6BWwAIAAAAAA9LOcKmhek8F2wAh8yvT/vjp2gaouuO+Hmv10lwAeWPAADMTQ0AH0AAAAFZAAgAAAAAMfxz7gEaoCdPvXrubDhCZUS0ARLZc1svgbXgMDlVBPgBXMAIAAAAAB6a5dDA3fuT5Vz2KvAcbUEFX/+B7Nw2p1QqbPoQ5TTuAVsACAAAAAAcf/y75UOuI62A6vWH7bYr/5Jz+nirZVYK/81trN6XOQAAzE0NQB9AAAABWQAIAAAAACnYsqF/VzmjIImC9+dqrHO1TM6lJ6fRwM0mM6Wf6paOwVzACAAAAAA5tgZzch8uDCR1ky3SllVaKVpxAlbrhvlNDTazZZRZOAFbAAgAAAAALeGiLJS4z2zhgVpxzyPdRYyACP9QzQBOob34YrIZumCAAMxNDYAfQAAAAVkACAAAAAAEC0sIVmadtW4YMuRXH7RpAhXclsd+3bmqGXCMeaT014FcwAgAAAAABPpXh0uzpsJJB+IRUNajmMB9WGwswfpw5T9xk3Xj6ANBWwAIAAAAAAmf+NYh9TZ/QRu3w/GQz66n7DtfbJijN3G7KzeL8lstAADMTQ3AH0AAAAFZAAgAAAAABaIB3n49Xm9cOafSrQsE0WCcYp8rMIO/qVwIlMF5YLRBXMAIAAAAAC9EyWJV3xOu9bzgdJ/yX+ko7qLf1u3AxNMataW2C9EzQVsACAAAAAAvVbDkLxXx2DcMLifIQ3K0IIJcLcAG9DUrNfI6aoUjNcAAzE0OAB9AAAABWQAIAAAAAA5rZItA/cocRnngYqcJ3nBXQ+l688aKz3EQyLbYYunPAVzACAAAAAAwKyA+L7TgxztPClLrIMk2JXR+w7c04N3ZOqPgjvrIvsFbAAgAAAAACzvZ33h6aWEe8hmo+1f6OXJ72FY5hvWaUuha64ZV3KFAAMxNDkAfQAAAAVkACAAAAAA3htn7oHJ0YYpIrs+Mzyh85Ys67HwAdv5LQl1mCdoMWkFcwAgAAAAAEHjCtNNLenHuSIYux6ezAHsXDaj2DlTF67ToDhDDe6HBWwAIAAAAAD+P4H0sk9jOd+7vOANt2/1Ectb+4ZRGPE8GkHWNXW3MgADMTUwAH0AAAAFZAAgAAAAAEnt18Km/nqggfIJWxzTr9r3hnXNaueG6XO9A5G11LnGBXMAIAAAAAD7QxzGMN/ard5TfFLecE6uusMmXG2+RBsBR+/NCQHUwAVsACAAAAAAQEZ1ZZ8GC8rdbg7s87OM5Gr9qkTXS9+P5DuAZxj5Gl4AAzE1MQB9AAAABWQAIAAAAAAVAKK/GoY8AACu/hyMpO4hdLq6JnEyWNzkyci9sbaD/wVzACAAAAAA2HmeqpMlvvBpV2zQTYIRmsc4MFlfHRwLof0ycJgMg/MFbAAgAAAAACdltCeWi5E/q1Li1eXLChpM2D9QQSGLBZ82NklQSc0oAAMxNTIAfQAAAAVkACAAAAAAhHyq1GQC/GiMwpYjcsfkNxolJ10ARKjIjfkW1Wipzi0FcwAgAAAAAD/uaGWxTDq87F8XZ6CrFI+RNa8yMqfSZdqK00Kj833BBWwAIAAAAAD6aEdOO0CsQGagioOCvANPCEHSpJ8BSixlPBq5ERhB7AADMTUzAH0AAAAFZAAgAAAAABAJJxHoZD+MQBWqm9UM9Dd3z5ZohIZGWRaRVRsMptKQBXMAIAAAAADrE/ca+gqj/SH4oao4wE4qn2ovoTydzcMbDbrfnUs3zAVsACAAAAAAeNCIQN6hVnGJinytQRFGlQ2ocoprXNqpia+BSxzl+uwAAzE1NAB9AAAABWQAIAAAAAAv01wz7VG9mTepjXQi6Zma+7b/OVBaKVkWNbgDLr1mFgVzACAAAAAA0I5sxz8r6wkCp5Tgvr+iL4p6MxSOq5d3e1kZG+0b7NkFbAAgAAAAAIA32v6oGkAOS96HexGouNTex+tLahtx9QF2dgGClk6WAAMxNTUAfQAAAAVkACAAAAAAWXecRwxSon68xaa9THXnRDw5ZfzARKnvvjTjtbae6T0FcwAgAAAAAPh0UfUMEo7eILCMv2tiJQe1bF9qtXq7GJtC6H5Va4fIBWwAIAAAAADqFr1ThRrTXNgIOrJWScO9mk86Ufi95IDu5gi4vP+HWQADMTU2AH0AAAAFZAAgAAAAAEY5WL8/LpX36iAB1wlQrMO/xHVjoO9BePVzbUlBYo+bBXMAIAAAAABoKcpadDXUARedDvTmzUzWPe1jTuvD0z9oIcZmKuiSXwVsACAAAAAAJuJbwuaMrAFoI+jU/IYr+k4RzAqITrOjAd3HWCpJHqEAAzE1NwB9AAAABWQAIAAAAADnJnWqsfx0xqNnqfFGCxIplVu8mXjaHTViJT9+y2RuTgVzACAAAAAAWAaSCwIXDwdYxWf2NZTly/iKVfG/KDjHUcA1BokN5sMFbAAgAAAAAJVxavipE0H4/JQvhagdytXBZ8qGooeXpkbPQ1RfYMVHAAMxNTgAfQAAAAVkACAAAAAAsPG7LaIpJvcwqcbtfFUpIjj+vpNj70Zjaw3eV9T+QYsFcwAgAAAAAJQ71zi0NlCyY8ZQs3IasJ4gB1PmWx57HpnlCf3+hmhqBWwAIAAAAACD58TO6d+71GaOoS+r73rAxliAO9GMs4Uc8JbOTmC0OwADMTU5AH0AAAAFZAAgAAAAAAGiSqKaQDakMi1W87rFAhkogfRAevnwQ41onWNUJKtuBXMAIAAAAAASgiDpXfGh7E47KkOD8MAcX8+BnDShlnU5JAGdnPdqOAVsACAAAAAAI+2TTQIgbFq4Yr3lkzGwhG/tqChP7hRAx2W0fNaH6jcAAzE2MAB9AAAABWQAIAAAAAB7L4EnhjKA5xJD3ORhH2wOA1BvpnQ+7IjRYi+jjVEaJAVzACAAAAAAuhBIm0nL3FJnVJId+7CKDASEo+l2E89Z9/5aWSITK4AFbAAgAAAAALtSICOzQDfV9d+gZuYxpEj6cCeHnKTT+2G3ceP2H65kAAMxNjEAfQAAAAVkACAAAAAAaROn1NaDZFOGEWw724dsXBAm6bgmL5i0cki6QZQNrOoFcwAgAAAAANVT8R6UvhrAlyqYlxtmnvkR4uYK/hlvyQmBu/LP6/3ZBWwAIAAAAAD+aHNMP/X+jcRHyUtrCNkk1KfMtoD3GTmShS8pWGLt+AADMTYyAH0AAAAFZAAgAAAAADqSR5e0/Th59LrauDA7OnGD1Xr3H3NokfVxzDWOFaN7BXMAIAAAAACt30faNwTWRbvmykDpiDYUOCwA6QDbBBYBFWS7rdOB4AVsACAAAAAAF7SvnjjRk5v2flFOKaBAEDvjXaL1cpjsQLtK2fv9zdQAAzE2MwB9AAAABWQAIAAAAADmtb1ZgpZjSeodPG/hIVlsnS8hoRRwRbrTVx89VwL62AVzACAAAAAAi38e1g6sEyVfSDkzZbaZXGxKI/zKNbMasOl2LYoWrq8FbAAgAAAAAALACk0KcCDN/Kv8WuazY8ORtUGkOZ5Dsm0ys1oOppp/AAMxNjQAfQAAAAVkACAAAAAAf/f7AWVgBxoKjr7YsEQ4w/fqSvuQWV2HMiA3rQ7ur0sFcwAgAAAAADkkeJozP6FFhUdRIN74H4UhIHue+eVbOs1NvbdWYFQrBWwAIAAAAAB55FlHAkmTzAYj/TWrGkRJw2EhrVWUnZXDoMYjyfB/ZwADMTY1AH0AAAAFZAAgAAAAAI2WEOymtuFpdKi4ctanPLnlQud+yMKKb8p/nfKmIy56BXMAIAAAAADVKrJmhjr1rfF3p+T+tl7UFd1B7+BfJRk0e7a4im7ozgVsACAAAAAA5E7Ti3PnFiBQoCcb/DN7V1uM3Xd6VKiexPKntssFL7kAAzE2NgB9AAAABWQAIAAAAAAuHU9Qd79hjyvKOujGanSGDIQlxzsql8JytTZhEnPw+AVzACAAAAAAjF2gV/4+sOHVgDd/oR5wDi9zL7NGpGD+NsEpGXy/a4QFbAAgAAAAAJzMoyojYV6Ed/LpVN5zge93Odv3U7JgP7wxeRaJZGTdAAMxNjcAfQAAAAVkACAAAAAA7dQDkt3iyWYCT94d7yqUtPPwp4qkC0ddu+HFdHgVKEkFcwAgAAAAANuYvtvZBTEq4Rm9+5eb7VuFopowkrAuv86PGP8Q8/QvBWwAIAAAAACeqXoAOQOE4j0zRMlkVd8plaW0RX1npsFvB38Xmzv7sAADMTY4AH0AAAAFZAAgAAAAAAwnZSDhL4tNGYxlHPhKYB8s28dY5ScSwiKZm3UhT8U3BXMAIAAAAABDoY6dhivufTURQExyC9Gx3ocpl09bgbbQLChj3qVGbgVsACAAAAAAF+1nS7O0v85s3CCy+9HkdeoEfm2C6ZiNbPMMnSfsMHUAAzE2OQB9AAAABWQAIAAAAAC2VuRdaC4ZJmLdNOvD6R2tnvkyARteqXouJmI46V306QVzACAAAAAAMn1Z6B35wFTX9mEYAPM+IiJ5hauEwfD0CyIvBrxHg7IFbAAgAAAAAOG6DvDZkT9B/xZWmjao2AevN7MMbs3Oh9YJeSd/hZ+hAAMxNzAAfQAAAAVkACAAAAAAVerb7qVNy457rNOHOgDSKyWl5ojun7iWrv1uHPXrIZQFcwAgAAAAAIDcYS9j5z+gx0xdJj09L7876r/vjvKTi/d3bXDE3PhyBWwAIAAAAADuhVLqb1Bkrx8aNymS+bx2cL8GvLFNH4SAi690DUgnWQADMTcxAH0AAAAFZAAgAAAAAH/E44yLxKCJjuSmU9A8SEhbmkDOx1PqqtYcZtgOzJdrBXMAIAAAAABgLh9v2HjBbogrRoQ82LS6KjZQnzjxyJH4PH+F3jupSAVsACAAAAAAIlO46ehXp4TqpDV0t6op++KO+uWBFh8iFORZjmx2IjkAAzE3MgB9AAAABWQAIAAAAAAlNUdDL+f/SSQ5074mrq0JNh7CTXwTbbhsQyDwWeDVMwVzACAAAAAANIH2IlSNG0kUw4qz0budjcWn8mNR9cJlYUqPYdonucAFbAAgAAAAAJMrOUOyiu5Y3sV76zwEFct8L7+i8WGlQI2+8z2W2kzaAAMxNzMAfQAAAAVkACAAAAAASZ+CvUDtlk/R4HAQ3a+PHrKeY/8ifAfh0oXYFqliu80FcwAgAAAAAJelpzPgM65OZFt/mvGGpwibclQ49wH+1gbUGzd9OindBWwAIAAAAAD9qeDchteEpVXWcycmD9kl9449C1dOw0r60TBm5jK+cQADMTc0AH0AAAAFZAAgAAAAAN9fkoUVbvFV2vMNMAkak4gYfEnzwKI3eDM3pnDK5q3lBXMAIAAAAACnDkgVNVNUlbQ9RhR6Aot2nVy+U4km6+GHPkLr631jEAVsACAAAAAANzg/BnkvkmvOr8nS4omF+q9EG/4oisB+ul4YHi938hwAAzE3NQB9AAAABWQAIAAAAAASyK3b1nmNCMptVEGOjwoxYLLS9fYWm/Zxilqea0jpEQVzACAAAAAADDHsGrbqlKGEpxlvfyqOJKQJjwJrzsrB7k3HG0AUJbkFbAAgAAAAAKwx3S4XfDZh4+LuI9jf7XgUh5qiefNv87JD4qvVRfPSAAMxNzYAfQAAAAVkACAAAAAAlSP9iK31GlcG9MKGbLmq+VXMslURr+As736rrVNXcsUFcwAgAAAAAAvbj0zfq9zzi8XReheKFbCB+h9IsOLgXPPpI5vrEJNZBWwAIAAAAABXvoZhaQE7ogWjeBjceVkp03N20cKYP3TA8vuNsgpfAgADMTc3AH0AAAAFZAAgAAAAAOJNORH8Bev97gVU7y6bznOxJ+E6Qoykur1QP76hG1/7BXMAIAAAAAC+C1PtOOrSZgzBAGhr+dPe/kR0JUw9GTwLVNr61xC1aAVsACAAAAAAeA/L8MQIXkamaObtMPLpoDoi5FypA5WAPtMeMrgi0eQAAzE3OAB9AAAABWQAIAAAAAAKcHzLUomavInN6upPkyWhAqYQACP/vdVCIYpiy6U6HgVzACAAAAAATsR4KItY6R2+U7Gg6sJdaEcf58gjd1OulyWovIqfxKcFbAAgAAAAAFbm10ko67ahboAejQdAV0U2uA5OhZYdb8XUFJ8OL46LAAMxNzkAfQAAAAVkACAAAAAAqTOLiMpCdR59tLZzzIPqJvbCNvz2XQL9ust0qYaehtcFcwAgAAAAAArefox/3k5xGOeiw2m6NUdzuGxmPwcu5IFcj+jMwHgHBWwAIAAAAADLZGFJ7MQd5JXMgMXjqZO5LDLxcFClcXPlnRMWRn+1oAADMTgwAH0AAAAFZAAgAAAAAIPSqSeVzSRgNVNmrPYHmUMgykCY27NbdDUNhE5kx/SgBXMAIAAAAAAhX90nNfxyXmZe/+btZ7q6xMX4PFyj0paM1ccJ/5IUUQVsACAAAAAA419oHmD2W0SYoOMwhrhrp8jf68fg9hTkaRdCuVd3CN0AAzE4MQB9AAAABWQAIAAAAACLn5DxiqAosHGXIAY96FwFKjeqrzXWf3VJIQMwx1fl4gVzACAAAAAAindvU27nveutopdvuHmzdENBbeGFtI3Qcsr07jxmvm8FbAAgAAAAAPvl9pBStQvP4OGkN5v0MghUY6djm9n7XdKKfrW0l1sMAAMxODIAfQAAAAVkACAAAAAA7i2S6rHRSPBwZEn59yxaS7HiYBOmObIkeyCcFU42kf8FcwAgAAAAAGb3RSEyBmgarkTvyLWtOLJcPwCKbCRkESG4RZjVmY4iBWwAIAAAAADB2/wo5CSHR4ANtifY6ZRXNTO5+O8qP82DfAiAeanpZwADMTgzAH0AAAAFZAAgAAAAAFz+M+H/Z94mdPW5oP51B4HWptp1rxcMWAjnlHvWJDWrBXMAIAAAAACBFEOQyL7ZHu4Cq33QvXkmKuH5ibG/Md3RaED9CtG5HwVsACAAAAAAfggtJTprQ/yZzj7y5z9KvXsdeXMWP0yUXMMJqpOwI88AAzE4NAB9AAAABWQAIAAAAAAE7c2x3Z3aM1XGfLNk/XQ9jCazNRbGhVm7H8c2NjS5ywVzACAAAAAARJ9h8fdcwA19velF3L/Wcvi2rCzewlKZ2nA0p8bT9uwFbAAgAAAAAJtWe6b4wK2Hae2dZm/OEpYQnvoZjz4Sz5IgJC2wInecAAMxODUAfQAAAAVkACAAAAAAVoRt9B9dNVvIMGN+ea5TzRzQC+lqSZ8dd/170zU5o9cFcwAgAAAAAEwM95XZin5mv2yhCI8+ugtKuvRVmNgzzIQN0yi1+9aIBWwAIAAAAAAMGBq72n00rox3uqhxSB98mkenTGCdbbUF1gXrgottzgADMTg2AH0AAAAFZAAgAAAAAKRDkjyWv/etlYT4GyoXrmBED2FgZHnhc+l9Wsl06cH2BXMAIAAAAABohlpm3K850Vndf3NmNE0hHqDlNbSR8/IvMidQ3LnIZAVsACAAAAAAW42nGHa6q2MCAaaPVwaIDfr8QLyQwjKq23onZJYsqVsAAzE4NwB9AAAABWQAIAAAAAC3DFh5oklLCNLY90bgWm68dFXz65JpAZSp1K99MBTPAQVzACAAAAAAQgZecmxEUZVHoptEQClDwAf8smI3WynQ/i+JBP0g+kQFbAAgAAAAAEUSQGVnAPISD6voD0DiBUqyWKgt2rta0tjmoe+LNt6IAAMxODgAfQAAAAVkACAAAAAAQ5WKvWSB503qeNlOI2Tpjd5blheNr6OBO8pfJfPNstcFcwAgAAAAAKwHgQLSDJ5NwLBQbY5OnblQIsVDpGV7q3RCbFLD1U4/BWwAIAAAAACQ5nED99LnpbqXZuUOUjnO2HTphEAFBjLD4OZeDEYybgADMTg5AH0AAAAFZAAgAAAAAGfhFY3RGRm5ZgWRQef1tXxHBq5Y6fXaLAR4yJhrTBplBXMAIAAAAACKEF0ApLoB6lP2UqTFsTQYNc9OdDrs/vziPGzttGVLKQVsACAAAAAArOO6FyfNRyBi0sPT5iye7M8d16MTLcwRfodZq4uCYKEAAzE5MAB9AAAABWQAIAAAAAAIM73gPcgzgotYHLeMa2zAU4mFsr7CbILUZWfnuKSwagVzACAAAAAAJCSu98uV8xv88f2BIOWzt6p+6EjQStMBdkGPUkgN79cFbAAgAAAAAMGqPGMPxXbmYbVfSa/japvUljht1zZT33TY7ZjAiuPfAAMxOTEAfQAAAAVkACAAAAAAkWmHCUsiMy1pwZTHxVPBzPTrWFBUDqHNrVqcyyt7nO8FcwAgAAAAAMv2CebFRG/br7USELR98sIdgE9OQCRBGV5JZCO+uPMgBWwAIAAAAABt7qSmn3gxJu7aswsbUiwvO+G6lXj/Xhx+J/zQyZxzLAADMTkyAH0AAAAFZAAgAAAAAGInUYv0lP/rK7McM8taEHXRefk8Q2AunrvWqdfSV7UaBXMAIAAAAACE+WPxJ3gan7iRTbIxXXx+bKVcaf8kP4JD8DcwU0aL7wVsACAAAAAAUC4eTprX4DUZn2X+UXYU6QjtiXk+u57yoOPBbPQUmDkAAzE5MwB9AAAABWQAIAAAAACmHlg2ud3cplXlTsNTpvNnY6Qm1Fce0m899COamoDjaQVzACAAAAAArtJQeJIlepBWRU2aYar7+YGYVQ7dfDc1oxgTmA8r9q0FbAAgAAAAAOk45vg5VqZHAFCO3i0Z52SZi5RADf8NXwf68T5yad/DAAMxOTQAfQAAAAVkACAAAAAApzcWSAbZWV/Rq+ylRNqqlJqNVR4fhXrz4633/MQOQgcFcwAgAAAAAN/jz/bsEleiuCl+li83EWlG6UMHA8CyaOMRKCkXkSCPBWwAIAAAAAC3Sd+Qg+uFDKpGZHbrQgokXHQ1az1aFl4YK343OB6hcQAAEmNtAAAAAAAAAAAAABBwYXlsb2FkSWQAAAAAABBmaXJzdE9wZXJhdG9yAAEAAAAA", + "subType": "06" + } + } + } + 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: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$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" + }, + "encryptedDecimalNoPrecision": { $$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/fle2v2-Range-Decimal-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml new file mode 100644 index 0000000000..e79fd082ec --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml @@ -0,0 +1,1697 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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 {'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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: updateOne + arguments: + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0" } } } + update: { "$set": { "encryptedDecimalNoPrecision": { $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, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + 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_name: update + command: + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDecimalNoPrecision": { $$type: "binData" } + } + } + } + ] + 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: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$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" + }, + "encryptedDecimalNoPrecision": { $$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/fle2v2-Range-DecimalPrecision-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml new file mode 100644 index 0000000000..08f4a380cf --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml @@ -0,0 +1,329 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + 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: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + 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: 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/fle2v2-Range-DecimalPrecision-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml new file mode 100644 index 0000000000..18252b4bbe --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml @@ -0,0 +1,424 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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/fle2v2-Range-DecimalPrecision-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml new file mode 100644 index 0000000000..ade385d202 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml @@ -0,0 +1,226 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + 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: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + 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: 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/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml new file mode 100644 index 0000000000..7100d58886 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml @@ -0,0 +1,327 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + 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: + findAndModify: *collection_name + query: { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "DdIJAAADcGF5bG9hZACiCQAABGcAjgkAAAMwAH0AAAAFZAAgAAAAAHdJ2Vnb4MMzqVYVssjSdDy8XU4GVzMTfGifGETgQ2mYBXMAIAAAAAD7cFfKJGIXo6PjyeX2ria02CckW7dWFDoY/3FyBdm1NQVsACAAAAAAhEPSNv4M023A3hzNFuy83+hIKuZ2mKRY954N++aEOBUAAzEAfQAAAAVkACAAAAAAlmvfDrZoydUet4eCVMq7z6a58Ea+1HLJOWxN5lNcrWEFcwAgAAAAAEBo5AWZyC41b9ayjWNQSL4iYEAIwR/JG+ssN8bdoK9RBWwAIAAAAACEndE0SLxFSElOrNnqeX0EPmgDio3udZjVREy4JLS3sQADMgB9AAAABWQAIAAAAABbiLaoxAA6rinMJw1hC8ZUiq6UU1AQaPFn/py/Y06WuQVzACAAAAAAhtDasFkvYE7SCNu1je/hxdE9TJtAvvH3NtdEbKzNbCUFbAAgAAAAAIGepU1RSCF8sWODHEpKglsoqw3VBBH4a/URGxgGzbq2AAMzAH0AAAAFZAAgAAAAALORWwSr+tYNxcil2KIGSbNhTHvcPbdj+rLVQNx21S/KBXMAIAAAAAD6diZBkPEJ1cQy06LAxdbNK8Nlxbb44fH4Wk3Y3260nQVsACAAAAAA1eYAZBFHlDiaDAljWi8blGQ2nvvZa5AO5doeo0SFZsgAAzQAfQAAAAVkACAAAAAAG5XMK96PjClNlUvg82j4pMY1YxsznZfj4uNweD394FoFcwAgAAAAAKHgQLdGJHkrfFg9nB93Ac+3VgBw6aU44MTkKIQ91dZoBWwAIAAAAAAPxXmi+SDJ+40A0KdwfRczexlZQrHjIA+D3oUB0EY9tAADNQB9AAAABWQAIAAAAAA6M++b9I0YFemmWBAWAE3glu2Ah3Ta1FBxAQEIWS0toAVzACAAAAAANXYTqPf1Y6X3Ns6YQIX0C3FKCyWUo+Kk+fNcQvc0WSoFbAAgAAAAAA+uJUw1ICYgyeygSRe206VTWVtUnhdci3iHbyP5YtEVAAM2AH0AAAAFZAAgAAAAAKl8bV1riH/uyJ+X0HHd3+18k2cJl2dQFXCdoagutFcaBXMAIAAAAABm8F2Ew9f0VOABdcF+lP0Bi+zWvEUPniWgrxPq/Sx3uwVsACAAAAAAJfFErjZ6BPhsw5LjJLqNtKDLJ4zV0eIZppQpd9b0wZoAAzcAfQAAAAVkACAAAAAAsYZD8JEP6kYsPncFnNZwJxhu4YtUTKPNcjHtv67H+rYFcwAgAAAAAI4LqZcRkvbs/2F62Flu0pixNcor4WmBD0DHGaf039wLBWwAIAAAAAD4wUR3xd9lKltcqqo8LYvdMQWzCRobkV/ppKB/yn5dUgADOAB9AAAABWQAIAAAAAC0vdAi+dmoIXvZ5LqUqvyKV9/tHqSI2SWiSJO5pTnA2wVzACAAAAAAS2qvf9fvfVUH5WtsVxjxmskpGjYTQV34LwvQQw1y9wIFbAAgAAAAAE0+FKuK7HxbypvCeEJzMTcjOWE0ScYOlTBMUNlIv55hAAM5AH0AAAAFZAAgAAAAAH31lb/srBcrOXkzddCwAnclsR5/3QijEVgECs2JjOWBBXMAIAAAAABg7+prDT73YcCvLE5QbuIrqGcjLc5pQD2Miq0d29yrxgVsACAAAAAAetRiPwDSFWBzpWSWkOKWM6fKStRJ8SyObnpc79ux8p0AAzEwAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzExAH0AAAAFZAAgAAAAAFdthRhe2Q8CvxGIhjTJZv0Lk97GkHciTPxZ/mckLoNaBXMAIAAAAAAqOxsAr23LOVB0DIHbPf9UDJJRFXY2YoKbjhRqw5psbQVsACAAAAAA0G2GD8ZQjDBntjLpW4rqwKRS6HiUjL03g1N6chANozcAAzEyAH0AAAAFZAAgAAAAAMWymwwbvIeMqmnKWWifUqoCxOsdpnonM2qdLPyjqJO/BXMAIAAAAAB6IDmmpUhBD2zpRj8/y/kmOSXcjuIU14sNh6GKSsg2uwVsACAAAAAAWMFPNOk3EMSQDS9JGPSMIQP0oNGVugxXKKUrIPPlhHgAAzEzAH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzE0AH0AAAAFZAAgAAAAAJaRYmo8zqI2BEUzdSwp4tVRpPmVWsfydkYN3UHh6TMuBXMAIAAAAAAeD6mDnQeLlbC9i0sVgE8+RH6y+e94OJQ0tJ0PvblVSgVsACAAAAAAWp4jvretbDEsqEMzP/WLTnwOiJwCtfrCiB6m8k+yEMoAAzE1AH0AAAAFZAAgAAAAAAZZ538coNPwyRjhEwr5P8Xw32oWOJF+R+nfCGgy2qO3BXMAIAAAAACOPLnJlKwGNPDBReRKnHfteq0wFb3ezhrc7BVXs8RUHwVsACAAAAAA+lGesNk3+SyB/60rSvdQ2aN2vfJPR7llJVhufGTNhHkAAzE2AH0AAAAFZAAgAAAAAFH9l9GGA1I52atJV5jNUf1lx8jBjoEoVoME97v5GFJiBXMAIAAAAAC1qH3Kd78Dr9NGbw7y9D/XYBwv5h1LLO8la5OU7g8UkQVsACAAAAAArZ6atJCYrVfHB8dSNPOFf6nnDADBMJcIEj8ljPvxHp8AAzE3AH0AAAAFZAAgAAAAADtbVEI2tdkrowEMdkacD2w0Y3T3Ofi7PH6HmA6sP0c/BXMAIAAAAADuBSROnZHA+NgUPH8d0LnWFiDsM2bY8bzjC1+elSsIygVsACAAAAAAR0G2m+uANoWknkr/NerFcG+fECVxNIs0cqbY1t/U/0MAAzE4AH0AAAAFZAAgAAAAAAh3WpeMVlikPFYj9hLj+fmIqVt6omCSF75W3TPExyWpBXMAIAAAAAAsQkRmwqeVj2gGE03orb6PtrIzDt6dDU3hgSQi8E2wKgVsACAAAAAA3GHaRE2RAcaBRd8VzmYzWeBD2Gmy91eTK1k8YdWObZcAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDecimalPrecision": { $$type: "binData" }} } + 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: 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/fle2v2-Range-DecimalPrecision-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml new file mode 100644 index 0000000000..32785d6589 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml @@ -0,0 +1,319 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + 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: + find: *collection_name + filter: + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + 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: 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/fle2v2-Range-DecimalPrecision-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml new file mode 100644 index 0000000000..eedd076084 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml @@ -0,0 +1,336 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + 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_name: update + command: + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "DdIJAAADcGF5bG9hZACiCQAABGcAjgkAAAMwAH0AAAAFZAAgAAAAAHdJ2Vnb4MMzqVYVssjSdDy8XU4GVzMTfGifGETgQ2mYBXMAIAAAAAD7cFfKJGIXo6PjyeX2ria02CckW7dWFDoY/3FyBdm1NQVsACAAAAAAhEPSNv4M023A3hzNFuy83+hIKuZ2mKRY954N++aEOBUAAzEAfQAAAAVkACAAAAAAlmvfDrZoydUet4eCVMq7z6a58Ea+1HLJOWxN5lNcrWEFcwAgAAAAAEBo5AWZyC41b9ayjWNQSL4iYEAIwR/JG+ssN8bdoK9RBWwAIAAAAACEndE0SLxFSElOrNnqeX0EPmgDio3udZjVREy4JLS3sQADMgB9AAAABWQAIAAAAABbiLaoxAA6rinMJw1hC8ZUiq6UU1AQaPFn/py/Y06WuQVzACAAAAAAhtDasFkvYE7SCNu1je/hxdE9TJtAvvH3NtdEbKzNbCUFbAAgAAAAAIGepU1RSCF8sWODHEpKglsoqw3VBBH4a/URGxgGzbq2AAMzAH0AAAAFZAAgAAAAALORWwSr+tYNxcil2KIGSbNhTHvcPbdj+rLVQNx21S/KBXMAIAAAAAD6diZBkPEJ1cQy06LAxdbNK8Nlxbb44fH4Wk3Y3260nQVsACAAAAAA1eYAZBFHlDiaDAljWi8blGQ2nvvZa5AO5doeo0SFZsgAAzQAfQAAAAVkACAAAAAAG5XMK96PjClNlUvg82j4pMY1YxsznZfj4uNweD394FoFcwAgAAAAAKHgQLdGJHkrfFg9nB93Ac+3VgBw6aU44MTkKIQ91dZoBWwAIAAAAAAPxXmi+SDJ+40A0KdwfRczexlZQrHjIA+D3oUB0EY9tAADNQB9AAAABWQAIAAAAAA6M++b9I0YFemmWBAWAE3glu2Ah3Ta1FBxAQEIWS0toAVzACAAAAAANXYTqPf1Y6X3Ns6YQIX0C3FKCyWUo+Kk+fNcQvc0WSoFbAAgAAAAAA+uJUw1ICYgyeygSRe206VTWVtUnhdci3iHbyP5YtEVAAM2AH0AAAAFZAAgAAAAAKl8bV1riH/uyJ+X0HHd3+18k2cJl2dQFXCdoagutFcaBXMAIAAAAABm8F2Ew9f0VOABdcF+lP0Bi+zWvEUPniWgrxPq/Sx3uwVsACAAAAAAJfFErjZ6BPhsw5LjJLqNtKDLJ4zV0eIZppQpd9b0wZoAAzcAfQAAAAVkACAAAAAAsYZD8JEP6kYsPncFnNZwJxhu4YtUTKPNcjHtv67H+rYFcwAgAAAAAI4LqZcRkvbs/2F62Flu0pixNcor4WmBD0DHGaf039wLBWwAIAAAAAD4wUR3xd9lKltcqqo8LYvdMQWzCRobkV/ppKB/yn5dUgADOAB9AAAABWQAIAAAAAC0vdAi+dmoIXvZ5LqUqvyKV9/tHqSI2SWiSJO5pTnA2wVzACAAAAAAS2qvf9fvfVUH5WtsVxjxmskpGjYTQV34LwvQQw1y9wIFbAAgAAAAAE0+FKuK7HxbypvCeEJzMTcjOWE0ScYOlTBMUNlIv55hAAM5AH0AAAAFZAAgAAAAAH31lb/srBcrOXkzddCwAnclsR5/3QijEVgECs2JjOWBBXMAIAAAAABg7+prDT73YcCvLE5QbuIrqGcjLc5pQD2Miq0d29yrxgVsACAAAAAAetRiPwDSFWBzpWSWkOKWM6fKStRJ8SyObnpc79ux8p0AAzEwAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzExAH0AAAAFZAAgAAAAAFdthRhe2Q8CvxGIhjTJZv0Lk97GkHciTPxZ/mckLoNaBXMAIAAAAAAqOxsAr23LOVB0DIHbPf9UDJJRFXY2YoKbjhRqw5psbQVsACAAAAAA0G2GD8ZQjDBntjLpW4rqwKRS6HiUjL03g1N6chANozcAAzEyAH0AAAAFZAAgAAAAAMWymwwbvIeMqmnKWWifUqoCxOsdpnonM2qdLPyjqJO/BXMAIAAAAAB6IDmmpUhBD2zpRj8/y/kmOSXcjuIU14sNh6GKSsg2uwVsACAAAAAAWMFPNOk3EMSQDS9JGPSMIQP0oNGVugxXKKUrIPPlhHgAAzEzAH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzE0AH0AAAAFZAAgAAAAAJaRYmo8zqI2BEUzdSwp4tVRpPmVWsfydkYN3UHh6TMuBXMAIAAAAAAeD6mDnQeLlbC9i0sVgE8+RH6y+e94OJQ0tJ0PvblVSgVsACAAAAAAWp4jvretbDEsqEMzP/WLTnwOiJwCtfrCiB6m8k+yEMoAAzE1AH0AAAAFZAAgAAAAAAZZ538coNPwyRjhEwr5P8Xw32oWOJF+R+nfCGgy2qO3BXMAIAAAAACOPLnJlKwGNPDBReRKnHfteq0wFb3ezhrc7BVXs8RUHwVsACAAAAAA+lGesNk3+SyB/60rSvdQ2aN2vfJPR7llJVhufGTNhHkAAzE2AH0AAAAFZAAgAAAAAFH9l9GGA1I52atJV5jNUf1lx8jBjoEoVoME97v5GFJiBXMAIAAAAAC1qH3Kd78Dr9NGbw7y9D/XYBwv5h1LLO8la5OU7g8UkQVsACAAAAAArZ6atJCYrVfHB8dSNPOFf6nnDADBMJcIEj8ljPvxHp8AAzE3AH0AAAAFZAAgAAAAADtbVEI2tdkrowEMdkacD2w0Y3T3Ofi7PH6HmA6sP0c/BXMAIAAAAADuBSROnZHA+NgUPH8d0LnWFiDsM2bY8bzjC1+elSsIygVsACAAAAAAR0G2m+uANoWknkr/NerFcG+fECVxNIs0cqbY1t/U/0MAAzE4AH0AAAAFZAAgAAAAAAh3WpeMVlikPFYj9hLj+fmIqVt6omCSF75W3TPExyWpBXMAIAAAAAAsQkRmwqeVj2gGE03orb6PtrIzDt6dDU3hgSQi8E2wKgVsACAAAAAA3GHaRE2RAcaBRd8VzmYzWeBD2Gmy91eTK1k8YdWObZcAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDecimalPrecision": { $$type: "binData" } + } + } + } + ] + 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: + # 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/fle2v2-Range-Double-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml new file mode 100644 index 0000000000..4fb95343b0 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml @@ -0,0 +1,913 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDoubleNoPrecision": { $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, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + 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: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$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, + "encryptedDoubleNoPrecision": { $$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/fle2v2-Range-Double-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml new file mode 100644 index 0000000000..7289bb24dc --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml @@ -0,0 +1,292 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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, encryptedDoubleNoPrecision: { $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: { encryptedDoubleNoPrecision: { $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/fle2v2-Range-Double-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml new file mode 100644 index 0000000000..2f42c1da10 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml @@ -0,0 +1,518 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDoubleNoPrecision": { $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, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + 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: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$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/fle2v2-Range-Double-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml new file mode 100644 index 0000000000..d6573ff862 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml @@ -0,0 +1,911 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDoubleNoPrecision: { $gt: {$numberDouble: "0"}} } + update: { "$set": { "encryptedDoubleNoPrecision": {$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, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + findAndModify: *collection_name + query: { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDoubleNoPrecision": { $$type: "binData" }} } + 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: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$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, + "encryptedDoubleNoPrecision": { $$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/fle2v2-Range-Double-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml new file mode 100644 index 0000000000..0122ba243c --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml @@ -0,0 +1,907 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $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, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + find: *collection_name + filter: + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + 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: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$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, + "encryptedDoubleNoPrecision": { $$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/fle2v2-Range-Double-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml new file mode 100644 index 0000000000..176db3971e --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml @@ -0,0 +1,924 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: updateOne + arguments: + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0" } } } + update: { "$set": { "encryptedDoubleNoPrecision": { $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, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + 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_name: update + command: + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDoubleNoPrecision": { $$type: "binData" } + } + } + } + ] + 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: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$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, + "encryptedDoubleNoPrecision": { $$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/fle2v2-Range-DoublePrecision-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml new file mode 100644 index 0000000000..134003bf9c --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml @@ -0,0 +1,325 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + 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: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + 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: 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/fle2v2-Range-DoublePrecision-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml new file mode 100644 index 0000000000..a8fc4ec2a4 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml @@ -0,0 +1,424 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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/fle2v2-Range-DoublePrecision-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml new file mode 100644 index 0000000000..a6f83da786 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml @@ -0,0 +1,224 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + 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: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + 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: 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/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml new file mode 100644 index 0000000000..5def8d287d --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml @@ -0,0 +1,323 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + 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: + 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: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + 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/fle2v2-Range-DoublePrecision-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml new file mode 100644 index 0000000000..8900f79a86 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml @@ -0,0 +1,319 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + 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: + find: *collection_name + filter: + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + 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: 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/fle2v2-Range-DoublePrecision-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml new file mode 100644 index 0000000000..3e31f40181 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml @@ -0,0 +1,338 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + 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_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": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$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/fle2v2-Range-Int-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml new file mode 100644 index 0000000000..13c350ea5f --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml @@ -0,0 +1,241 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + 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: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "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" + } + } + } + } + } + ] + cursor: {} + 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: 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/fle2v2-Range-Int-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml new file mode 100644 index 0000000000..1e7d5d47fc --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml @@ -0,0 +1,423 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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/fle2v2-Range-Int-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml new file mode 100644 index 0000000000..ab1e9d2e5e --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml @@ -0,0 +1,182 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + 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: + delete: *collection_name + deletes: [ + { + "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" + } + } + } + }, + "limit": 1 + } + ] + 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: 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/fle2v2-Range-Int-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml new file mode 100644 index 0000000000..a33a5120a5 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml @@ -0,0 +1,239 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + 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: + findAndModify: *collection_name + query: { + "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" + } + } + } + } + update: { "$set": {"encryptedInt": { $$type: "binData" }} } + 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: 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/fle2v2-Range-Int-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml new file mode 100644 index 0000000000..4ef8c8e520 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml @@ -0,0 +1,235 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + 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: + find: *collection_name + filter: + "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" + } + } + } + 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: 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/fle2v2-Range-Int-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml new file mode 100644 index 0000000000..cf5716dab0 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml @@ -0,0 +1,254 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + 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_name: update + command: + + "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": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$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/fle2v2-Range-Long-Aggregate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml new file mode 100644 index 0000000000..cb5e42c158 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml @@ -0,0 +1,241 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + 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: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "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" + } + } + } + } + } + ] + cursor: {} + 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: 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/fle2v2-Range-Long-Correctness.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml new file mode 100644 index 0000000000..a7a33e274e --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-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.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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/fle2v2-Range-Long-Delete.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml new file mode 100644 index 0000000000..8dd1603f33 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml @@ -0,0 +1,182 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + 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: + delete: *collection_name + deletes: [ + { + "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" + } + } + } + }, + "limit": 1 + } + ] + 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: 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/fle2v2-Range-Long-FindOneAndUpdate.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml new file mode 100644 index 0000000000..0641988b98 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml @@ -0,0 +1,239 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + 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: + findAndModify: *collection_name + query: { + "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" + } + } + } + } + update: { "$set": {"encryptedLong": { $$type: "binData" }} } + 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: 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/fle2v2-Range-Long-InsertFind.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml new file mode 100644 index 0000000000..076670d49e --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml @@ -0,0 +1,235 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + 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: + find: *collection_name + filter: + "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" + } + } + } + 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: 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/fle2v2-Range-Long-Update.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml new file mode 100644 index 0000000000..0aad7c4416 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml @@ -0,0 +1,254 @@ + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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: + # 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: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + 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_name: update + command: + + "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": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$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/fle2v2-Range-WrongType.yml b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml new file mode 100644 index 0000000000..b44e8c5055 --- /dev/null +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml @@ -0,0 +1,43 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "7.0.0" + # 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': '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" 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 19bb549b23..3bed027181 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,13 @@ +# Requires libmongocrypt 1.8.0. runOn: - - minServerVersion: "6.0.0" + - minServerVersion: "7.0.0" + # 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" ] + 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 +65,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 +81,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -82,27 +89,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 +164,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 +180,7 @@ tests: "encryptedIndexed": { "$eq": { "$binary": { - "base64": "BbEAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVjACAAAAAA19X9v9NlWidu/wR5/C/7WUV54DfL5CkNmT5WYrhxdDcFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsEmNtAAAAAAAAAAAAAA==", + "base64": "DIkAAAAFZAAgAAAAAPtVteJQAlgb2YMa/+7YWH00sbQPyt7L6Rb8OwBdMmL2BXMAIAAAAAAd44hgVKnEnTFlwNVC14oyc9OZOTspeymusqkRQj57nAVsACAAAAAAaZ9s3G+4znfxStxeOZwcZy1OhzjMGc5hjmdMN+b/w6kSY20AAAAAAAAAAAAA", "subType": "06" } } @@ -187,28 +188,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 64% 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 b85a7c876d..3eb90ea935 100644 --- a/spec/spec_tests/data/client_side_encryption/fle2-validatorAndPartialFieldExpression.yml +++ b/spec/spec_tests/data/client_side_encryption/fle2v2-validatorAndPartialFieldExpression.yml @@ -1,9 +1,10 @@ -# 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" + # 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" ] + topology: [ "replicaset", "sharded", "load-balanced" ] database_name: &database_name "default" collection_name: &collection_name "default" @@ -15,7 +16,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 +40,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 +61,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 +85,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 +111,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 +144,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/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" 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" 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..5ff0cd7d8f 100644 --- a/spec/spec_tests/data/cmap/pool-checkin.yml +++ b/spec/spec_tests/data/cmap/pool-checkin.yml @@ -1,7 +1,11 @@ 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 label: conn - name: checkIn @@ -12,6 +16,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-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-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-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..11acf17e76 --- /dev/null +++ b/spec/spec_tests/data/cmap/pool-checkout-minPoolSize-connection-maxConnecting.yml @@ -0,0 +1,64 @@ +version: 1 +style: integration +description: threads blocked by maxConnecting check out minPoolSize connections +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: 1000 +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. + - name: wait + ms: 200 + - 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-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-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-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 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-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-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-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/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/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 6b9da01b42..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,9 +1,16 @@ 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: + # ensure no connections are created until this pool is ready + - name: wait + ms: 200 + - name: ready - name: waitForEvent event: ConnectionCreated count: 3 @@ -15,6 +22,8 @@ events: - type: ConnectionPoolCreated address: 42 options: 42 + - type: ConnectionPoolReady + address: 42 - type: ConnectionCreated connectionId: 42 address: 42 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-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..e0fd224810 100644 --- a/spec/spec_tests/data/cmap/wait-queue-timeout.yml +++ b/spec/spec_tests/data/cmap/wait-queue-timeout.yml @@ -1,10 +1,14 @@ 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: 20 + waitQueueTimeoutMS: 50 operations: + - name: ready # Check out only possible connection - name: checkOut label: conn0 @@ -44,3 +48,4 @@ ignore: - ConnectionReady - ConnectionClosed - ConnectionPoolCreated + - ConnectionPoolReady 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/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 + 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/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 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 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/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/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..0f456b9cdf --- /dev/null +++ b/spec/spec_tests/data/crud_unified/find-test-all-options.yml @@ -0,0 +1,348 @@ +description: "find options" + +schemaVersion: "1.0" +runOnRequirements: + - serverless: 'forbid' + + +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 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/index_management/createSearchIndex.yml b/spec/spec_tests/data/index_management/createSearchIndex.yml new file mode 100644 index 0000000000..2e3cf50f8d --- /dev/null +++ b/spec/spec_tests/data/index_management/createSearchIndex.yml @@ -0,0 +1,64 @@ +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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: 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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index' } ] + $db: *database0 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..db8f02e551 --- /dev/null +++ b/spec/spec_tests/data/index_management/createSearchIndexes.yml @@ -0,0 +1,86 @@ +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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: 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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: 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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index' } ] + $db: *database0 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..8a8e829454 --- /dev/null +++ b/spec/spec_tests/data/index_management/dropSearchIndex.yml @@ -0,0 +1,43 @@ +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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: 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..c0d2d9ddc5 --- /dev/null +++ b/spec/spec_tests/data/index_management/listSearchIndexes.yml @@ -0,0 +1,91 @@ +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" + skipReason: https://jira.mongodb.org/browse/DRIVERS-2794 + 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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + pipeline: + - $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: + 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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + pipeline: + - $listSearchIndexes: { name: *indexName } + $db: *database0 + + - description: aggregation cursor options are supported + skipReason: https://jira.mongodb.org/browse/DRIVERS-2794 + 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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + cursor: { batchSize: 10 } + pipeline: + - $listSearchIndexes: { name: *indexName } + $db: *database0 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..2c56e75ef6 --- /dev/null +++ b/spec/spec_tests/data/index_management/updateSearchIndex.yml @@ -0,0 +1,46 @@ +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. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + updateSearchIndex: *collection0 + name: *indexName + definition: *definition + $db: *database0 + 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/bulkWrite-serverErrors.yml b/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml index cb67304c74..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-replicaset ] operations: - name: failPoint object: testRunner 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 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: [] 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..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-replicaset ] operations: - name: failPoint object: testRunner 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/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"}, } } ] 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_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: 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-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/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-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/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..bbb11d8a93 --- /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: 2 + 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/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/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 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..e5b49de35d --- /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/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 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 } 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 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/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/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/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/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 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/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" } 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/observeSensitiveCommands.yml b/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml new file mode 100644 index 0000000000..cd4d27a165 --- /dev/null +++ b/spec/spec_tests/data/unified/valid-pass/observeSensitiveCommands.yml @@ -0,0 +1,255 @@ +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" + runOnRequirements: + - maxServerVersion: 6.1.99 # getnonce removed as of 6.2 via SERVER-71007 + 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" + runOnRequirements: + - maxServerVersion: 6.1.99 # getnonce removed as of 6.2 via SERVER-71007 + operations: + - name: runCommand + object: *databaseDoNotObserveSensitiveCommands + arguments: + commandName: getnonce + command: { getnonce: 1 } + expectEvents: + - client: *clientDoNotObserveSensitiveCommands + 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 + 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 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..5748c0779f 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 @@ -141,7 +143,8 @@ tests: - description: "readConcern majority with out stage" runOnRequirements: - minServerVersion: "4.1.0" - topologies: [ replicaset, sharded-replicaset ] + topologies: [ replicaset, sharded ] + 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..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 @@ -141,8 +140,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 @@ -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 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 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/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/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 1fb849913d..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' @@ -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/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/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 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_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 2c0149b581..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' @@ -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/spec_tests/sdam_unified_spec.rb b/spec/spec_tests/sdam_unified_spec.rb new file mode 100644 index 0000000000..21d6e1cbb3 --- /dev/null +++ b/spec/spec_tests/sdam_unified_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +# rubocop:todo all + +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 + forbid_x509_auth + + define_unified_spec_tests(base, SDAM_UNIFIED_TESTS) +end 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 609be2f067..70ad2f3efd 100644 --- a/spec/spec_tests/unified_spec.rb +++ b/spec/spec_tests/unified_spec.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all require 'spec_helper' 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 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 6a3c91e4bd..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' @@ -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 124ef61026..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' @@ -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,36 +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}" - 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 = Time.now - 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 with low max idle time: #{Mongo::Utils.monotonic_time - start}" end end @@ -156,9 +131,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 +147,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..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' @@ -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 @@ -103,7 +103,6 @@ status.exitstatus.should == 0 end end - end end end 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..b6dd43c677 100644 --- a/spec/support/aws_utils.rb +++ b/spec/support/aws_utils.rb @@ -1,16 +1,17 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all 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 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 ce6864506a..aca9fb2876 100644 --- a/spec/support/aws_utils/orchestrator.rb +++ b/spec/support/aws_utils/orchestrator.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all + +require 'securerandom' module AwsUtils class Orchestrator < Base @@ -13,13 +15,24 @@ 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 ) 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 +42,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 +56,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 +301,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 +326,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/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/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 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 356aabba0d..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' @@ -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 @@ -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. 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 24be67e54a..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' @@ -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 @@ -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/common_shortcuts.rb b/spec/support/common_shortcuts.rb index 5d85c10cd2..32d386b36a 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 @@ -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,17 @@ 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) + # Stop the populator so that we don't have leftover threads. + pool.instance_variable_get('@populator').stop! + end + end end end @@ -363,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 @@ -392,5 +406,16 @@ 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) + if pool = server.pool_internal + pool.close + end + server.remove_instance_variable('@pool') + server.pool.ready + end end end diff --git a/spec/support/constraints.rb b/spec/support/constraints.rb index 1bb073489f..6d92409937 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 @@ -16,4 +16,24 @@ def require_local_tls end 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) + 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 diff --git a/spec/support/crypt.rb b/spec/support/crypt.rb index e270b5d78b..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. # @@ -115,7 +115,16 @@ 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[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"), + mode: :bson + ) + end end let(:key1_document) do @@ -139,6 +148,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 @@ -300,7 +310,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) diff --git a/spec/support/crypt/encryptedFields.json b/spec/support/crypt/encrypted_fields/encryptedFields.json similarity index 93% rename from spec/support/crypt/encryptedFields.json rename to spec/support/crypt/encrypted_fields/encryptedFields.json index 2364590e4c..15186fdeba 100644 --- a/spec/support/crypt/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/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 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 f14324c204..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| @@ -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/mongos_macros.rb b/spec/support/mongos_macros.rb index 84b450dba4..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 @@ -14,4 +15,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 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/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 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..8f44ac78c7 100644 --- a/spec/support/shared/app_metadata.rb +++ b/spec/support/shared/app_metadata.rb @@ -1,5 +1,17 @@ # frozen_string_literal: true -# encoding: utf-8 +# 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 @@ -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 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 756027e2aa..34d6c6d478 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' @@ -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/ @@ -170,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? @@ -370,7 +377,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 @@ -393,6 +400,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 @@ -472,7 +494,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/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 5f9106cb0d..a80b8a2c8e 100644 --- a/spec/support/using_hash.rb +++ b/spec/support/using_hash.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true -# encoding: utf-8 +# rubocop:todo all 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 diff --git a/spec/support/utils.rb b/spec/support/utils.rb index c17bef20fc..fa572adaa6 100644 --- a/spec/support/utils.rb +++ b/spec/support/utils.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -# encoding: utf-8 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,20 +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' - 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[:auto_encryption_options] = auto_encryption_options else mapper.add_uri_option(name, value.to_s, opts) end @@ -155,77 +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 - end - module_function :convert_auto_encryption_client_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'] @@ -250,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 @@ -265,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', @@ -349,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| @@ -367,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 @@ -388,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'] @@ -429,80 +357,72 @@ 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 - deadline = Time.now + 15 + def wait_for_instance_profile + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 15 loop do begin ip = ec2_instance_profile @@ -510,52 +430,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 Time.now >= deadline + + 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 - deadline = Time.now + 15 + 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 Time.now >= deadline + + if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline raise 'Instance profile did not get cleared in 15 seconds' end + sleep 3 end end - 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) @@ -576,7 +498,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 @@ -593,7 +515,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 @@ -601,50 +523,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 diff --git a/upload-api-docs b/upload-api-docs new file mode 100755 index 0000000000..79d1be7780 --- /dev/null +++ b/upload-api-docs @@ -0,0 +1,127 @@ +#!/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', '>= 0.9.35' +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 + puts "Uploading to #{@bucket}" + 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] + ) + begin + File.delete(File.join(options[:docs_path], 'frames.html')) + rescue StandardError + nil + end +end + +options = Options.new +generate_docs(options) +FileUploader.new(options).upload_docs +return