From 241f400ba82ec12e249f172e8de247564c75c298 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Fri, 30 Apr 2021 16:04:37 -0700 Subject: [PATCH 01/14] PYTHON-2672 Add load balancer tests in EVG Add loadBalanced=true to default test client opts. --- .evergreen/config.yml | 49 +++++++++++++++++++ .evergreen/run-tests.sh | 12 ++++- test/__init__.py | 13 ++++- test/load_balancer/test_crud_unified.py | 23 +++++++++ test/load_balancer/test_dns.py | 23 +++++++++ .../test_retryable_change_stream.py | 23 +++++++++ test/load_balancer/test_retryable_reads.py | 23 +++++++++ test/load_balancer/test_retryable_writes.py | 23 +++++++++ .../test_transactions_unified.py | 23 +++++++++ test/load_balancer/test_uri_options.py | 23 +++++++++ test/load_balancer/test_versioned_api.py | 23 +++++++++ 11 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 test/load_balancer/test_crud_unified.py create mode 100644 test/load_balancer/test_dns.py create mode 100644 test/load_balancer/test_retryable_change_stream.py create mode 100644 test/load_balancer/test_retryable_reads.py create mode 100644 test/load_balancer/test_retryable_writes.py create mode 100644 test/load_balancer/test_transactions_unified.py create mode 100644 test/load_balancer/test_uri_options.py create mode 100644 test/load_balancer/test_versioned_api.py diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 3634c97faa..7dc8a92c45 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -411,6 +411,11 @@ functions: if [ -n "${SETDEFAULTENCODING}" ]; then export SETDEFAULTENCODING="${SETDEFAULTENCODING}" fi + if [ -n "${test_loadbalancer}" ]; then + export TEST_LOADBALANCER=1 + export SINGLE_MONGOS_LB_URI="${SINGLE_MONGOS_LB_URI}" + export MULTI_MONGOS_LB_URI="${MULTI_MONGOS_LB_URI}" + fi PYTHON_BINARY=${PYTHON_BINARY} \ GREEN_FRAMEWORK=${GREEN_FRAMEWORK} \ @@ -788,6 +793,22 @@ functions: -v \ --fault revoked + "run load-balancer": + - command: shell.exec + params: + script: | + DRIVERS_TOOLS=${DRIVERS_TOOLS} MONGODB_URI=${MONGODB_URI} bash ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh start + - command: expansions.update + params: + file: lb-expansion.yml + + "stop load-balancer": + - command: shell.exec + params: + script: | + cd ${DRIVERS_TOOLS}/.evergreen + DRIVERS_TOOLS=${DRIVERS_TOOLS} bash ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh stop + "teardown_docker": - command: shell.exec params: @@ -1537,6 +1558,13 @@ tasks: - func: "run aws auth test with aws EC2 credentials" - func: "run aws ECS auth test" + - name: load-balancer-test + commands: + - func: "bootstrap mongo-orchestration" + vars: + TOPOLOGY: "sharded_cluster" + - func: "run load-balancer" + - func: "run tests" # }}} - name: "coverage-report" tags: ["coverage"] @@ -1941,6 +1969,16 @@ axes: variables: ORCHESTRATION_FILE: "versioned-api-testing.json" + # Run load balancer tests? + - id: loadbalancer + display_name: "Load Balancer" + values: + - id: "enabled" + display_name: "Load Balancer" + variables: + test_loadbalancer: true + batchtime: 10080 # 7 days + buildvariants: - matrix_name: "tests-all" matrix_spec: @@ -2463,6 +2501,17 @@ buildvariants: - name: "aws-auth-test-4.4" - name: "aws-auth-test-latest" +- matrix_name: "load-balancer" + matrix_spec: + platform: ubuntu-18.04 + mongodb-version: ["latest"] + auth-ssl: "*" + python-version: ["3.6", "3.9"] + loadbalancer: "*" + display_name: "Load Balancer ${platform} ${python-version} ${mongodb-version} ${auth-ssl}" + tasks: + - name: "load-balancer-test" + - matrix_name: "Release" matrix_spec: platform: [ubuntu-20.04, windows-64-vsMulti-small, macos-1014] diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 7a78401264..12728f7dc0 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -51,6 +51,11 @@ fi if [ "$SSL" != "nossl" ]; then export CLIENT_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/client.pem" export CA_PEM="$DRIVERS_TOOLS/.evergreen/x509gen/ca.pem" + + if [ -n "$TEST_LOADBALANCER" ]; then + export SINGLE_MONGOS_LB_URI="${SINGLE_MONGOS_LB_URI}&tls=true" + export MULTI_MONGOS_LB_URI="${MULTI_MONGOS_LB_URI}&tls=true" + fi fi # For createvirtualenv. @@ -191,7 +196,12 @@ if [ -z "$GREEN_FRAMEWORK" ]; then # causing this script to exit. $PYTHON -c "from bson import _cbson; from pymongo import _cmessage" fi - $PYTHON $COVERAGE_ARGS setup.py $C_EXTENSIONS test $TEST_ARGS $OUTPUT + + if [ -n "$TEST_LOADBALANCER" ]; then + $PYTHON -m unittest discover -s test/load_balancer -v + else + $PYTHON $COVERAGE_ARGS setup.py $C_EXTENSIONS test $TEST_ARGS $OUTPUT + fi else # --no_ext has to come before "test" so there is no way to toggle extensions here. $PYTHON green_framework_test.py $GREEN_FRAMEWORK $OUTPUT diff --git a/test/__init__.py b/test/__init__.py index 83dac398e4..4b03a2f572 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -50,6 +50,7 @@ from pymongo.common import partition_node from pymongo.server_api import ServerApi from pymongo.ssl_support import HAVE_SSL, validate_cert_reqs +from pymongo.uri_parser import parse_uri from test.version import Version if HAVE_SSL: @@ -92,6 +93,14 @@ COMPRESSORS = os.environ.get("COMPRESSORS") MONGODB_API_VERSION = os.environ.get("MONGODB_API_VERSION") +TEST_LOADBALANCER = bool(os.environ.get("TEST_LOADBALANCER")) +SINGLE_MONGOS_LB_URI = os.environ.get("SINGLE_MONGOS_LB_URI") +MULTI_MONGOS_LB_URI = os.environ.get("MULTI_MONGOS_LB_URI") +if TEST_LOADBALANCER: + res = parse_uri(SINGLE_MONGOS_LB_URI) + host, port = res['nodelist'][0] + db_user = res['username'] or db_user + db_pwd = res['password'] or db_pwd def is_server_resolvable(): @@ -216,7 +225,9 @@ def __init__(self): self.client = None self.conn_lock = threading.Lock() self.is_data_lake = False - self.load_balancer = False + self.load_balancer = TEST_LOADBALANCER + if self.load_balancer: + self.default_client_options["loadBalanced"] = True if COMPRESSORS: self.default_client_options["compressors"] = COMPRESSORS if MONGODB_API_VERSION: diff --git a/test/load_balancer/test_crud_unified.py b/test/load_balancer/test_crud_unified.py new file mode 100644 index 0000000000..dfe0935bba --- /dev/null +++ b/test/load_balancer/test_crud_unified.py @@ -0,0 +1,23 @@ +# Copyright 2021-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. + +import sys +import unittest + +sys.path[0:0] = [""] + +from test.test_crud_unified import * + +if __name__ == '__main__': + unittest.main() diff --git a/test/load_balancer/test_dns.py b/test/load_balancer/test_dns.py new file mode 100644 index 0000000000..047b98b121 --- /dev/null +++ b/test/load_balancer/test_dns.py @@ -0,0 +1,23 @@ +# Copyright 2021-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. + +import sys +import unittest + +sys.path[0:0] = [""] + +from test.test_dns import * + +if __name__ == '__main__': + unittest.main() diff --git a/test/load_balancer/test_retryable_change_stream.py b/test/load_balancer/test_retryable_change_stream.py new file mode 100644 index 0000000000..b7c902dd30 --- /dev/null +++ b/test/load_balancer/test_retryable_change_stream.py @@ -0,0 +1,23 @@ +# Copyright 2021-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. + +import sys +import unittest + +sys.path[0:0] = [""] + +from test.test_change_stream import * + +if __name__ == '__main__': + unittest.main() diff --git a/test/load_balancer/test_retryable_reads.py b/test/load_balancer/test_retryable_reads.py new file mode 100644 index 0000000000..c5de3c9078 --- /dev/null +++ b/test/load_balancer/test_retryable_reads.py @@ -0,0 +1,23 @@ +# Copyright 2021-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. + +import sys +import unittest + +sys.path[0:0] = [""] + +from test.test_retryable_reads import * + +if __name__ == '__main__': + unittest.main() diff --git a/test/load_balancer/test_retryable_writes.py b/test/load_balancer/test_retryable_writes.py new file mode 100644 index 0000000000..3800641b08 --- /dev/null +++ b/test/load_balancer/test_retryable_writes.py @@ -0,0 +1,23 @@ +# Copyright 2021-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. + +import sys +import unittest + +sys.path[0:0] = [""] + +from test.test_retryable_writes import * + +if __name__ == '__main__': + unittest.main() diff --git a/test/load_balancer/test_transactions_unified.py b/test/load_balancer/test_transactions_unified.py new file mode 100644 index 0000000000..2572028046 --- /dev/null +++ b/test/load_balancer/test_transactions_unified.py @@ -0,0 +1,23 @@ +# Copyright 2021-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. + +import sys +import unittest + +sys.path[0:0] = [""] + +from test.test_transactions_unified import * + +if __name__ == '__main__': + unittest.main() diff --git a/test/load_balancer/test_uri_options.py b/test/load_balancer/test_uri_options.py new file mode 100644 index 0000000000..b644d7d334 --- /dev/null +++ b/test/load_balancer/test_uri_options.py @@ -0,0 +1,23 @@ +# Copyright 2021-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. + +import sys +import unittest + +sys.path[0:0] = [""] + +from test.test_uri_spec import * + +if __name__ == '__main__': + unittest.main() diff --git a/test/load_balancer/test_versioned_api.py b/test/load_balancer/test_versioned_api.py new file mode 100644 index 0000000000..7e801968cb --- /dev/null +++ b/test/load_balancer/test_versioned_api.py @@ -0,0 +1,23 @@ +# Copyright 2021-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. + +import sys +import unittest + +sys.path[0:0] = [""] + +from test.test_versioned_api import * + +if __name__ == '__main__': + unittest.main() From 492421c9aa4c45a53ad0e6c8f36e7c32d50e1656 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Tue, 18 May 2021 16:30:23 -0700 Subject: [PATCH 02/14] PYTHON-2676 Add load balancer spec tests --- test/load_balancer/test_load_balancer.py | 34 + test/load_balancer/unified/cursors.json | 1228 +++++++++++++ .../unified/event-monitoring.json | 184 ++ .../unified/lb-connection-establishment.json | 58 + .../non-lb-connection-establishment.json | 92 + .../unified/sdam-error-handling.json | 508 ++++++ .../unified/server-selection.json | 82 + test/load_balancer/unified/transactions.json | 1606 +++++++++++++++++ .../unified/wait-queue-timeouts.json | 153 ++ 9 files changed, 3945 insertions(+) create mode 100644 test/load_balancer/test_load_balancer.py create mode 100644 test/load_balancer/unified/cursors.json create mode 100644 test/load_balancer/unified/event-monitoring.json create mode 100644 test/load_balancer/unified/lb-connection-establishment.json create mode 100644 test/load_balancer/unified/non-lb-connection-establishment.json create mode 100644 test/load_balancer/unified/sdam-error-handling.json create mode 100644 test/load_balancer/unified/server-selection.json create mode 100644 test/load_balancer/unified/transactions.json create mode 100644 test/load_balancer/unified/wait-queue-timeouts.json diff --git a/test/load_balancer/test_load_balancer.py b/test/load_balancer/test_load_balancer.py new file mode 100644 index 0000000000..c31ff58ef1 --- /dev/null +++ b/test/load_balancer/test_load_balancer.py @@ -0,0 +1,34 @@ +# Copyright 2021-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. + +"""Test the Load Balancer unified spec tests.""" + +import os +import sys + +sys.path[0:0] = [""] + +from test import unittest + +from test.unified_format import generate_test_classes + +# Location of JSON test specifications. +TEST_PATH = os.path.join( + os.path.dirname(os.path.realpath(__file__)), 'unified') + +# Generate unified tests. +globals().update(generate_test_classes(TEST_PATH, module=__name__)) + +if __name__ == "__main__": + unittest.main() diff --git a/test/load_balancer/unified/cursors.json b/test/load_balancer/unified/cursors.json new file mode 100644 index 0000000000..43e4fbb4f6 --- /dev/null +++ b/test/load_balancer/unified/cursors.json @@ -0,0 +1,1228 @@ +{ + "description": "cursors are correctly pinned to connections for load-balanced clusters", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent", + "connectionReadyEvent", + "connectionClosedEvent", + "connectionCheckedOutEvent", + "connectionCheckedInEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + }, + { + "collection": { + "id": "collection1", + "database": "database0", + "collectionName": "coll1" + } + }, + { + "collection": { + "id": "collection2", + "database": "database0", + "collectionName": "coll2" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + }, + { + "collectionName": "coll1", + "databaseName": "database0Name", + "documents": [] + }, + { + "collectionName": "coll2", + "databaseName": "database0Name", + "documents": [] + } + ], + "tests": [ + { + "description": "no connection is pinned if all documents are returned in the initial batch", + "operations": [ + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {} + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": {} + }, + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": 0, + "firstBatch": { + "$$type": "array" + }, + "ns": { + "$$type": "string" + } + } + }, + "commandName": "find" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connections are returned when the cursor is drained", + "operations": [ + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 2 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 3 + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + }, + { + "name": "close", + "object": "cursor0" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": {}, + "batchSize": 2 + }, + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": { + "$$type": "long" + }, + "firstBatch": { + "$$type": "array" + }, + "ns": { + "$$type": "string" + } + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": "long" + }, + "collection": "coll0" + }, + "commandName": "getMore" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": 0, + "ns": { + "$$type": "string" + }, + "nextBatch": { + "$$type": "array" + } + } + }, + "commandName": "getMore" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connections are returned to the pool when the cursor is closed", + "operations": [ + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "close", + "object": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": {}, + "batchSize": 2 + }, + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": { + "$$type": "long" + }, + "firstBatch": { + "$$type": "array" + }, + "ns": { + "$$type": "string" + } + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "killCursors" + } + }, + { + "commandSucceededEvent": { + "commandName": "killCursors" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connections are not returned after an network error during getMore", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "closeConnection": true + } + } + } + }, + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 2 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectError": { + "isClientError": true + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "close", + "object": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": {}, + "batchSize": 2 + }, + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": { + "$$type": "long" + }, + "firstBatch": { + "$$type": "array" + }, + "ns": { + "$$type": "string" + } + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": "long" + }, + "collection": "coll0" + }, + "commandName": "getMore" + } + }, + { + "commandFailedEvent": { + "commandName": "getMore" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": { + "reason": "error" + } + } + ] + } + ] + }, + { + "description": "pinned connections are returned after a network error during a killCursors request", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "killCursors" + ], + "closeConnection": true + } + } + } + }, + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "close", + "object": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": {}, + "batchSize": 2 + }, + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": { + "$$type": "long" + }, + "firstBatch": { + "$$type": "array" + }, + "ns": { + "$$type": "string" + } + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "killCursors" + } + }, + { + "commandFailedEvent": { + "commandName": "killCursors" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": { + "reason": "error" + } + } + ] + } + ] + }, + { + "description": "pinned connections are not returned to the pool after a non-network error on getMore", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getMore" + ], + "errorCode": 7 + } + } + } + }, + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectResult": { + "_id": 2 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectError": { + "errorCode": 7 + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "close", + "object": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": {}, + "batchSize": 2 + }, + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": { + "$$type": "long" + }, + "firstBatch": { + "$$type": "array" + }, + "ns": { + "$$type": "string" + } + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": "long" + }, + "collection": "coll0" + }, + "commandName": "getMore" + } + }, + { + "commandFailedEvent": { + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "commandName": "killCursors" + } + }, + { + "commandSucceededEvent": { + "commandName": "killCursors" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "aggregate pins the cursor to a connection", + "operations": [ + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "pipeline": [], + "batchSize": 2 + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll0", + "cursor": { + "batchSize": 2 + } + }, + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": "long" + }, + "collection": "coll0" + }, + "commandName": "getMore" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": 0, + "ns": { + "$$type": "string" + }, + "nextBatch": { + "$$type": "array" + } + } + }, + "commandName": "getMore" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "listCollections pins the cursor to a connection", + "operations": [ + { + "name": "listCollections", + "object": "database0", + "arguments": { + "filter": {}, + "batchSize": 2 + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1, + "cursor": { + "batchSize": 2 + } + }, + "commandName": "listCollections", + "databaseName": "database0Name" + } + }, + { + "commandSucceededEvent": { + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": "long" + }, + "collection": { + "$$type": "string" + } + }, + "commandName": "getMore" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": 0, + "ns": { + "$$type": "string" + }, + "nextBatch": { + "$$type": "array" + } + } + }, + "commandName": "getMore" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "listIndexes pins the cursor to a connection", + "operations": [ + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "x": 1 + }, + "name": "x_1" + } + }, + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "y": 1 + }, + "name": "y_1" + } + }, + { + "name": "listIndexes", + "object": "collection0", + "arguments": { + "batchSize": 2 + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createIndexes": "coll0", + "indexes": [ + { + "name": "x_1", + "key": { + "x": 1 + } + } + ] + }, + "commandName": "createIndexes" + } + }, + { + "commandSucceededEvent": { + "commandName": "createIndexes" + } + }, + { + "commandStartedEvent": { + "command": { + "createIndexes": "coll0", + "indexes": [ + { + "name": "y_1", + "key": { + "y": 1 + } + } + ] + }, + "commandName": "createIndexes" + } + }, + { + "commandSucceededEvent": { + "commandName": "createIndexes" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll0", + "cursor": { + "batchSize": 2 + } + }, + "commandName": "listIndexes", + "databaseName": "database0Name" + } + }, + { + "commandSucceededEvent": { + "commandName": "listIndexes" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": "long" + }, + "collection": "coll0" + }, + "commandName": "getMore" + } + }, + { + "commandSucceededEvent": { + "reply": { + "cursor": { + "id": 0, + "ns": { + "$$type": "string" + }, + "nextBatch": { + "$$type": "array" + } + } + }, + "commandName": "getMore" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "change streams pin to a connection", + "operations": [ + { + "name": "createChangeStream", + "object": "collection0", + "arguments": { + "pipeline": [] + }, + "saveResultAsEntity": "changeStream0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "close", + "object": "changeStream0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate" + } + }, + { + "commandSucceededEvent": { + "commandName": "aggregate" + } + }, + { + "commandStartedEvent": { + "commandName": "killCursors" + } + }, + { + "commandSucceededEvent": { + "commandName": "killCursors" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + } + ] +} diff --git a/test/load_balancer/unified/event-monitoring.json b/test/load_balancer/unified/event-monitoring.json new file mode 100644 index 0000000000..938c70bf38 --- /dev/null +++ b/test/load_balancer/unified/event-monitoring.json @@ -0,0 +1,184 @@ +{ + "description": "monitoring events include correct fields", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent", + "poolClearedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "databaseName": "database0", + "collectionName": "coll0", + "documents": [] + } + ], + "tests": [ + { + "description": "command started and succeeded events include serviceId", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "hasServiceId": true + } + }, + { + "commandSucceededEvent": { + "commandName": "insert", + "hasServiceId": true + } + } + ] + } + ] + }, + { + "description": "command failed events include serviceId", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "hasServiceId": true + } + }, + { + "commandFailedEvent": { + "commandName": "find", + "hasServiceId": true + } + } + ] + } + ] + }, + { + "description": "poolClearedEvent events include serviceId", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": {} + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "find", + "hasServiceId": true + } + }, + { + "commandFailedEvent": { + "commandName": "find", + "hasServiceId": true + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "poolClearedEvent": { + "hasServiceId": true + } + } + ] + } + ] + } + ] +} diff --git a/test/load_balancer/unified/lb-connection-establishment.json b/test/load_balancer/unified/lb-connection-establishment.json new file mode 100644 index 0000000000..0eaadf30c2 --- /dev/null +++ b/test/load_balancer/unified/lb-connection-establishment.json @@ -0,0 +1,58 @@ +{ + "description": "connection establishment for load-balanced clusters", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "uriOptions": { + "loadBalanced": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + } + ], + "tests": [ + { + "description": "operations against load balancers fail if URI contains loadBalanced=false", + "skipReason": "servers have not implemented LB support yet so they will not fail the connection handshake in this case", + "operations": [ + { + "name": "runCommand", + "object": "database0", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ] + } + ] +} diff --git a/test/load_balancer/unified/non-lb-connection-establishment.json b/test/load_balancer/unified/non-lb-connection-establishment.json new file mode 100644 index 0000000000..6aaa7bdf98 --- /dev/null +++ b/test/load_balancer/unified/non-lb-connection-establishment.json @@ -0,0 +1,92 @@ +{ + "description": "connection establishment if loadBalanced is specified for non-load balanced clusters", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "single", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "lbTrueClient", + "useMultipleMongoses": false, + "uriOptions": { + "loadBalanced": true + } + } + }, + { + "database": { + "id": "lbTrueDatabase", + "client": "lbTrueClient", + "databaseName": "lbTrueDb" + } + }, + { + "client": { + "id": "lbFalseClient", + "uriOptions": { + "loadBalanced": false + } + } + }, + { + "database": { + "id": "lbFalseDatabase", + "client": "lbFalseClient", + "databaseName": "lbFalseDb" + } + } + ], + "_yamlAnchors": { + "runCommandArguments": [ + { + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + } + } + ] + }, + "tests": [ + { + "description": "operations against non-load balanced clusters fail if URI contains loadBalanced=true", + "operations": [ + { + "name": "runCommand", + "object": "lbTrueDatabase", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "errorContains": "Driver attempted to initialize in load balancing mode, but the server does not support this mode" + } + } + ] + }, + { + "description": "operations against non-load balanced clusters succeed if URI contains loadBalanced=false", + "operations": [ + { + "name": "runCommand", + "object": "lbFalseDatabase", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + } + } + ] + } + ] +} diff --git a/test/load_balancer/unified/sdam-error-handling.json b/test/load_balancer/unified/sdam-error-handling.json new file mode 100644 index 0000000000..63aabc04db --- /dev/null +++ b/test/load_balancer/unified/sdam-error-handling.json @@ -0,0 +1,508 @@ +{ + "description": "state change errors are correctly handled", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "_yamlAnchors": { + "observedEvents": [ + "connectionCreatedEvent", + "connectionReadyEvent", + "connectionCheckedOutEvent", + "connectionCheckOutFailedEvent", + "connectionCheckedInEvent", + "connectionClosedEvent", + "poolClearedEvent" + ] + }, + "createEntities": [ + { + "client": { + "id": "failPointClient", + "useMultipleMongoses": false + } + }, + { + "client": { + "id": "singleClient", + "useMultipleMongoses": false, + "uriOptions": { + "appname": "lbSDAMErrorTestClient", + "retryWrites": false + }, + "observeEvents": [ + "connectionCreatedEvent", + "connectionReadyEvent", + "connectionCheckedOutEvent", + "connectionCheckOutFailedEvent", + "connectionCheckedInEvent", + "connectionClosedEvent", + "poolClearedEvent" + ] + } + }, + { + "database": { + "id": "singleDB", + "client": "singleClient", + "databaseName": "singleDB" + } + }, + { + "collection": { + "id": "singleColl", + "database": "singleDB", + "collectionName": "singleColl" + } + }, + { + "client": { + "id": "multiClient", + "useMultipleMongoses": true, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "connectionCreatedEvent", + "connectionReadyEvent", + "connectionCheckedOutEvent", + "connectionCheckOutFailedEvent", + "connectionCheckedInEvent", + "connectionClosedEvent", + "poolClearedEvent" + ] + } + }, + { + "database": { + "id": "multiDB", + "client": "multiClient", + "databaseName": "multiDB" + } + }, + { + "collection": { + "id": "multiColl", + "database": "multiDB", + "collectionName": "multiColl" + } + } + ], + "initialData": [ + { + "collectionName": "singleColl", + "databaseName": "singleDB", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + }, + { + "collectionName": "multiColl", + "databaseName": "multiDB", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "tests": [ + { + "description": "only connections for a specific serviceId are closed when pools are cleared", + "operations": [ + { + "name": "createFindCursor", + "object": "multiColl", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "createFindCursor", + "object": "multiColl", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor1" + }, + { + "name": "close", + "object": "cursor0" + }, + { + "name": "close", + "object": "cursor1" + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "multiClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11600 + } + } + } + }, + { + "name": "insertOne", + "object": "multiColl", + "arguments": { + "document": { + "x": 1 + } + }, + "expectError": { + "errorCode": 11600 + } + }, + { + "name": "insertOne", + "object": "multiColl", + "arguments": { + "document": { + "x": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "multiClient", + "eventType": "cmap", + "events": [ + { + "connectionCreatedEvent": {} + }, + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCreatedEvent": {} + }, + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "poolClearedEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": { + "reason": "stale" + } + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "errors during the initial connection hello are ignore", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "isMaster" + ], + "closeConnection": true, + "appName": "lbSDAMErrorTestClient" + } + } + } + }, + { + "name": "insertOne", + "object": "singleColl", + "arguments": { + "document": { + "x": 1 + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "singleClient", + "eventType": "cmap", + "events": [ + { + "connectionCreatedEvent": {} + }, + { + "connectionClosedEvent": { + "reason": "error" + } + }, + { + "connectionCheckOutFailedEvent": { + "reason": "connectionError" + } + } + ] + } + ] + }, + { + "description": "errors during authentication are processed", + "runOnRequirements": [ + { + "auth": true + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "saslContinue" + ], + "closeConnection": true, + "appName": "lbSDAMErrorTestClient" + } + } + } + }, + { + "name": "insertOne", + "object": "singleColl", + "arguments": { + "document": { + "x": 1 + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "singleClient", + "eventType": "cmap", + "events": [ + { + "connectionCreatedEvent": {} + }, + { + "poolClearedEvent": {} + }, + { + "connectionClosedEvent": { + "reason": "error" + } + }, + { + "connectionCheckOutFailedEvent": { + "reason": "connectionError" + } + } + ] + } + ] + }, + { + "description": "stale errors are ignored", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "getMore" + ], + "closeConnection": true + } + } + } + }, + { + "name": "createFindCursor", + "object": "singleColl", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "createFindCursor", + "object": "singleColl", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor1" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor0", + "expectError": { + "isClientError": true + } + }, + { + "name": "close", + "object": "cursor0" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor1" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor1" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor1", + "expectError": { + "isClientError": true + } + }, + { + "name": "close", + "object": "cursor1" + } + ], + "expectEvents": [ + { + "client": "singleClient", + "eventType": "cmap", + "events": [ + { + "connectionCreatedEvent": {} + }, + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCreatedEvent": {} + }, + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "poolClearedEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/test/load_balancer/unified/server-selection.json b/test/load_balancer/unified/server-selection.json new file mode 100644 index 0000000000..00c7e4c95b --- /dev/null +++ b/test/load_balancer/unified/server-selection.json @@ -0,0 +1,82 @@ +{ + "description": "server selection for load-balanced clusters", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0", + "collectionOptions": { + "readPreference": { + "mode": "secondaryPreferred" + } + } + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [] + } + ], + "tests": [ + { + "description": "$readPreference is sent for load-balanced clusters", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": {}, + "$readPreference": { + "mode": "secondaryPreferred" + } + }, + "commandName": "find", + "databaseName": "database0Name" + } + } + ] + } + ] + } + ] +} diff --git a/test/load_balancer/unified/transactions.json b/test/load_balancer/unified/transactions.json new file mode 100644 index 0000000000..add2453848 --- /dev/null +++ b/test/load_balancer/unified/transactions.json @@ -0,0 +1,1606 @@ +{ + "description": "transactions are correctly pinned to connections for load-balanced clusters", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent", + "connectionReadyEvent", + "connectionClosedEvent", + "connectionCheckedOutEvent", + "connectionCheckedInEvent" + ] + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "_yamlAnchors": { + "documents": [ + { + "_id": 4 + } + ] + }, + "tests": [ + { + "description": "sessions are reused in LB mode", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + } + } + }, + { + "name": "assertSameLsidOnLastTwoCommands", + "object": "testRunner", + "arguments": { + "client": "client0" + } + } + ] + }, + { + "description": "all operations go to the same mongos", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "commitTransaction", + "object": "session0" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + } + ] + } + ] + }, + { + "description": "transaction can be committed multiple times", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is not released after a non-transient CRUD error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 51 + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + }, + "expectError": { + "errorCode": 51, + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is not released after a non-transient commit error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 51 + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "commitTransaction", + "object": "session0", + "expectError": { + "errorCode": 51, + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is released after a non-transient abort error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 51 + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "abortTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is released after a transient non-network CRUD error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 24 + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + }, + "expectError": { + "errorCode": 24, + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + }, + { + "name": "abortTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is released after a transient network CRUD error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + }, + "expectError": { + "isClientError": true, + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + }, + { + "name": "abortTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": { + "reason": "error" + } + }, + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is released after a transient non-network commit error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 24 + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "commitTransaction", + "object": "session0", + "expectError": { + "errorCode": 24, + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is released after a transient network commit error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "commitTransaction", + "object": "session0", + "ignoreResultAndError": true + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": { + "reason": "error" + } + }, + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is released after a transient non-network abort error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 24 + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "abortTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is released after a transient network abort error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "abortTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": { + "reason": "error" + } + }, + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is released on successful abort", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "abortTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is returned when a new transaction is started", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "commitTransaction", + "object": "session0" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + } + ] + } + ] + }, + { + "description": "pinned connection is returned when a non-transaction operation uses the session", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction" + } + }, + { + "commandStartedEvent": { + "commandName": "insert" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "a connection can be shared by a transaction and a cursor", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2, + "session": "session0" + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "close", + "object": "cursor0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 1 + } + }, + { + "name": "abortTransaction", + "object": "session0" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client0", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "killCursors" + } + }, + { + "commandStartedEvent": { + "commandName": "abortTransaction" + } + } + ] + }, + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + } + ] +} diff --git a/test/load_balancer/unified/wait-queue-timeouts.json b/test/load_balancer/unified/wait-queue-timeouts.json new file mode 100644 index 0000000000..61575d6706 --- /dev/null +++ b/test/load_balancer/unified/wait-queue-timeouts.json @@ -0,0 +1,153 @@ +{ + "description": "wait queue timeout errors include details about checked out connections", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "uriOptions": { + "maxPoolSize": 1, + "waitQueueTimeoutMS": 5 + }, + "observeEvents": [ + "connectionCheckedOutEvent", + "connectionCheckOutFailedEvent" + ] + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "tests": [ + { + "description": "wait queue timeout errors include cursor statistics", + "operations": [ + { + "name": "createFindCursor", + "object": "collection0", + "arguments": { + "filter": {}, + "batchSize": 2 + }, + "saveResultAsEntity": "cursor0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + } + }, + "expectError": { + "isClientError": true, + "errorContains": "maxPoolSize: 1, connections in use by cursors: 1, connections in use by transactions: 0, connections in use by other operations: 0" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckOutFailedEvent": {} + } + ] + } + ] + }, + { + "description": "wait queue timeout errors include transaction statistics", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + } + }, + "expectError": { + "isClientError": true, + "errorContains": "maxPoolSize: 1, connections in use by cursors: 0, connections in use by transactions: 1, connections in use by other operations: 0" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckOutFailedEvent": {} + } + ] + } + ] + } + ] +} From e3a8c88c723758af7ab4fa8ccc5842021f701b1a Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Tue, 18 May 2021 17:51:05 -0700 Subject: [PATCH 03/14] PYTHON-2676 Add assertNumberConnectionsCheckedOut, createFindCursor, ignoreResultAndError --- test/unified_format.py | 77 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/test/unified_format.py b/test/unified_format.py index ad1e73a519..afefcb03fb 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -49,9 +49,10 @@ from pymongo.server_api import ServerApi from pymongo.write_concern import WriteConcern -from test import client_context, unittest, IntegrationTest +from test import client_context, unittest, IntegrationTest, MULTI_MONGOS_LB_URI from test.utils import ( - camel_to_snake, rs_or_single_client, single_client, snake_to_camel) + camel_to_snake, get_pool, rs_or_single_client, single_client, + snake_to_camel) from test.version import Version from test.utils import ( @@ -142,6 +143,24 @@ def parse_bulk_write_error_result(error): return parse_bulk_write_result(write_result) +class NonLazyCursor(object): + """A find cursor proxy that creates the remote cursor when initialized.""" + def __init__(self, find_cursor): + self.find_cursor = find_cursor + # Create the server side cursor. + self.first_result = next(find_cursor, None) + + def __next__(self): + if self.first_result is not None: + first = self.first_result + self.first_result = None + return first + return next(self.find_cursor) + + def close(self): + self.find_cursor.close() + + class EventListenerUtil(CommandListener): def __init__(self, observe_events, ignore_commands): self._event_types = set(observe_events) @@ -149,10 +168,14 @@ def __init__(self, observe_events, ignore_commands): self._ignore_commands.add('configurefailpoint') self.results = [] + def _add_event(self, event): + self.results.append(event) + def _observe_event(self, event): if event.command_name.lower() not in self._ignore_commands: - self.results.append(event) + self._add_event(event) + # TODO: Support CMAP events here? def started(self, event): if 'commandStartedEvent' in self._event_types: self._observe_event(event) @@ -208,8 +231,11 @@ def _create_entity(self, entity_spec): listener = EventListenerUtil(observe_events, ignore_commands) self._listeners[spec['id']] = listener kwargs['event_listeners'] = [listener] - if client_context.is_mongos and spec.get('useMultipleMongoses'): - kwargs['h'] = client_context.mongos_seeds() + if spec.get('useMultipleMongoses'): + if client_context.load_balancer: + kwargs['h'] = MULTI_MONGOS_LB_URI + elif client_context.is_mongos: + kwargs['h'] = client_context.mongos_seeds() kwargs.update(spec.get('uriOptions', {})) server_api = spec.get('serverApi') if server_api: @@ -699,6 +725,10 @@ def __entityOperation_aggregate(self, target, *args, **kwargs): def _databaseOperation_aggregate(self, target, *args, **kwargs): return self.__entityOperation_aggregate(target, *args, **kwargs) + def _databaseOperation_listCollections(self, target, *args, **kwargs): + cursor = target.list_collections(*args, **kwargs) + return list(cursor) + def _collectionOperation_aggregate(self, target, *args, **kwargs): return self.__entityOperation_aggregate(target, *args, **kwargs) @@ -707,6 +737,10 @@ def _collectionOperation_find(self, target, *args, **kwargs): find_cursor = target.find(*args, **kwargs) return list(find_cursor) + def _collectionOperation_createFindCursor(self, target, *args, **kwargs): + self.__raise_if_unsupported('find', target, Collection) + return NonLazyCursor(target.find(*args, **kwargs)) + def _sessionOperation_withTransaction(self, target, *args, **kwargs): if client_context.storage_engine == 'mmapv1': self.skipTest('MMAPv1 does not support document-level locking') @@ -725,11 +759,27 @@ def _changeStreamOperation_iterateUntilDocumentOrError(self, target, 'iterateUntilDocumentOrError', target, ChangeStream) return next(target) + def _cursor_iterateUntilDocumentOrError(self, target, *args, **kwargs): + self.__raise_if_unsupported( + 'iterateUntilDocumentOrError', target, NonLazyCursor) + return next(target) + + def _cursor_close(self, target, *args, **kwargs): + self.__raise_if_unsupported('close', target, NonLazyCursor) + return target.close() + def run_entity_operation(self, spec): target = self.entity_map[spec['object']] opname = spec['name'] opargs = spec.get('arguments') expect_error = spec.get('expectError') + save_as_entity = spec.get('saveResultAsEntity') + expect_result = spec.get('expectResult') + ignore = spec.get('ignoreResultAndError') + if ignore and (expect_error or save_as_entity or expect_result): + raise ValueError( + 'ignoreResultAndError is incompatible with saveResultAsEntity' + ', expectError, and expectResult') if opargs: arguments = parse_spec_options(copy.deepcopy(opargs)) prepare_spec_arguments(spec, arguments, camel_to_snake(opname), @@ -745,6 +795,8 @@ def run_entity_operation(self, spec): method_name = '_collectionOperation_%s' % (opname,) elif isinstance(target, ChangeStream): method_name = '_changeStreamOperation_%s' % (opname,) + elif isinstance(target, NonLazyCursor): + method_name = '_cursor_%s' % (opname,) elif isinstance(target, ClientSession): method_name = '_sessionOperation_%s' % (opname,) elif isinstance(target, GridFSBucket): @@ -766,15 +818,16 @@ def run_entity_operation(self, spec): try: result = cmd(**dict(arguments)) except Exception as exc: + if ignore: + return if expect_error: return self.process_error(exc, expect_error) raise - if 'expectResult' in spec: + if expect_result: actual = coerce_result(opname, result) - self.match_evaluator.match_result(spec['expectResult'], actual) + self.match_evaluator.match_result(expect_result, actual) - save_as_entity = spec.get('saveResultAsEntity') if save_as_entity: self.entity_map[save_as_entity] = result @@ -869,6 +922,11 @@ def _testOperation_assertIndexNotExists(self, spec): for index in collection.list_indexes(): self.assertNotEqual(spec['indexName'], index['name']) + def _testOperation_assertNumberConnectionsCheckedOut(self, spec): + client = self.entity_map[spec['client']] + pool = get_pool(client) + self.assertEqual(spec['connections'], pool.active_sockets) + def run_special_operation(self, spec): opname = spec['name'] method_name = '_testOperation_%s' % (opname,) @@ -891,6 +949,9 @@ def check_events(self, spec): for event_spec in spec: client_name = event_spec['client'] events = event_spec['events'] + # TODO: CMAP event matching + # Valid types: 'command', 'cmap' + event_type = event_spec.get('eventType', 'command') listener = self.entity_map.get_listener_for_client(client_name) if len(events) == 0: From f557b6c2a91b67c4424718313cde1dc27d3330d7 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Tue, 18 May 2021 18:07:02 -0700 Subject: [PATCH 04/14] PYTHON-2676 Pin to load balancer server address --- pymongo/client_session.py | 10 +++++----- pymongo/mongo_client.py | 13 +++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pymongo/client_session.py b/pymongo/client_session.py index 950fe0dc13..1bd1ddb883 100644 --- a/pymongo/client_session.py +++ b/pymongo/client_session.py @@ -660,7 +660,7 @@ def abort_transaction(self): pass finally: self._transaction.state = _TxnState.ABORTED - self._unpin_mongos() + self._unpin() def _finish_transaction_with_retry(self, command_name): """Run commit or abort with one retry after any retryable error. @@ -779,13 +779,13 @@ def _pinned_address(self): return self._transaction.pinned_address return None - def _pin_mongos(self, server): - """Pin this session to the given mongos Server.""" + def _pin(self, server): + """Pin this session to the given Server.""" self._transaction.sharded = True self._transaction.pinned_address = server.description.address - def _unpin_mongos(self): - """Unpin this session from any pinned mongos address.""" + def _unpin(self): + """Unpin this session from any pinned Server.""" self._transaction.pinned_address = None def _txn_read_preference(self): diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index ebd11970a2..11515cf474 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -1197,15 +1197,16 @@ def _select_server(self, server_selector, session, address=None): server = topology.select_server(server_selector) # Pin this session to the selected server if it's performing a # sharded transaction. - if server.description.mongos and (session and - session.in_transaction): - session._pin_mongos(server) + if (server.description.server_type in ( + SERVER_TYPE.Mongos, SERVER_TYPE.LoadBalancer) + and session and session.in_transaction): + session._pin(server) return server except PyMongoError as exc: # Server selection errors in a transaction are transient. if session and session.in_transaction: exc._add_error_label("TransientTransactionError") - session._unpin_mongos() + session._unpin() raise def _socket_for_writes(self, session): @@ -1350,7 +1351,7 @@ def is_retrying(): _add_retryable_write_error(exc, max_wire_version) retryable_error = exc.has_error_label("RetryableWriteError") if retryable_error: - session._unpin_mongos() + session._unpin() if is_retrying() or not retryable_error: raise if bulk: @@ -2001,7 +2002,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): if issubclass(exc_type, PyMongoError): if (exc_val.has_error_label("TransientTransactionError") or exc_val.has_error_label("RetryableWriteError")): - self.session._unpin_mongos() + self.session._unpin() err_ctx = _ErrorContext( exc_val, self.max_wire_version, self.sock_generation, From 5e7aaef9fd62b0be8df24038009afa1d360fd9c6 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Tue, 18 May 2021 18:11:51 -0700 Subject: [PATCH 05/14] PYTHON-2676 Ensure LB supports retryable reads/writes --- pymongo/server_description.py | 6 +++--- pymongo/topology.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pymongo/server_description.py b/pymongo/server_description.py index 5dc8222fef..19cc349c78 100644 --- a/pymongo/server_description.py +++ b/pymongo/server_description.py @@ -204,10 +204,10 @@ def is_server_type_known(self): @property def retryable_writes_supported(self): """Checks if this server supports retryable writes.""" - return ( + return (( self._ls_timeout_minutes is not None and - self._server_type in (SERVER_TYPE.Mongos, SERVER_TYPE.RSPrimary, - SERVER_TYPE.LoadBalancer)) + self._server_type in (SERVER_TYPE.Mongos, SERVER_TYPE.RSPrimary)) + or self._server_type == SERVER_TYPE.LoadBalancer) @property def retryable_reads_supported(self): diff --git a/pymongo/topology.py b/pymongo/topology.py index 446bb9353d..8a60bb5819 100644 --- a/pymongo/topology.py +++ b/pymongo/topology.py @@ -566,7 +566,8 @@ def _ensure_opened(self): # Emit initial SDAM events for load balancer mode. self._process_change(ServerDescription( self._seed_addresses[0], - IsMaster({'ok': 1, 'serviceId': self._topology_id}))) + IsMaster({'ok': 1, 'serviceId': self._topology_id, + 'maxWireVersion': 13}))) # Ensure that the monitors are open. for server in self._servers.values(): From a51b4e621e17b129cbe232dd0c3e3f8ea9172e35 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Wed, 19 May 2021 09:05:51 -0700 Subject: [PATCH 06/14] PYTHON-2676 Support useMultipleMongoses for LB --- test/__init__.py | 1 + test/unified_format.py | 20 ++++++++++++++------ test/utils_spec_runner.py | 19 ++++++++++--------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/test/__init__.py b/test/__init__.py index 4b03a2f572..af656fb704 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -199,6 +199,7 @@ def _all_users(db): class ClientContext(object): + MULTI_MONGOS_LB_URI = MULTI_MONGOS_LB_URI def __init__(self): """Create a client and grab essential information from the server.""" diff --git a/test/unified_format.py b/test/unified_format.py index afefcb03fb..bb7b1e1554 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -49,7 +49,7 @@ from pymongo.server_api import ServerApi from pymongo.write_concern import WriteConcern -from test import client_context, unittest, IntegrationTest, MULTI_MONGOS_LB_URI +from test import client_context, unittest, IntegrationTest from test.utils import ( camel_to_snake, get_pool, rs_or_single_client, single_client, snake_to_camel) @@ -233,7 +233,7 @@ def _create_entity(self, entity_spec): kwargs['event_listeners'] = [listener] if spec.get('useMultipleMongoses'): if client_context.load_balancer: - kwargs['h'] = MULTI_MONGOS_LB_URI + kwargs['h'] = client_context.MULTI_MONGOS_LB_URI elif client_context.is_mongos: kwargs['h'] = client_context.mongos_seeds() kwargs.update(spec.get('uriOptions', {})) @@ -718,6 +718,12 @@ def _databaseOperation_runCommand(self, target, **kwargs): kwargs['command'] = ordered_command return target.command(**kwargs) + def _databaseOperation_listCollections(self, target, *args, **kwargs): + if 'batch_size' in kwargs: + kwargs['cursor'] = {'batchSize': kwargs.pop('batch_size')} + cursor = target.list_collections(*args, **kwargs) + return list(cursor) + def __entityOperation_aggregate(self, target, *args, **kwargs): self.__raise_if_unsupported('aggregate', target, Database, Collection) return list(target.aggregate(*args, **kwargs)) @@ -725,10 +731,6 @@ def __entityOperation_aggregate(self, target, *args, **kwargs): def _databaseOperation_aggregate(self, target, *args, **kwargs): return self.__entityOperation_aggregate(target, *args, **kwargs) - def _databaseOperation_listCollections(self, target, *args, **kwargs): - cursor = target.list_collections(*args, **kwargs) - return list(cursor) - def _collectionOperation_aggregate(self, target, *args, **kwargs): return self.__entityOperation_aggregate(target, *args, **kwargs) @@ -741,6 +743,12 @@ def _collectionOperation_createFindCursor(self, target, *args, **kwargs): self.__raise_if_unsupported('find', target, Collection) return NonLazyCursor(target.find(*args, **kwargs)) + def _collectionOperation_listIndexes(self, target, *args, **kwargs): + if 'batch_size' in kwargs: + self.skipTest('PyMongo does not support batch_size for ' + 'list_indexes') + return target.list_indexes(*args, **kwargs) + def _sessionOperation_withTransaction(self, target, *args, **kwargs): if client_context.storage_engine == 'mmapv1': self.skipTest('MMAPv1 does not support document-level locking') diff --git a/test/utils_spec_runner.py b/test/utils_spec_runner.py index 17175884da..5f79789ec8 100644 --- a/test/utils_spec_runner.py +++ b/test/utils_spec_runner.py @@ -504,15 +504,16 @@ def run_scenario(self, scenario_def, test): client_context.storage_engine == 'mmapv1'): self.skipTest("MMAPv1 does not support retryWrites=True") use_multi_mongos = test['useMultipleMongoses'] - if client_context.is_mongos and use_multi_mongos: - client = rs_client( - client_context.mongos_seeds(), - event_listeners=[listener, pool_listener, server_listener], - **client_options) - else: - client = rs_client( - event_listeners=[listener, pool_listener, server_listener], - **client_options) + host = None + if use_multi_mongos: + if client_context.load_balancer: + host = client_context.MULTI_MONGOS_LB_URI + elif client_context.is_mongos: + host = client_context.mongos_seeds() + client = rs_client( + h=host, + event_listeners=[listener, pool_listener, server_listener], + **client_options) self.scenario_client = client self.listener = listener self.pool_listener = pool_listener From d0b8f4afd7ff1797eaae62f5476189f3a2e3f375 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Wed, 19 May 2021 12:23:49 -0700 Subject: [PATCH 07/14] PYTHON-2676 Support CMAP events --- test/unified_format.py | 180 ++++++++++++++++++++++++----------------- 1 file changed, 108 insertions(+), 72 deletions(-) diff --git a/test/unified_format.py b/test/unified_format.py index bb7b1e1554..767809b037 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -42,7 +42,11 @@ from pymongo.errors import BulkWriteError, InvalidOperation, PyMongoError from pymongo.monitoring import ( CommandFailedEvent, CommandListener, CommandStartedEvent, - CommandSucceededEvent, _SENSITIVE_COMMANDS) + CommandSucceededEvent, _SENSITIVE_COMMANDS, PoolCreatedEvent, + PoolReadyEvent, PoolClearedEvent, PoolClosedEvent, ConnectionCreatedEvent, + ConnectionReadyEvent, ConnectionClosedEvent, + ConnectionCheckOutStartedEvent, ConnectionCheckOutFailedEvent, + ConnectionCheckedOutEvent, ConnectionCheckedInEvent) from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference from pymongo.results import BulkWriteResult @@ -52,7 +56,7 @@ from test import client_context, unittest, IntegrationTest from test.utils import ( camel_to_snake, get_pool, rs_or_single_client, single_client, - snake_to_camel) + snake_to_camel, CMAPListener) from test.version import Version from test.utils import ( @@ -161,32 +165,34 @@ def close(self): self.find_cursor.close() -class EventListenerUtil(CommandListener): +class EventListenerUtil(CMAPListener, CommandListener): def __init__(self, observe_events, ignore_commands): - self._event_types = set(observe_events) + self._event_types = set(name.lower() for name in observe_events) self._ignore_commands = _SENSITIVE_COMMANDS | set(ignore_commands) self._ignore_commands.add('configurefailpoint') - self.results = [] + super(EventListenerUtil, self).__init__() - def _add_event(self, event): - self.results.append(event) + def get_events(self, event_type): + if event_type == 'command': + return [e for e in self.events if 'Command' in type(e).__name__] + return [e for e in self.events if 'Command' not in type(e).__name__] - def _observe_event(self, event): + def add_event(self, event): + if type(event).__name__.lower() in self._event_types: + super(EventListenerUtil, self).add_event(event) + + def _command_event(self, event): if event.command_name.lower() not in self._ignore_commands: - self._add_event(event) + self.add_event(event) - # TODO: Support CMAP events here? def started(self, event): - if 'commandStartedEvent' in self._event_types: - self._observe_event(event) + self._command_event(event) def succeeded(self, event): - if 'commandSucceededEvent' in self._event_types: - self._observe_event(event) + self._command_event(event) def failed(self, event): - if 'commandFailedEvent' in self._event_types: - self._observe_event(event) + self._command_event(event) class EntityMapUtil(object): @@ -196,28 +202,28 @@ def __init__(self, test_class): self._entities = {} self._listeners = {} self._session_lsids = {} - self._test_class = test_class + self.test = test_class def __getitem__(self, item): try: return self._entities[item] except KeyError: - self._test_class.fail('Could not find entity named %s in map' % ( + self.test.fail('Could not find entity named %s in map' % ( item,)) def __setitem__(self, key, value): if not isinstance(key, str): - self._test_class.fail( + self.test.fail( 'Expected entity name of type str, got %s' % (type(key))) if key in self._entities: - self._test_class.fail('Entity named %s already in map' % (key,)) + self.test.fail('Entity named %s already in map' % (key,)) self._entities[key] = value def _create_entity(self, entity_spec): if len(entity_spec) != 1: - self._test_class.fail( + self.test.fail( "Entity spec %s did not contain exactly one top-level key" % ( entity_spec,)) @@ -226,6 +232,7 @@ def _create_entity(self, entity_spec): kwargs = {} observe_events = spec.get('observeEvents', []) ignore_commands = spec.get('ignoreCommandMonitoringEvents', []) + # TODO: SUPPORT storeEventsAsEntities if len(observe_events) or len(ignore_commands): ignore_commands = [cmd.lower() for cmd in ignore_commands] listener = EventListenerUtil(observe_events, ignore_commands) @@ -244,12 +251,12 @@ def _create_entity(self, entity_spec): deprecation_errors=server_api.get('deprecationErrors')) client = rs_or_single_client(**kwargs) self[spec['id']] = client - self._test_class.addCleanup(client.close) + self.test.addCleanup(client.close) return elif entity_type == 'database': client = self[spec['client']] if not isinstance(client, MongoClient): - self._test_class.fail( + self.test.fail( 'Expected entity %s to be of type MongoClient, got %s' % ( spec['client'], type(client))) options = parse_collection_or_database_options( @@ -260,7 +267,7 @@ def _create_entity(self, entity_spec): elif entity_type == 'collection': database = self[spec['database']] if not isinstance(database, Database): - self._test_class.fail( + self.test.fail( 'Expected entity %s to be of type Database, got %s' % ( spec['database'], type(database))) options = parse_collection_or_database_options( @@ -271,7 +278,7 @@ def _create_entity(self, entity_spec): elif entity_type == 'session': client = self[spec['client']] if not isinstance(client, MongoClient): - self._test_class.fail( + self.test.fail( 'Expected entity %s to be of type MongoClient, got %s' % ( spec['client'], type(client))) opts = camel_to_snake_args(spec.get('sessionOptions', {})) @@ -284,13 +291,13 @@ def _create_entity(self, entity_spec): session = client.start_session(**dict(opts)) self[spec['id']] = session self._session_lsids[spec['id']] = copy.deepcopy(session.session_id) - self._test_class.addCleanup(session.end_session) + self.test.addCleanup(session.end_session) return elif entity_type == 'bucket': # TODO: implement the 'bucket' entity type - self._test_class.skipTest( + self.test.skipTest( 'GridFS is not currently supported (PYTHON-2459)') - self._test_class.fail( + self.test.fail( 'Unable to create entity of unknown type %s' % (entity_type,)) def create_entities_from_spec(self, entity_spec): @@ -300,13 +307,13 @@ def create_entities_from_spec(self, entity_spec): def get_listener_for_client(self, client_name): client = self[client_name] if not isinstance(client, MongoClient): - self._test_class.fail( + self.test.fail( 'Expected entity %s to be of type MongoClient, got %s' % ( client_name, type(client))) listener = self._listeners.get(client_name) if not listener: - self._test_class.fail( + self.test.fail( 'No listeners configured for client %s' % (client_name,)) return listener @@ -314,7 +321,7 @@ def get_listener_for_client(self, client_name): def get_lsid_for_session(self, session_name): session = self[session_name] if not isinstance(session, ClientSession): - self._test_class.fail( + self.test.fail( 'Expected entity %s to be of type ClientSession, got %s' % ( session_name, type(session))) @@ -360,21 +367,21 @@ class MatchEvaluatorUtil(object): """Utility class that implements methods for evaluating matches as per the unified test format specification.""" def __init__(self, test_class): - self._test_class = test_class + self.test = test_class def _operation_exists(self, spec, actual, key_to_compare): if spec is True: - self._test_class.assertIn(key_to_compare, actual) + self.test.assertIn(key_to_compare, actual) elif spec is False: - self._test_class.assertNotIn(key_to_compare, actual) + self.test.assertNotIn(key_to_compare, actual) else: - self._test_class.fail( + self.test.fail( 'Expected boolean value for $$exists operator, got %s' % ( spec,)) def __type_alias_to_type(self, alias): if alias not in BSON_TYPE_ALIAS_MAP: - self._test_class.fail('Unrecognized BSON type alias %s' % (alias,)) + self.test.fail('Unrecognized BSON type alias %s' % (alias,)) return BSON_TYPE_ALIAS_MAP[alias] def _operation_type(self, spec, actual, key_to_compare): @@ -383,13 +390,13 @@ def _operation_type(self, spec, actual, key_to_compare): t for alias in spec for t in self.__type_alias_to_type(alias)]) else: permissible_types = self.__type_alias_to_type(spec) - self._test_class.assertIsInstance( + self.test.assertIsInstance( actual[key_to_compare], permissible_types) def _operation_matchesEntity(self, spec, actual, key_to_compare): - expected_entity = self._test_class.entity_map[spec] - self._test_class.assertIsInstance(expected_entity, abc.Mapping) - self._test_class.assertEqual(expected_entity, actual[key_to_compare]) + expected_entity = self.test.entity_map[spec] + self.test.assertIsInstance(expected_entity, abc.Mapping) + self.test.assertEqual(expected_entity, actual[key_to_compare]) def _operation_matchesHexBytes(self, spec, actual, key_to_compare): raise NotImplementedError @@ -406,8 +413,8 @@ def _operation_unsetOrMatches(self, spec, actual, key_to_compare): self.match_result(spec, actual[key_to_compare], in_recursive_call=True) def _operation_sessionLsid(self, spec, actual, key_to_compare): - expected_lsid = self._test_class.entity_map.get_lsid_for_session(spec) - self._test_class.assertEqual(expected_lsid, actual[key_to_compare]) + expected_lsid = self.test.entity_map.get_lsid_for_session(spec) + self.test.assertEqual(expected_lsid, actual[key_to_compare]) def _evaluate_special_operation(self, opname, spec, actual, key_to_compare): @@ -415,7 +422,7 @@ def _evaluate_special_operation(self, opname, spec, actual, try: method = getattr(self, method_name) except AttributeError: - self._test_class.fail( + self.test.fail( 'Unsupported special matching operator %s' % (opname,)) else: method(spec, actual, key_to_compare) @@ -466,16 +473,16 @@ def _match_document(self, expectation, actual, is_root): if self._evaluate_if_special_operation(expectation, actual): return - self._test_class.assertIsInstance(actual, abc.Mapping) + self.test.assertIsInstance(actual, abc.Mapping) for key, value in expectation.items(): if self._evaluate_if_special_operation(expectation, actual, key): continue - self._test_class.assertIn(key, actual) + self.test.assertIn(key, actual) self.match_result(value, actual[key], in_recursive_call=True) if not is_root: - self._test_class.assertEqual( + self.test.assertEqual( set(expectation.keys()), set(actual.keys())) def match_result(self, expectation, actual, @@ -485,7 +492,7 @@ def match_result(self, expectation, actual, expectation, actual, is_root=not in_recursive_call) if isinstance(expectation, abc.MutableSequence): - self._test_class.assertIsInstance(actual, abc.MutableSequence) + self.test.assertIsInstance(actual, abc.MutableSequence) for e, a in zip(expectation, actual): if isinstance(e, abc.Mapping): self._match_document( @@ -497,21 +504,22 @@ def match_result(self, expectation, actual, # account for flexible numerics in element-wise comparison if (isinstance(expectation, int) or isinstance(expectation, float)): - self._test_class.assertEqual(expectation, actual) + self.test.assertEqual(expectation, actual) else: - self._test_class.assertIsInstance(actual, type(expectation)) - self._test_class.assertEqual(expectation, actual) + self.test.assertIsInstance(actual, type(expectation)) + self.test.assertEqual(expectation, actual) - def match_event(self, expectation, actual): - event_type, spec = next(iter(expectation.items())) + def match_event(self, event_type, expectation, actual): + name, spec = next(iter(expectation.items())) - # every event type has the commandName field - command_name = spec.get('commandName') - if command_name: - self._test_class.assertEqual(command_name, actual.command_name) + # every command event has the commandName field + if event_type == 'command': + command_name = spec.get('commandName') + if command_name: + self.test.assertEqual(command_name, actual.command_name) - if event_type == 'commandStartedEvent': - self._test_class.assertIsInstance(actual, CommandStartedEvent) + if name == 'commandStartedEvent': + self.test.assertIsInstance(actual, CommandStartedEvent) command = spec.get('command') database_name = spec.get('databaseName') if command: @@ -523,18 +531,45 @@ def match_event(self, expectation, actual): update.setdefault('multi', False) self.match_result(command, actual.command) if database_name: - self._test_class.assertEqual( + self.test.assertEqual( database_name, actual.database_name) - elif event_type == 'commandSucceededEvent': - self._test_class.assertIsInstance(actual, CommandSucceededEvent) + elif name == 'commandSucceededEvent': + self.test.assertIsInstance(actual, CommandSucceededEvent) reply = spec.get('reply') if reply: self.match_result(reply, actual.reply) - elif event_type == 'commandFailedEvent': - self._test_class.assertIsInstance(actual, CommandFailedEvent) + elif name == 'commandFailedEvent': + self.test.assertIsInstance(actual, CommandFailedEvent) + elif name == 'poolCreatedEvent': + self.test.assertIsInstance(actual, PoolCreatedEvent) + elif name == 'poolReadyEvent': + self.test.assertIsInstance(actual, PoolReadyEvent) + elif name == 'poolClearedEvent': + self.test.assertIsInstance(actual, PoolClearedEvent) + if spec.get('hasServiceId'): + # TODO: Assert hasServiceId + self.test.assertTrue(actual.address) + elif name == 'poolClosedEvent': + self.test.assertIsInstance(actual, PoolClosedEvent) + elif name == 'connectionCreatedEvent': + self.test.assertIsInstance(actual, ConnectionCreatedEvent) + elif name == 'connectionReadyEvent': + self.test.assertIsInstance(actual, ConnectionReadyEvent) + elif name == 'connectionClosedEvent': + self.test.assertIsInstance(actual, ConnectionClosedEvent) + self.test.assertEqual(actual.reason, spec['reason']) + elif name == 'connectionCheckOutStartedEvent': + self.test.assertIsInstance(actual, ConnectionCheckOutStartedEvent) + elif name == 'connectionCheckOutFailedEvent': + self.test.assertIsInstance(actual, ConnectionCheckOutFailedEvent) + self.test.assertEqual(actual.reason, spec['reason']) + elif name == 'connectionCheckedOutEvent': + self.test.assertIsInstance(actual, ConnectionCheckedOutEvent) + elif name == 'connectionCheckedInEvent': + self.test.assertIsInstance(actual, ConnectionCheckedInEvent) else: - self._test_class.fail( - 'Unsupported event type %s' % (event_type,)) + self.test.fail( + 'Unsupported event type %s' % (name,)) def coerce_result(opname, result): @@ -882,7 +917,7 @@ def _testOperation_assertSessionUnpinned(self, spec): def __get_last_two_command_lsids(self, listener): cmd_started_events = [] - for event in reversed(listener.results): + for event in reversed(listener.events): if isinstance(event, CommandStartedEvent): cmd_started_events.append(event) if len(cmd_started_events) < 2: @@ -957,22 +992,23 @@ def check_events(self, spec): for event_spec in spec: client_name = event_spec['client'] events = event_spec['events'] - # TODO: CMAP event matching # Valid types: 'command', 'cmap' event_type = event_spec.get('eventType', 'command') - listener = self.entity_map.get_listener_for_client(client_name) + assert event_type in ('command', 'cmap') + listener = self.entity_map.get_listener_for_client(client_name) + actual_events = listener.get_events(event_type) if len(events) == 0: - self.assertEqual(listener.results, []) + self.assertEqual(actual_events, []) continue - if len(events) > len(listener.results): + if len(events) > len(actual_events): self.fail('Expected to see %s events, got %s' % ( - len(events), len(listener.results))) + len(events), len(actual_events))) for idx, expected_event in enumerate(events): self.match_evaluator.match_event( - expected_event, listener.results[idx]) + event_type, expected_event, actual_events[idx]) def verify_outcome(self, spec): for collection_data in spec: From cbce8288c9445efc010249daf71edb9b4615944a Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Wed, 19 May 2021 14:45:31 -0700 Subject: [PATCH 08/14] PYTHON-2676 Skip session expiration check for load balancers when checking into pool Fixes: FAIL: test_sessions_are_reused_in_LB_mode (test_load_balancer.TestUnifiedTransactions) ---------------------------------------------------------------------- Traceback (most recent call last): File "/data/mci/941f91603ef9b23e469965893d64cc4e/src/test/unified_format.py", line 1071, in test_case self.run_scenario(spec) File "/data/mci/941f91603ef9b23e469965893d64cc4e/src/test/unified_format.py", line 1055, in run_scenario self.run_operations(spec['operations']) File "/data/mci/941f91603ef9b23e469965893d64cc4e/src/test/unified_format.py", line 989, in run_operations self.run_special_operation(op) File "/data/mci/941f91603ef9b23e469965893d64cc4e/src/test/unified_format.py", line 981, in run_special_operation method(spec['arguments']) File "/data/mci/941f91603ef9b23e469965893d64cc4e/src/test/unified_format.py", line 934, in _testOperation_assertSameLsidOnLastTwoCommands self.assertEqual(*self.__get_last_two_command_lsids(listener)) AssertionError: {'id': Binary(b'\x06\xa6)\xfc\xe5QK/\x80"\xab\xc9]\xb2Tr', 4)} != {'id': Binary(b'\x9c\x86\x19/s\x96O\x85\xa6\x93k\x92d\xff{\x8b', 4)} - {'id': Binary(b'\x06\xa6)\xfc\xe5QK/\x80"\xab\xc9]\xb2Tr', 4)} + {'id': Binary(b'\x9c\x86\x19/s\x96O\x85\xa6\x93k\x92d\xff{\x8b', 4)} --- pymongo/client_session.py | 8 +++++--- pymongo/topology.py | 8 +++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pymongo/client_session.py b/pymongo/client_session.py index 1bd1ddb883..5b6ff7524d 100644 --- a/pymongo/client_session.py +++ b/pymongo/client_session.py @@ -906,9 +906,11 @@ def get_server_session(self, session_timeout_minutes): return _ServerSession(self.generation) def return_server_session(self, server_session, session_timeout_minutes): - self._clear_stale(session_timeout_minutes) - if not server_session.timed_out(session_timeout_minutes): - self.return_server_session_no_lock(server_session) + if session_timeout_minutes is not None: + self._clear_stale(session_timeout_minutes) + if server_session.timed_out(session_timeout_minutes): + return + self.return_server_session_no_lock(server_session) def return_server_session_no_lock(self, server_session): # Discard sessions from an old pool to avoid duplicate sessions in the diff --git a/pymongo/topology.py b/pymongo/topology.py index 8a60bb5819..ca5e1d973d 100644 --- a/pymongo/topology.py +++ b/pymongo/topology.py @@ -528,11 +528,9 @@ def get_server_session(self): def return_server_session(self, server_session, lock): if lock: with self._lock: - session_timeout = \ - self._description.logical_session_timeout_minutes - if session_timeout is not None: - self._session_pool.return_server_session(server_session, - session_timeout) + self._session_pool.return_server_session( + server_session, + self._description.logical_session_timeout_minutes) else: # Called from a __del__ method, can't use a lock. self._session_pool.return_server_session_no_lock(server_session) From a35b2aa9bfb89a5e45411e9089f2cb640e395d62 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Fri, 21 May 2021 14:25:27 -0700 Subject: [PATCH 09/14] PYTHON-2676 Add PoolClearedEvent.service_id and fix isClientError unified test assertion --- pymongo/mongo_client.py | 6 +- pymongo/monitoring.py | 79 ++++++++++++++++++++------- pymongo/pool.py | 9 +-- pymongo/server.py | 4 +- pymongo/topology.py | 12 ++-- test/test_client.py | 2 +- test/test_discovery_and_monitoring.py | 2 +- test/test_monitoring.py | 7 ++- test/test_topology.py | 4 +- test/unified_format.py | 16 ++++-- 10 files changed, 98 insertions(+), 43 deletions(-) diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index 11515cf474..728c9a1670 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -1966,7 +1966,7 @@ def _add_retryable_write_error(exc, max_wire_version): class _MongoClientErrorHandler(object): """Handle errors raised when executing an operation.""" __slots__ = ('client', 'server_address', 'session', 'max_wire_version', - 'sock_generation', 'completed_handshake') + 'sock_generation', 'completed_handshake', 'service_id') def __init__(self, client, server, session): self.client = client @@ -1979,11 +1979,13 @@ def __init__(self, client, server, session): # of the pool at the time the connection attempt was started." self.sock_generation = server.pool.generation self.completed_handshake = False + self.service_id = None def contribute_socket(self, sock_info): """Provide socket information to the error handler.""" self.max_wire_version = sock_info.max_wire_version self.sock_generation = sock_info.generation + self.service_id = sock_info.service_id self.completed_handshake = True def __enter__(self): @@ -2006,5 +2008,5 @@ def __exit__(self, exc_type, exc_val, exc_tb): err_ctx = _ErrorContext( exc_val, self.max_wire_version, self.sock_generation, - self.completed_handshake) + self.completed_handshake, self.service_id) self.client._topology.handle_error(self.server_address, err_ctx) diff --git a/pymongo/monitoring.py b/pymongo/monitoring.py index 7537765637..113cb1f696 100644 --- a/pymongo/monitoring.py +++ b/pymongo/monitoring.py @@ -512,13 +512,16 @@ def register(listener): class _CommandEvent(object): """Base class for command events.""" - __slots__ = ("__cmd_name", "__rqst_id", "__conn_id", "__op_id") + __slots__ = ("__cmd_name", "__rqst_id", "__conn_id", "__op_id", + "__service_id") - def __init__(self, command_name, request_id, connection_id, operation_id): + def __init__(self, command_name, request_id, connection_id, operation_id, + service_id=None): self.__cmd_name = command_name self.__rqst_id = request_id self.__conn_id = connection_id self.__op_id = operation_id + self.__service_id = service_id @property def command_name(self): @@ -535,6 +538,14 @@ def connection_id(self): """The address (host, port) of the server this command was sent to.""" return self.__conn_id + @property + def service_id(self): + """The service_id this command was sent to, or ``None``. + + .. versionadded:: 3.12 + """ + return self.__service_id + @property def operation_id(self): """An id for this series of events or None.""" @@ -551,15 +562,17 @@ class CommandStartedEvent(_CommandEvent): - `connection_id`: The address (host, port) of the server this command was sent to. - `operation_id`: An optional identifier for a series of related events. + - `service_id`: The service_id this command was sent to, or ``None``. """ __slots__ = ("__cmd", "__db") - def __init__(self, command, database_name, *args): + def __init__(self, command, database_name, *args, service_id=None): if not command: raise ValueError("%r is not a valid command" % (command,)) # Command name must be first key. command_name = next(iter(command)) - super(CommandStartedEvent, self).__init__(command_name, *args) + super(CommandStartedEvent, self).__init__( + command_name, *args, service_id=service_id) if command_name.lower() in _SENSITIVE_COMMANDS: self.__cmd = {} else: @@ -577,9 +590,12 @@ def database_name(self): return self.__db def __repr__(self): - return "<%s %s db: %r, command: %r, operation_id: %s>" % ( - self.__class__.__name__, self.connection_id, self.database_name, - self.command_name, self.operation_id) + return ( + "<%s %s db: %r, command: %r, operation_id: %s, " + "service_id: %s>") % ( + self.__class__.__name__, self.connection_id, + self.database_name, self.command_name, self.operation_id, + self.service_id) class CommandSucceededEvent(_CommandEvent): @@ -593,13 +609,15 @@ class CommandSucceededEvent(_CommandEvent): - `connection_id`: The address (host, port) of the server this command was sent to. - `operation_id`: An optional identifier for a series of related events. + - `service_id`: The service_id this command was sent to, or ``None``. """ __slots__ = ("__duration_micros", "__reply") def __init__(self, duration, reply, command_name, - request_id, connection_id, operation_id): + request_id, connection_id, operation_id, service_id=None): super(CommandSucceededEvent, self).__init__( - command_name, request_id, connection_id, operation_id) + command_name, request_id, connection_id, operation_id, + service_id=service_id) self.__duration_micros = _to_micros(duration) if command_name.lower() in _SENSITIVE_COMMANDS: self.__reply = {} @@ -617,9 +635,12 @@ def reply(self): return self.__reply def __repr__(self): - return "<%s %s command: %r, operation_id: %s, duration_micros: %s>" % ( - self.__class__.__name__, self.connection_id, - self.command_name, self.operation_id, self.duration_micros) + return ( + "<%s %s command: %r, operation_id: %s, duration_micros: %s, " + "service_id: %s>") % ( + self.__class__.__name__, self.connection_id, + self.command_name, self.operation_id, self.duration_micros, + self.service_id) class CommandFailedEvent(_CommandEvent): @@ -633,11 +654,12 @@ class CommandFailedEvent(_CommandEvent): - `connection_id`: The address (host, port) of the server this command was sent to. - `operation_id`: An optional identifier for a series of related events. + - `service_id`: The service_id this command was sent to, or ``None``. """ __slots__ = ("__duration_micros", "__failure") - def __init__(self, duration, failure, *args): - super(CommandFailedEvent, self).__init__(*args) + def __init__(self, duration, failure, *args, service_id=None): + super(CommandFailedEvent, self).__init__(*args, service_id=service_id) self.__duration_micros = _to_micros(duration) self.__failure = failure @@ -654,9 +676,10 @@ def failure(self): def __repr__(self): return ( "<%s %s command: %r, operation_id: %s, duration_micros: %s, " - "failure: %r>" % ( + "failure: %r, service_id: %s>") % ( self.__class__.__name__, self.connection_id, self.command_name, - self.operation_id, self.duration_micros, self.failure)) + self.operation_id, self.duration_micros, self.failure, + self.service_id) class _PoolEvent(object): @@ -724,7 +747,25 @@ class PoolClearedEvent(_PoolEvent): .. versionadded:: 3.9 """ - __slots__ = () + __slots__ = ("__service_id",) + + def __init__(self, address, service_id): + super(PoolClearedEvent, self).__init__(address) + self.__service_id = service_id + + @property + def service_id(self): + """Connections with this service_id are cleared. + + When service_id is ``None``, all connections in the pool are cleared. + + .. versionadded:: 3.12 + """ + return self.__service_id + + def __repr__(self): + return '%s(%r, %r)' % ( + self.__class__.__name__, self.address, self.__service_id) class PoolClosedEvent(_PoolEvent): @@ -1508,10 +1549,10 @@ def publish_pool_ready(self, address): except Exception: _handle_exception() - def publish_pool_cleared(self, address): + def publish_pool_cleared(self, address, service_id): """Publish a :class:`PoolClearedEvent` to all pool listeners. """ - event = PoolClearedEvent(address) + event = PoolClearedEvent(address, service_id) for subscriber in self.__cmap_listeners: try: subscriber.pool_cleared(event) diff --git a/pymongo/pool.py b/pymongo/pool.py index 728fec0f60..23ccdcab67 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -1129,7 +1129,7 @@ def ready(self): def closed(self): return self.state == PoolState.CLOSED - def _reset(self, close, pause=True): + def _reset(self, close, pause=True, service_id=None): old_state = self.state with self.size_cond: if self.closed: @@ -1161,7 +1161,8 @@ def _reset(self, close, pause=True): listeners.publish_pool_closed(self.address) else: if old_state != PoolState.PAUSED and self.enabled_for_cmap: - listeners.publish_pool_cleared(self.address) + listeners.publish_pool_cleared(self.address, + service_id=service_id) for sock_info in sockets: sock_info.close_socket(ConnectionClosedReason.STALE) @@ -1174,8 +1175,8 @@ def update_is_writable(self, is_writable): for socket in self.sockets: socket.update_is_writable(self.is_writable) - def reset(self): - self._reset(close=False) + def reset(self, service_id=None): + self._reset(close=False, service_id=service_id) def reset_without_pause(self): self._reset(close=False, pause=False) diff --git a/pymongo/server.py b/pymongo/server.py index fbfddae2e2..e9e29f49ea 100644 --- a/pymongo/server.py +++ b/pymongo/server.py @@ -49,9 +49,9 @@ def open(self): if not self._pool.opts.load_balanced: self._monitor.open() - def reset(self): + def reset(self, service_id=None): """Clear the connection pool.""" - self.pool.reset() + self.pool.reset(service_id) def close(self): """Clear the connection pool and stop the monitor. diff --git a/pymongo/topology.py b/pymongo/topology.py index ca5e1d973d..18d5c4c8f4 100644 --- a/pymongo/topology.py +++ b/pymongo/topology.py @@ -453,7 +453,7 @@ def update_pool(self, all_credentials): try: server.pool.remove_stale_sockets(generation, all_credentials) except PyMongoError as exc: - ctx = _ErrorContext(exc, 0, generation, False) + ctx = _ErrorContext(exc, 0, generation, False, None) self.handle_error(server.description.address, ctx) raise @@ -598,6 +598,7 @@ def _handle_error(self, address, err_ctx): server = self._servers[address] error = err_ctx.error exc_type = type(error) + service_id = err_ctx.service_id if (issubclass(exc_type, NetworkTimeout) and err_ctx.completed_handshake): # The socket has been closed. Don't reset the server. @@ -628,21 +629,21 @@ def _handle_error(self, address, err_ctx): self._process_change(ServerDescription(address, error=error)) if is_shutting_down or (err_ctx.max_wire_version <= 7): # Clear the pool. - server.reset() + server.reset(service_id) server.request_check() elif not err_ctx.completed_handshake: # Unknown command error during the connection handshake. if not self._settings.load_balanced: self._process_change(ServerDescription(address, error=error)) # Clear the pool. - server.reset() + server.reset(service_id) elif issubclass(exc_type, ConnectionFailure): # "Client MUST replace the server's description with type Unknown # ... MUST NOT request an immediate check of the server." if not self._settings.load_balanced: self._process_change(ServerDescription(address, error=error)) # Clear the pool. - server.reset() + server.reset(service_id) # "When a client marks a server Unknown from `Network error when # reading or writing`_, clients MUST cancel the isMaster check on # that server and close the current monitoring connection." @@ -794,11 +795,12 @@ def __repr__(self): class _ErrorContext(object): """An error with context for SDAM error handling.""" def __init__(self, error, max_wire_version, sock_generation, - completed_handshake): + completed_handshake, service_id): self.error = error self.max_wire_version = max_wire_version self.sock_generation = sock_generation self.completed_handshake = completed_handshake + self.service_id = service_id def _is_stale_error_topology_version(current_tv, error_tv): diff --git a/test/test_client.py b/test/test_client.py index 3754cb0ac3..5d57c32c1c 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -1483,7 +1483,7 @@ def stop(self): def run(self): while self.running: exc = AutoReconnect('mock pool error') - ctx = _ErrorContext(exc, 0, pool.generation, False) + ctx = _ErrorContext(exc, 0, pool.generation, False, None) client._topology.handle_error(pool.address, ctx) time.sleep(0.001) diff --git a/test/test_discovery_and_monitoring.py b/test/test_discovery_and_monitoring.py index ddd017bb9a..7fa96b5e18 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -113,7 +113,7 @@ def got_app_error(topology, app_error): topology.handle_error( server_address, _ErrorContext(e, max_wire_version, generation, - completed_handshake)) + completed_handshake, None)) def get_type(topology, hostname): diff --git a/test/test_monitoring.py b/test/test_monitoring.py index 46cfe87c4a..31e8282828 100644 --- a/test/test_monitoring.py +++ b/test/test_monitoring.py @@ -1183,7 +1183,7 @@ def test_command_event_repr(self): self.assertEqual( repr(event), "") + "command: 'isMaster', operation_id: 2, service_id: None>") delta = datetime.timedelta(milliseconds=100) event = monitoring.CommandSucceededEvent( delta, {'ok': 1}, 'isMaster', request_id, connection_id, @@ -1191,7 +1191,8 @@ def test_command_event_repr(self): self.assertEqual( repr(event), "") + "command: 'isMaster', operation_id: 2, duration_micros: 100000, " + "service_id: None>") event = monitoring.CommandFailedEvent( delta, {'ok': 0}, 'isMaster', request_id, connection_id, operation_id) @@ -1199,7 +1200,7 @@ def test_command_event_repr(self): repr(event), "") + "failure: {'ok': 0}, service_id: None>") def test_server_heartbeat_event_repr(self): connection_id = ('localhost', 27017) diff --git a/test/test_topology.py b/test/test_topology.py index 2abcab47b1..5e2f683f70 100644 --- a/test/test_topology.py +++ b/test/test_topology.py @@ -401,7 +401,7 @@ def test_handle_error(self): 'setName': 'rs', 'hosts': ['a', 'b']}) - errctx = _ErrorContext(AutoReconnect('mock'), 0, 0, True) + errctx = _ErrorContext(AutoReconnect('mock'), 0, 0, True, None) t.handle_error(('a', 27017), errctx) self.assertEqual(SERVER_TYPE.Unknown, get_type(t, 'a')) self.assertEqual(SERVER_TYPE.RSSecondary, get_type(t, 'b')) @@ -430,7 +430,7 @@ def test_handle_error_removed_server(self): t = create_mock_topology(replica_set_name='rs') # No error resetting a server not in the TopologyDescription. - errctx = _ErrorContext(AutoReconnect('mock'), 0, 0, True) + errctx = _ErrorContext(AutoReconnect('mock'), 0, 0, True, None) t.handle_error(('b', 27017), errctx) # Server was *not* added as type Unknown. diff --git a/test/unified_format.py b/test/unified_format.py index 767809b037..7fa7f55136 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -39,7 +39,9 @@ from pymongo.change_stream import ChangeStream from pymongo.collection import Collection from pymongo.database import Database -from pymongo.errors import BulkWriteError, InvalidOperation, PyMongoError +from pymongo.errors import ( + BulkWriteError, ConnectionFailure, InvalidOperation, NotMasterError, + PyMongoError) from pymongo.monitoring import ( CommandFailedEvent, CommandListener, CommandStartedEvent, CommandSucceededEvent, _SENSITIVE_COMMANDS, PoolCreatedEvent, @@ -547,8 +549,10 @@ def match_event(self, event_type, expectation, actual): elif name == 'poolClearedEvent': self.test.assertIsInstance(actual, PoolClearedEvent) if spec.get('hasServiceId'): - # TODO: Assert hasServiceId - self.test.assertTrue(actual.address) + self.test.assertIsNotNone(actual.service_id) + self.test.assertIsInstance(actual.service_id, ObjectId) + else: + self.test.assertIsNone(actual.service_id) elif name == 'poolClosedEvent': self.test.assertIsInstance(actual, PoolClosedEvent) elif name == 'connectionCreatedEvent': @@ -684,7 +688,11 @@ def process_error(self, exception, spec): pass if is_client_error: - self.assertNotIsInstance(exception, PyMongoError) + # Connection errors are considered client errors. + if isinstance(exception, ConnectionFailure): + self.assertNotIsInstance(exception, NotMasterError) + else: + self.assertNotIsInstance(exception, PyMongoError) if error_contains: if isinstance(exception, BulkWriteError): From fe91291e7118e699d091b6613d34246a67de2f72 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Fri, 21 May 2021 14:52:34 -0700 Subject: [PATCH 10/14] PYTHON-2676 Remove tests that rely on connection pinning --- test/load_balancer/unified/cursors.json | 1228 ------------- .../unified/sdam-error-handling.json | 508 ------ test/load_balancer/unified/transactions.json | 1606 ----------------- .../unified/wait-queue-timeouts.json | 153 -- 4 files changed, 3495 deletions(-) delete mode 100644 test/load_balancer/unified/cursors.json delete mode 100644 test/load_balancer/unified/sdam-error-handling.json delete mode 100644 test/load_balancer/unified/transactions.json delete mode 100644 test/load_balancer/unified/wait-queue-timeouts.json diff --git a/test/load_balancer/unified/cursors.json b/test/load_balancer/unified/cursors.json deleted file mode 100644 index 43e4fbb4f6..0000000000 --- a/test/load_balancer/unified/cursors.json +++ /dev/null @@ -1,1228 +0,0 @@ -{ - "description": "cursors are correctly pinned to connections for load-balanced clusters", - "schemaVersion": "1.3", - "runOnRequirements": [ - { - "topologies": [ - "load-balanced" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "client0", - "useMultipleMongoses": true, - "observeEvents": [ - "commandStartedEvent", - "commandSucceededEvent", - "commandFailedEvent", - "connectionReadyEvent", - "connectionClosedEvent", - "connectionCheckedOutEvent", - "connectionCheckedInEvent" - ] - } - }, - { - "database": { - "id": "database0", - "client": "client0", - "databaseName": "database0Name" - } - }, - { - "collection": { - "id": "collection0", - "database": "database0", - "collectionName": "coll0" - } - }, - { - "collection": { - "id": "collection1", - "database": "database0", - "collectionName": "coll1" - } - }, - { - "collection": { - "id": "collection2", - "database": "database0", - "collectionName": "coll2" - } - } - ], - "initialData": [ - { - "collectionName": "coll0", - "databaseName": "database0Name", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - }, - { - "collectionName": "coll1", - "databaseName": "database0Name", - "documents": [] - }, - { - "collectionName": "coll2", - "databaseName": "database0Name", - "documents": [] - } - ], - "tests": [ - { - "description": "no connection is pinned if all documents are returned in the initial batch", - "operations": [ - { - "name": "createFindCursor", - "object": "collection0", - "arguments": { - "filter": {} - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "find": "coll0", - "filter": {} - }, - "commandName": "find" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": 0, - "firstBatch": { - "$$type": "array" - }, - "ns": { - "$$type": "string" - } - } - }, - "commandName": "find" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connections are returned when the cursor is drained", - "operations": [ - { - "name": "createFindCursor", - "object": "collection0", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectResult": { - "_id": 1 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectResult": { - "_id": 2 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectResult": { - "_id": 3 - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - }, - { - "name": "close", - "object": "cursor0" - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "find": "coll0", - "filter": {}, - "batchSize": 2 - }, - "commandName": "find" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": { - "$$type": "long" - }, - "firstBatch": { - "$$type": "array" - }, - "ns": { - "$$type": "string" - } - } - }, - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "command": { - "getMore": { - "$$type": "long" - }, - "collection": "coll0" - }, - "commandName": "getMore" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": 0, - "ns": { - "$$type": "string" - }, - "nextBatch": { - "$$type": "array" - } - } - }, - "commandName": "getMore" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connections are returned to the pool when the cursor is closed", - "operations": [ - { - "name": "createFindCursor", - "object": "collection0", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "close", - "object": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "find": "coll0", - "filter": {}, - "batchSize": 2 - }, - "commandName": "find" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": { - "$$type": "long" - }, - "firstBatch": { - "$$type": "array" - }, - "ns": { - "$$type": "string" - } - } - }, - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "killCursors" - } - }, - { - "commandSucceededEvent": { - "commandName": "killCursors" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connections are not returned after an network error during getMore", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "getMore" - ], - "closeConnection": true - } - } - } - }, - { - "name": "createFindCursor", - "object": "collection0", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectResult": { - "_id": 1 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectResult": { - "_id": 2 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectError": { - "isClientError": true - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "close", - "object": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "find": "coll0", - "filter": {}, - "batchSize": 2 - }, - "commandName": "find" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": { - "$$type": "long" - }, - "firstBatch": { - "$$type": "array" - }, - "ns": { - "$$type": "string" - } - } - }, - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "command": { - "getMore": { - "$$type": "long" - }, - "collection": "coll0" - }, - "commandName": "getMore" - } - }, - { - "commandFailedEvent": { - "commandName": "getMore" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionClosedEvent": { - "reason": "error" - } - } - ] - } - ] - }, - { - "description": "pinned connections are returned after a network error during a killCursors request", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "killCursors" - ], - "closeConnection": true - } - } - } - }, - { - "name": "createFindCursor", - "object": "collection0", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "close", - "object": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "find": "coll0", - "filter": {}, - "batchSize": 2 - }, - "commandName": "find" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": { - "$$type": "long" - }, - "firstBatch": { - "$$type": "array" - }, - "ns": { - "$$type": "string" - } - } - }, - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "killCursors" - } - }, - { - "commandFailedEvent": { - "commandName": "killCursors" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionClosedEvent": { - "reason": "error" - } - } - ] - } - ] - }, - { - "description": "pinned connections are not returned to the pool after a non-network error on getMore", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "getMore" - ], - "errorCode": 7 - } - } - } - }, - { - "name": "createFindCursor", - "object": "collection0", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectResult": { - "_id": 1 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectResult": { - "_id": 2 - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectError": { - "errorCode": 7 - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "close", - "object": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "find": "coll0", - "filter": {}, - "batchSize": 2 - }, - "commandName": "find" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": { - "$$type": "long" - }, - "firstBatch": { - "$$type": "array" - }, - "ns": { - "$$type": "string" - } - } - }, - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "command": { - "getMore": { - "$$type": "long" - }, - "collection": "coll0" - }, - "commandName": "getMore" - } - }, - { - "commandFailedEvent": { - "commandName": "getMore" - } - }, - { - "commandStartedEvent": { - "commandName": "killCursors" - } - }, - { - "commandSucceededEvent": { - "commandName": "killCursors" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "aggregate pins the cursor to a connection", - "operations": [ - { - "name": "aggregate", - "object": "collection0", - "arguments": { - "pipeline": [], - "batchSize": 2 - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "aggregate": "coll0", - "cursor": { - "batchSize": 2 - } - }, - "commandName": "aggregate" - } - }, - { - "commandSucceededEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "command": { - "getMore": { - "$$type": "long" - }, - "collection": "coll0" - }, - "commandName": "getMore" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": 0, - "ns": { - "$$type": "string" - }, - "nextBatch": { - "$$type": "array" - } - } - }, - "commandName": "getMore" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "listCollections pins the cursor to a connection", - "operations": [ - { - "name": "listCollections", - "object": "database0", - "arguments": { - "filter": {}, - "batchSize": 2 - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "listCollections": 1, - "cursor": { - "batchSize": 2 - } - }, - "commandName": "listCollections", - "databaseName": "database0Name" - } - }, - { - "commandSucceededEvent": { - "commandName": "listCollections" - } - }, - { - "commandStartedEvent": { - "command": { - "getMore": { - "$$type": "long" - }, - "collection": { - "$$type": "string" - } - }, - "commandName": "getMore" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": 0, - "ns": { - "$$type": "string" - }, - "nextBatch": { - "$$type": "array" - } - } - }, - "commandName": "getMore" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "listIndexes pins the cursor to a connection", - "operations": [ - { - "name": "createIndex", - "object": "collection0", - "arguments": { - "keys": { - "x": 1 - }, - "name": "x_1" - } - }, - { - "name": "createIndex", - "object": "collection0", - "arguments": { - "keys": { - "y": 1 - }, - "name": "y_1" - } - }, - { - "name": "listIndexes", - "object": "collection0", - "arguments": { - "batchSize": 2 - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "createIndexes": "coll0", - "indexes": [ - { - "name": "x_1", - "key": { - "x": 1 - } - } - ] - }, - "commandName": "createIndexes" - } - }, - { - "commandSucceededEvent": { - "commandName": "createIndexes" - } - }, - { - "commandStartedEvent": { - "command": { - "createIndexes": "coll0", - "indexes": [ - { - "name": "y_1", - "key": { - "y": 1 - } - } - ] - }, - "commandName": "createIndexes" - } - }, - { - "commandSucceededEvent": { - "commandName": "createIndexes" - } - }, - { - "commandStartedEvent": { - "command": { - "listIndexes": "coll0", - "cursor": { - "batchSize": 2 - } - }, - "commandName": "listIndexes", - "databaseName": "database0Name" - } - }, - { - "commandSucceededEvent": { - "commandName": "listIndexes" - } - }, - { - "commandStartedEvent": { - "command": { - "getMore": { - "$$type": "long" - }, - "collection": "coll0" - }, - "commandName": "getMore" - } - }, - { - "commandSucceededEvent": { - "reply": { - "cursor": { - "id": 0, - "ns": { - "$$type": "string" - }, - "nextBatch": { - "$$type": "array" - } - } - }, - "commandName": "getMore" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "change streams pin to a connection", - "operations": [ - { - "name": "createChangeStream", - "object": "collection0", - "arguments": { - "pipeline": [] - }, - "saveResultAsEntity": "changeStream0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "close", - "object": "changeStream0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandSucceededEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "killCursors" - } - }, - { - "commandSucceededEvent": { - "commandName": "killCursors" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - } - ] -} diff --git a/test/load_balancer/unified/sdam-error-handling.json b/test/load_balancer/unified/sdam-error-handling.json deleted file mode 100644 index 63aabc04db..0000000000 --- a/test/load_balancer/unified/sdam-error-handling.json +++ /dev/null @@ -1,508 +0,0 @@ -{ - "description": "state change errors are correctly handled", - "schemaVersion": "1.3", - "runOnRequirements": [ - { - "topologies": [ - "load-balanced" - ] - } - ], - "_yamlAnchors": { - "observedEvents": [ - "connectionCreatedEvent", - "connectionReadyEvent", - "connectionCheckedOutEvent", - "connectionCheckOutFailedEvent", - "connectionCheckedInEvent", - "connectionClosedEvent", - "poolClearedEvent" - ] - }, - "createEntities": [ - { - "client": { - "id": "failPointClient", - "useMultipleMongoses": false - } - }, - { - "client": { - "id": "singleClient", - "useMultipleMongoses": false, - "uriOptions": { - "appname": "lbSDAMErrorTestClient", - "retryWrites": false - }, - "observeEvents": [ - "connectionCreatedEvent", - "connectionReadyEvent", - "connectionCheckedOutEvent", - "connectionCheckOutFailedEvent", - "connectionCheckedInEvent", - "connectionClosedEvent", - "poolClearedEvent" - ] - } - }, - { - "database": { - "id": "singleDB", - "client": "singleClient", - "databaseName": "singleDB" - } - }, - { - "collection": { - "id": "singleColl", - "database": "singleDB", - "collectionName": "singleColl" - } - }, - { - "client": { - "id": "multiClient", - "useMultipleMongoses": true, - "uriOptions": { - "retryWrites": false - }, - "observeEvents": [ - "connectionCreatedEvent", - "connectionReadyEvent", - "connectionCheckedOutEvent", - "connectionCheckOutFailedEvent", - "connectionCheckedInEvent", - "connectionClosedEvent", - "poolClearedEvent" - ] - } - }, - { - "database": { - "id": "multiDB", - "client": "multiClient", - "databaseName": "multiDB" - } - }, - { - "collection": { - "id": "multiColl", - "database": "multiDB", - "collectionName": "multiColl" - } - } - ], - "initialData": [ - { - "collectionName": "singleColl", - "databaseName": "singleDB", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - }, - { - "collectionName": "multiColl", - "databaseName": "multiDB", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - } - ], - "tests": [ - { - "description": "only connections for a specific serviceId are closed when pools are cleared", - "operations": [ - { - "name": "createFindCursor", - "object": "multiColl", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "createFindCursor", - "object": "multiColl", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor1" - }, - { - "name": "close", - "object": "cursor0" - }, - { - "name": "close", - "object": "cursor1" - }, - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "multiClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11600 - } - } - } - }, - { - "name": "insertOne", - "object": "multiColl", - "arguments": { - "document": { - "x": 1 - } - }, - "expectError": { - "errorCode": 11600 - } - }, - { - "name": "insertOne", - "object": "multiColl", - "arguments": { - "document": { - "x": 1 - } - } - } - ], - "expectEvents": [ - { - "client": "multiClient", - "eventType": "cmap", - "events": [ - { - "connectionCreatedEvent": {} - }, - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCreatedEvent": {} - }, - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "poolClearedEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionClosedEvent": { - "reason": "stale" - } - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "errors during the initial connection hello are ignore", - "runOnRequirements": [ - { - "minServerVersion": "4.9" - } - ], - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "failPointClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "isMaster" - ], - "closeConnection": true, - "appName": "lbSDAMErrorTestClient" - } - } - } - }, - { - "name": "insertOne", - "object": "singleColl", - "arguments": { - "document": { - "x": 1 - } - }, - "expectError": { - "isClientError": true - } - } - ], - "expectEvents": [ - { - "client": "singleClient", - "eventType": "cmap", - "events": [ - { - "connectionCreatedEvent": {} - }, - { - "connectionClosedEvent": { - "reason": "error" - } - }, - { - "connectionCheckOutFailedEvent": { - "reason": "connectionError" - } - } - ] - } - ] - }, - { - "description": "errors during authentication are processed", - "runOnRequirements": [ - { - "auth": true - } - ], - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "failPointClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "saslContinue" - ], - "closeConnection": true, - "appName": "lbSDAMErrorTestClient" - } - } - } - }, - { - "name": "insertOne", - "object": "singleColl", - "arguments": { - "document": { - "x": 1 - } - }, - "expectError": { - "isClientError": true - } - } - ], - "expectEvents": [ - { - "client": "singleClient", - "eventType": "cmap", - "events": [ - { - "connectionCreatedEvent": {} - }, - { - "poolClearedEvent": {} - }, - { - "connectionClosedEvent": { - "reason": "error" - } - }, - { - "connectionCheckOutFailedEvent": { - "reason": "connectionError" - } - } - ] - } - ] - }, - { - "description": "stale errors are ignored", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "failPointClient", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "getMore" - ], - "closeConnection": true - } - } - } - }, - { - "name": "createFindCursor", - "object": "singleColl", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "createFindCursor", - "object": "singleColl", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor1" - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0" - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0" - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor0", - "expectError": { - "isClientError": true - } - }, - { - "name": "close", - "object": "cursor0" - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor1" - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor1" - }, - { - "name": "iterateUntilDocumentOrError", - "object": "cursor1", - "expectError": { - "isClientError": true - } - }, - { - "name": "close", - "object": "cursor1" - } - ], - "expectEvents": [ - { - "client": "singleClient", - "eventType": "cmap", - "events": [ - { - "connectionCreatedEvent": {} - }, - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCreatedEvent": {} - }, - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "poolClearedEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionClosedEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionClosedEvent": {} - } - ] - } - ] - } - ] -} diff --git a/test/load_balancer/unified/transactions.json b/test/load_balancer/unified/transactions.json deleted file mode 100644 index add2453848..0000000000 --- a/test/load_balancer/unified/transactions.json +++ /dev/null @@ -1,1606 +0,0 @@ -{ - "description": "transactions are correctly pinned to connections for load-balanced clusters", - "schemaVersion": "1.3", - "runOnRequirements": [ - { - "topologies": [ - "load-balanced" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "client0", - "useMultipleMongoses": true, - "observeEvents": [ - "commandStartedEvent", - "connectionReadyEvent", - "connectionClosedEvent", - "connectionCheckedOutEvent", - "connectionCheckedInEvent" - ] - } - }, - { - "session": { - "id": "session0", - "client": "client0" - } - }, - { - "database": { - "id": "database0", - "client": "client0", - "databaseName": "database0Name" - } - }, - { - "collection": { - "id": "collection0", - "database": "database0", - "collectionName": "coll0" - } - } - ], - "initialData": [ - { - "collectionName": "coll0", - "databaseName": "database0Name", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - } - ], - "_yamlAnchors": { - "documents": [ - { - "_id": 4 - } - ] - }, - "tests": [ - { - "description": "sessions are reused in LB mode", - "operations": [ - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - } - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - } - } - }, - { - "name": "assertSameLsidOnLastTwoCommands", - "object": "testRunner", - "arguments": { - "client": "client0" - } - } - ] - }, - { - "description": "all operations go to the same mongos", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - } - ] - } - ] - }, - { - "description": "transaction can be committed multiple times", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is not released after a non-transient CRUD error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 51 - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - }, - "expectError": { - "errorCode": 51, - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is not released after a non-transient commit error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 51 - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0", - "expectError": { - "errorCode": 51, - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is released after a non-transient abort error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 51 - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is released after a transient non-network CRUD error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 24 - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - }, - "expectError": { - "errorCode": 24, - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is released after a transient network CRUD error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - }, - "expectError": { - "isClientError": true, - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionClosedEvent": { - "reason": "error" - } - }, - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is released after a transient non-network commit error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 24 - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0", - "expectError": { - "errorCode": 24, - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is released after a transient network commit error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "closeConnection": true - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0", - "ignoreResultAndError": true - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionClosedEvent": { - "reason": "error" - } - }, - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is released after a transient non-network abort error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 24 - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is released after a transient network abort error", - "operations": [ - { - "name": "failPoint", - "object": "testRunner", - "arguments": { - "client": "client0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "closeConnection": true - } - } - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionClosedEvent": { - "reason": "error" - } - }, - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is released on successful abort", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is returned when a new transaction is started", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - } - ] - } - ] - }, - { - "description": "pinned connection is returned when a non-transaction operation uses the session", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - }, - { - "description": "a connection can be shared by a transaction and a cursor", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "createFindCursor", - "object": "collection0", - "arguments": { - "filter": {}, - "batchSize": 2, - "session": "session0" - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "close", - "object": "cursor0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "assertNumberConnectionsCheckedOut", - "object": "testRunner", - "arguments": { - "client": "client0", - "connections": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "killCursors" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - } - ] - }, - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionReadyEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - } - ] - } - ] - } - ] -} diff --git a/test/load_balancer/unified/wait-queue-timeouts.json b/test/load_balancer/unified/wait-queue-timeouts.json deleted file mode 100644 index 61575d6706..0000000000 --- a/test/load_balancer/unified/wait-queue-timeouts.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "description": "wait queue timeout errors include details about checked out connections", - "schemaVersion": "1.3", - "runOnRequirements": [ - { - "topologies": [ - "load-balanced" - ] - } - ], - "createEntities": [ - { - "client": { - "id": "client0", - "useMultipleMongoses": true, - "uriOptions": { - "maxPoolSize": 1, - "waitQueueTimeoutMS": 5 - }, - "observeEvents": [ - "connectionCheckedOutEvent", - "connectionCheckOutFailedEvent" - ] - } - }, - { - "session": { - "id": "session0", - "client": "client0" - } - }, - { - "database": { - "id": "database0", - "client": "client0", - "databaseName": "database0Name" - } - }, - { - "collection": { - "id": "collection0", - "database": "database0", - "collectionName": "coll0" - } - } - ], - "initialData": [ - { - "collectionName": "coll0", - "databaseName": "database0Name", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - } - ], - "tests": [ - { - "description": "wait queue timeout errors include cursor statistics", - "operations": [ - { - "name": "createFindCursor", - "object": "collection0", - "arguments": { - "filter": {}, - "batchSize": 2 - }, - "saveResultAsEntity": "cursor0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - } - }, - "expectError": { - "isClientError": true, - "errorContains": "maxPoolSize: 1, connections in use by cursors: 1, connections in use by transactions: 0, connections in use by other operations: 0" - } - } - ], - "expectEvents": [ - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckOutFailedEvent": {} - } - ] - } - ] - }, - { - "description": "wait queue timeout errors include transaction statistics", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - }, - "session": "session0" - } - }, - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "x": 1 - } - }, - "expectError": { - "isClientError": true, - "errorContains": "maxPoolSize: 1, connections in use by cursors: 0, connections in use by transactions: 1, connections in use by other operations: 0" - } - } - ], - "expectEvents": [ - { - "client": "client0", - "eventType": "cmap", - "events": [ - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckOutFailedEvent": {} - } - ] - } - ] - } - ] -} From 16c251fb38b570c70061e59078ad08e3e7d99d98 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Fri, 21 May 2021 15:04:56 -0700 Subject: [PATCH 11/14] Fix pool reset in tests and PoolClearedEvent service_id arg --- pymongo/monitoring.py | 3 ++- test/test_cmap.py | 2 ++ test/utils.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pymongo/monitoring.py b/pymongo/monitoring.py index 113cb1f696..b53629d12b 100644 --- a/pymongo/monitoring.py +++ b/pymongo/monitoring.py @@ -744,12 +744,13 @@ class PoolClearedEvent(_PoolEvent): :Parameters: - `address`: The address (host, port) pair of the server this Pool is attempting to connect to. + - `service_id`: The service_id this command was sent to, or ``None``. .. versionadded:: 3.9 """ __slots__ = ("__service_id",) - def __init__(self, address, service_id): + def __init__(self, address, service_id=None): super(PoolClearedEvent, self).__init__(address) self.__service_id = service_id diff --git a/test/test_cmap.py b/test/test_cmap.py index 7a9ab51804..053f27ba73 100644 --- a/test/test_cmap.py +++ b/test/test_cmap.py @@ -22,6 +22,7 @@ sys.path[0:0] = [""] from bson.son import SON +from bson.objectid import ObjectId from pymongo.errors import (ConnectionFailure, OperationFailure, @@ -422,6 +423,7 @@ def test_events_repr(self): self.assertRepr(ConnectionCheckOutStartedEvent(host)) self.assertRepr(PoolCreatedEvent(host, {})) self.assertRepr(PoolClearedEvent(host)) + self.assertRepr(PoolClearedEvent(host, service_id=ObjectId())) self.assertRepr(PoolClosedEvent(host)) def test_close_leaves_pool_unpaused(self): diff --git a/test/utils.py b/test/utils.py index f3d7dbe2aa..682782a432 100644 --- a/test/utils.py +++ b/test/utils.py @@ -277,7 +277,7 @@ def _reset(self): def ready(self): pass - def reset(self): + def reset(self, service_id=None): self._reset() def reset_without_pause(self): From dc940ccacbbd401180a77dee8e24b4d3a7774426 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Fri, 21 May 2021 16:53:28 -0700 Subject: [PATCH 12/14] Fix runOnRequirements for load-balancer --- test/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/__init__.py b/test/__init__.py index af656fb704..9e76b28f50 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -644,8 +644,10 @@ def check_auth_with_sharding(self, func): func=func) def is_topology_type(self, topologies): - if 'load-balanced' in topologies and self.load_balancer: - return True + if self.load_balancer: + if 'load-balanced' in topologies: + return True + return False if 'single' in topologies and not (self.is_mongos or self.is_rs): return True if 'replicaset' in topologies and self.is_rs: From e08c68ff62dd437a85ce42ff1d7a27924b2a4af3 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Tue, 25 May 2021 09:28:50 -0700 Subject: [PATCH 13/14] Generate xunit output, show local variables in tracebacks --- .evergreen/run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 12728f7dc0..cc3fa799c4 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -198,7 +198,7 @@ if [ -z "$GREEN_FRAMEWORK" ]; then fi if [ -n "$TEST_LOADBALANCER" ]; then - $PYTHON -m unittest discover -s test/load_balancer -v + $PYTHON -m xmlrunner discover -s test/load_balancer -v --locals -o xunit-results else $PYTHON $COVERAGE_ARGS setup.py $C_EXTENSIONS test $TEST_ARGS $OUTPUT fi From dc3eb5745f73f7cfcfc35ff0de57ef1227a0ef95 Mon Sep 17 00:00:00 2001 From: Shane Harvey Date: Tue, 25 May 2021 10:01:10 -0700 Subject: [PATCH 14/14] xml output dir must be absolute path --- .evergreen/run-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index cc3fa799c4..9848b91877 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -198,7 +198,7 @@ if [ -z "$GREEN_FRAMEWORK" ]; then fi if [ -n "$TEST_LOADBALANCER" ]; then - $PYTHON -m xmlrunner discover -s test/load_balancer -v --locals -o xunit-results + $PYTHON -m xmlrunner discover -s test/load_balancer -v --locals -o $XUNIT_DIR else $PYTHON $COVERAGE_ARGS setup.py $C_EXTENSIONS test $TEST_ARGS $OUTPUT fi