1616from __future__ import annotations
1717
1818import asyncio
19- import base64
20- import contextlib
2119import gc
2220import multiprocessing
2321import os
2725import sys
2826import threading
2927import time
30- import traceback
3128import unittest
3229import warnings
3330from asyncio import iscoroutinefunction
5451 sanitize_reply ,
5552)
5653
54+ from pymongo .uri_parser import parse_uri
55+
5756try :
5857 import ipaddress
5958
8079_IS_SYNC = True
8180
8281
82+ def _connection_string (h ):
83+ if h .startswith (("mongodb://" , "mongodb+srv://" )):
84+ return h
85+ return f"mongodb://{ h !s} "
86+
87+
8388class ClientContext :
8489 client : MongoClient
8590
@@ -230,6 +235,9 @@ def _init_client(self):
230235 if not self ._check_user_provided ():
231236 _create_user (self .client .admin , db_user , db_pwd )
232237
238+ if self .client :
239+ self .client .close ()
240+
233241 self .client = self ._connect (
234242 host ,
235243 port ,
@@ -256,6 +264,8 @@ def _init_client(self):
256264 if "setName" in hello :
257265 self .replica_set_name = str (hello ["setName" ])
258266 self .is_rs = True
267+ if self .client :
268+ self .client .close ()
259269 if self .auth_enabled :
260270 # It doesn't matter which member we use as the seed here.
261271 self .client = pymongo .MongoClient (
@@ -318,6 +328,7 @@ def _init_client(self):
318328 hello = mongos_client .admin .command (HelloCompat .LEGACY_CMD )
319329 if hello .get ("msg" ) == "isdbgrid" :
320330 self .mongoses .append (next_address )
331+ mongos_client .close ()
321332
322333 def init (self ):
323334 with self .conn_lock :
@@ -537,12 +548,6 @@ def require_auth(self, func):
537548 lambda : self .auth_enabled , "Authentication is not enabled on the server" , func = func
538549 )
539550
540- def require_no_fips (self , func ):
541- """Run a test only if the host does not have FIPS enabled."""
542- return self ._require (
543- lambda : not self .fips_enabled , "Test cannot run on a FIPS-enabled host" , func = func
544- )
545-
546551 def require_no_auth (self , func ):
547552 """Run a test only if the server is running without auth enabled."""
548553 return self ._require (
@@ -930,6 +935,172 @@ def _target() -> None:
930935 self .fail (f"child timed out after { timeout } s (see traceback in logs): deadlock?" )
931936 self .assertEqual (proc .exitcode , 0 )
932937
938+ @classmethod
939+ def _unmanaged_async_mongo_client (
940+ cls , host , port , authenticate = True , directConnection = None , ** kwargs
941+ ):
942+ """Create a new client over SSL/TLS if necessary."""
943+ host = host or client_context .host
944+ port = port or client_context .port
945+ client_options : dict = client_context .default_client_options .copy ()
946+ if client_context .replica_set_name and not directConnection :
947+ client_options ["replicaSet" ] = client_context .replica_set_name
948+ if directConnection is not None :
949+ client_options ["directConnection" ] = directConnection
950+ client_options .update (kwargs )
951+
952+ uri = _connection_string (host )
953+ auth_mech = kwargs .get ("authMechanism" , "" )
954+ if client_context .auth_enabled and authenticate and auth_mech != "MONGODB-OIDC" :
955+ # Only add the default username or password if one is not provided.
956+ res = parse_uri (uri )
957+ if (
958+ not res ["username" ]
959+ and not res ["password" ]
960+ and "username" not in client_options
961+ and "password" not in client_options
962+ ):
963+ client_options ["username" ] = db_user
964+ client_options ["password" ] = db_pwd
965+ client = MongoClient (uri , port , ** client_options )
966+ if client ._options .connect :
967+ client ._connect ()
968+ return client
969+
970+ def _async_mongo_client (self , host , port , authenticate = True , directConnection = None , ** kwargs ):
971+ """Create a new client over SSL/TLS if necessary."""
972+ host = host or client_context .host
973+ port = port or client_context .port
974+ client_options : dict = client_context .default_client_options .copy ()
975+ if client_context .replica_set_name and not directConnection :
976+ client_options ["replicaSet" ] = client_context .replica_set_name
977+ if directConnection is not None :
978+ client_options ["directConnection" ] = directConnection
979+ client_options .update (kwargs )
980+
981+ uri = _connection_string (host )
982+ auth_mech = kwargs .get ("authMechanism" , "" )
983+ if client_context .auth_enabled and authenticate and auth_mech != "MONGODB-OIDC" :
984+ # Only add the default username or password if one is not provided.
985+ res = parse_uri (uri )
986+ if (
987+ not res ["username" ]
988+ and not res ["password" ]
989+ and "username" not in client_options
990+ and "password" not in client_options
991+ ):
992+ client_options ["username" ] = db_user
993+ client_options ["password" ] = db_pwd
994+ client = MongoClient (uri , port , ** client_options )
995+ if client ._options .connect :
996+ client ._connect ()
997+ self .addCleanup (client .close )
998+ return client
999+
1000+ @classmethod
1001+ def unmanaged_single_client_noauth (
1002+ cls , h : Any = None , p : Any = None , ** kwargs : Any
1003+ ) -> MongoClient [dict ]:
1004+ """Make a direct connection. Don't authenticate."""
1005+ return cls ._unmanaged_async_mongo_client (
1006+ h , p , authenticate = False , directConnection = True , ** kwargs
1007+ )
1008+
1009+ @classmethod
1010+ def unmanaged_single_client (
1011+ cls , h : Any = None , p : Any = None , ** kwargs : Any
1012+ ) -> MongoClient [dict ]:
1013+ """Make a direct connection. Don't authenticate."""
1014+ return cls ._unmanaged_async_mongo_client (h , p , directConnection = True , ** kwargs )
1015+
1016+ @classmethod
1017+ def unmanaged_rs_client (cls , h : Any = None , p : Any = None , ** kwargs : Any ) -> MongoClient [dict ]:
1018+ """Connect to the replica set and authenticate if necessary."""
1019+ return cls ._unmanaged_async_mongo_client (h , p , ** kwargs )
1020+
1021+ @classmethod
1022+ def unmanaged_rs_client_noauth (
1023+ cls , h : Any = None , p : Any = None , ** kwargs : Any
1024+ ) -> MongoClient [dict ]:
1025+ """Make a direct connection. Don't authenticate."""
1026+ return cls ._unmanaged_async_mongo_client (h , p , authenticate = False , ** kwargs )
1027+
1028+ @classmethod
1029+ def unmanaged_rs_or_single_client_noauth (
1030+ cls , h : Any = None , p : Any = None , ** kwargs : Any
1031+ ) -> MongoClient [dict ]:
1032+ """Make a direct connection. Don't authenticate."""
1033+ return cls ._unmanaged_async_mongo_client (h , p , authenticate = False , ** kwargs )
1034+
1035+ @classmethod
1036+ def unmanaged_rs_or_single_client (
1037+ cls , h : Any = None , p : Any = None , ** kwargs : Any
1038+ ) -> MongoClient [dict ]:
1039+ """Make a direct connection. Don't authenticate."""
1040+ return cls ._unmanaged_async_mongo_client (h , p , ** kwargs )
1041+
1042+ def single_client_noauth (
1043+ self , h : Any = None , p : Any = None , ** kwargs : Any
1044+ ) -> MongoClient [dict ]:
1045+ """Make a direct connection. Don't authenticate."""
1046+ return self ._async_mongo_client (h , p , authenticate = False , directConnection = True , ** kwargs )
1047+
1048+ def single_client (self , h : Any = None , p : Any = None , ** kwargs : Any ) -> MongoClient [dict ]:
1049+ """Make a direct connection, and authenticate if necessary."""
1050+ return self ._async_mongo_client (h , p , directConnection = True , ** kwargs )
1051+
1052+ def rs_client_noauth (self , h : Any = None , p : Any = None , ** kwargs : Any ) -> MongoClient [dict ]:
1053+ """Connect to the replica set. Don't authenticate."""
1054+ return self ._async_mongo_client (h , p , authenticate = False , ** kwargs )
1055+
1056+ def rs_client (self , h : Any = None , p : Any = None , ** kwargs : Any ) -> MongoClient [dict ]:
1057+ """Connect to the replica set and authenticate if necessary."""
1058+ return self ._async_mongo_client (h , p , ** kwargs )
1059+
1060+ def rs_or_single_client_noauth (
1061+ self , h : Any = None , p : Any = None , ** kwargs : Any
1062+ ) -> MongoClient [dict ]:
1063+ """Connect to the replica set if there is one, otherwise the standalone.
1064+
1065+ Like rs_or_single_client, but does not authenticate.
1066+ """
1067+ return self ._async_mongo_client (h , p , authenticate = False , ** kwargs )
1068+
1069+ def rs_or_single_client (self , h : Any = None , p : Any = None , ** kwargs : Any ) -> MongoClient [Any ]:
1070+ """Connect to the replica set if there is one, otherwise the standalone.
1071+
1072+ Authenticates if necessary.
1073+ """
1074+ return self ._async_mongo_client (h , p , ** kwargs )
1075+
1076+ def simple_client (self , h : Any = None , p : Any = None , ** kwargs : Any ) -> MongoClient :
1077+ if not h and not p :
1078+ client = MongoClient (** kwargs )
1079+ else :
1080+ client = MongoClient (h , p , ** kwargs )
1081+ self .addCleanup (client .close )
1082+ return client
1083+
1084+ @classmethod
1085+ def unmanaged_simple_client (cls , h : Any = None , p : Any = None , ** kwargs : Any ) -> MongoClient :
1086+ if not h and not p :
1087+ client = MongoClient (** kwargs )
1088+ else :
1089+ client = MongoClient (h , p , ** kwargs )
1090+ return client
1091+
1092+ def disable_replication (self , client ):
1093+ """Disable replication on all secondaries."""
1094+ for h , p in client .secondaries :
1095+ secondary = self .single_client (h , p )
1096+ secondary .admin .command ("configureFailPoint" , "stopReplProducer" , mode = "alwaysOn" )
1097+
1098+ def enable_replication (self , client ):
1099+ """Enable replication on all secondaries."""
1100+ for h , p in client .secondaries :
1101+ secondary = self .single_client (h , p )
1102+ secondary .admin .command ("configureFailPoint" , "stopReplProducer" , mode = "off" )
1103+
9331104
9341105class UnitTest (PyMongoTestCase ):
9351106 """Async base class for TestCases that don't require a connection to MongoDB."""
0 commit comments