Skip to content

Commit f7b94be

Browse files
authored
PYTHON-5143 Support auto encryption in unified tests (#2488)
1 parent db3d3c7 commit f7b94be

17 files changed

+1627
-559
lines changed

.evergreen/remove-unimplemented-tests.sh

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@ PYMONGO=$(dirname "$(cd "$(dirname "$0")" || exit; pwd)")
33

44
rm $PYMONGO/test/transactions/legacy/errors-client.json # PYTHON-1894
55
rm $PYMONGO/test/connection_monitoring/wait-queue-fairness.json # PYTHON-1873
6-
rm $PYMONGO/test/client-side-encryption/spec/unified/fle2v2-BypassQueryAnalysis.json # PYTHON-5143
7-
rm $PYMONGO/test/client-side-encryption/spec/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json # PYTHON-5143
8-
rm $PYMONGO/test/client-side-encryption/spec/unified/localSchema.json # PYTHON-5143
9-
rm $PYMONGO/test/client-side-encryption/spec/unified/maxWireVersion.json # PYTHON-5143
10-
rm $PYMONGO/test/unified-test-format/valid-pass/poc-queryable-encryption.json # PYTHON-5143
116
rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-application-error.json # PYTHON-4918
127
rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-checkout-error.json # PYTHON-4918
138
rm $PYMONGO/test/discovery_and_monitoring/unified/pool-clear-min-pool-size-error.json # PYTHON-4918

test/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,17 @@
5959

6060
sys.path[0:0] = [""]
6161

62-
from test.helpers import (
62+
from test.helpers import client_knobs, global_knobs
63+
from test.helpers_shared import (
6364
COMPRESSORS,
6465
IS_SRV,
6566
MONGODB_API_VERSION,
6667
MULTI_MONGOS_LB_URI,
6768
TEST_LOADBALANCER,
6869
TLS_OPTIONS,
6970
SystemCertsPatcher,
70-
client_knobs,
7171
db_pwd,
7272
db_user,
73-
global_knobs,
7473
host,
7574
is_server_resolvable,
7675
port,

test/asynchronous/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,17 @@
5959

6060
sys.path[0:0] = [""]
6161

62-
from test.helpers import (
62+
from test.asynchronous.helpers import client_knobs, global_knobs
63+
from test.helpers_shared import (
6364
COMPRESSORS,
6465
IS_SRV,
6566
MONGODB_API_VERSION,
6667
MULTI_MONGOS_LB_URI,
6768
TEST_LOADBALANCER,
6869
TLS_OPTIONS,
6970
SystemCertsPatcher,
70-
client_knobs,
7171
db_pwd,
7272
db_user,
73-
global_knobs,
7473
host,
7574
is_server_resolvable,
7675
port,

test/asynchronous/helpers.py

Lines changed: 5 additions & 243 deletions
Original file line numberDiff line numberDiff line change
@@ -12,137 +12,22 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""Shared constants and helper methods for pymongo, bson, and gridfs test suites."""
15+
"""Shared helper methods for pymongo, bson, and gridfs test suites."""
1616
from __future__ import annotations
1717

1818
import asyncio
19-
import base64
20-
import gc
21-
import multiprocessing
22-
import os
23-
import signal
24-
import socket
25-
import subprocess
26-
import sys
2719
import threading
28-
import time
2920
import traceback
30-
import unittest
31-
import warnings
32-
from inspect import iscoroutinefunction
33-
34-
from pymongo._asyncio_task import create_task
35-
36-
try:
37-
import ipaddress
38-
39-
HAVE_IPADDRESS = True
40-
except ImportError:
41-
HAVE_IPADDRESS = False
4221
from functools import wraps
43-
from typing import Any, Callable, Dict, Generator, Optional, no_type_check
44-
from unittest import SkipTest
22+
from typing import Optional, no_type_check
4523

46-
from bson.son import SON
47-
from pymongo import common, message
24+
from bson import SON
25+
from pymongo import common
26+
from pymongo._asyncio_task import create_task
4827
from pymongo.read_preferences import ReadPreference
49-
from pymongo.ssl_support import HAVE_SSL, _ssl # type:ignore[attr-defined]
50-
from pymongo.synchronous.uri_parser import parse_uri
51-
52-
if HAVE_SSL:
53-
import ssl
5428

5529
_IS_SYNC = False
5630

57-
# Enable debug output for uncollectable objects. PyPy does not have set_debug.
58-
if hasattr(gc, "set_debug"):
59-
gc.set_debug(
60-
gc.DEBUG_UNCOLLECTABLE | getattr(gc, "DEBUG_OBJECTS", 0) | getattr(gc, "DEBUG_INSTANCES", 0)
61-
)
62-
63-
# The host and port of a single mongod or mongos, or the seed host
64-
# for a replica set.
65-
host = os.environ.get("DB_IP", "localhost")
66-
port = int(os.environ.get("DB_PORT", 27017))
67-
IS_SRV = "mongodb+srv" in host
68-
69-
db_user = os.environ.get("DB_USER", "user")
70-
db_pwd = os.environ.get("DB_PASSWORD", "password")
71-
72-
CERT_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "certificates")
73-
CLIENT_PEM = os.environ.get("CLIENT_PEM", os.path.join(CERT_PATH, "client.pem"))
74-
CA_PEM = os.environ.get("CA_PEM", os.path.join(CERT_PATH, "ca.pem"))
75-
76-
TLS_OPTIONS: Dict = {"tls": True}
77-
if CLIENT_PEM:
78-
TLS_OPTIONS["tlsCertificateKeyFile"] = CLIENT_PEM
79-
if CA_PEM:
80-
TLS_OPTIONS["tlsCAFile"] = CA_PEM
81-
82-
COMPRESSORS = os.environ.get("COMPRESSORS")
83-
MONGODB_API_VERSION = os.environ.get("MONGODB_API_VERSION")
84-
TEST_LOADBALANCER = bool(os.environ.get("TEST_LOAD_BALANCER"))
85-
SINGLE_MONGOS_LB_URI = os.environ.get("SINGLE_MONGOS_LB_URI")
86-
MULTI_MONGOS_LB_URI = os.environ.get("MULTI_MONGOS_LB_URI")
87-
88-
if TEST_LOADBALANCER:
89-
res = parse_uri(SINGLE_MONGOS_LB_URI or "")
90-
host, port = res["nodelist"][0]
91-
db_user = res["username"] or db_user
92-
db_pwd = res["password"] or db_pwd
93-
94-
95-
# Shared KMS data.
96-
LOCAL_MASTER_KEY = base64.b64decode(
97-
b"Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ"
98-
b"5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk"
99-
)
100-
AWS_CREDS = {
101-
"accessKeyId": os.environ.get("FLE_AWS_KEY", ""),
102-
"secretAccessKey": os.environ.get("FLE_AWS_SECRET", ""),
103-
}
104-
AWS_CREDS_2 = {
105-
"accessKeyId": os.environ.get("FLE_AWS_KEY2", ""),
106-
"secretAccessKey": os.environ.get("FLE_AWS_SECRET2", ""),
107-
}
108-
AZURE_CREDS = {
109-
"tenantId": os.environ.get("FLE_AZURE_TENANTID", ""),
110-
"clientId": os.environ.get("FLE_AZURE_CLIENTID", ""),
111-
"clientSecret": os.environ.get("FLE_AZURE_CLIENTSECRET", ""),
112-
}
113-
GCP_CREDS = {
114-
"email": os.environ.get("FLE_GCP_EMAIL", ""),
115-
"privateKey": os.environ.get("FLE_GCP_PRIVATEKEY", ""),
116-
}
117-
KMIP_CREDS = {"endpoint": os.environ.get("FLE_KMIP_ENDPOINT", "localhost:5698")}
118-
119-
# Ensure Evergreen metadata doesn't result in truncation
120-
os.environ.setdefault("MONGOB_LOG_MAX_DOCUMENT_LENGTH", "2000")
121-
122-
123-
def is_server_resolvable():
124-
"""Returns True if 'server' is resolvable."""
125-
socket_timeout = socket.getdefaulttimeout()
126-
socket.setdefaulttimeout(1)
127-
try:
128-
try:
129-
socket.gethostbyname("server")
130-
return True
131-
except OSError:
132-
return False
133-
finally:
134-
socket.setdefaulttimeout(socket_timeout)
135-
136-
137-
def _create_user(authdb, user, pwd=None, roles=None, **kwargs):
138-
cmd = SON([("createUser", user)])
139-
# X509 doesn't use a password
140-
if pwd:
141-
cmd["pwd"] = pwd
142-
cmd["roles"] = roles or ["root"]
143-
cmd.update(**kwargs)
144-
return authdb.command(cmd)
145-
14631

14732
async def async_repl_set_step_down(client, **kwargs):
14833
"""Run replSetStepDown, first unfreezing a secondary with replSetFreeze."""
@@ -237,133 +122,10 @@ def __del__(self):
237122
raise Exception(msg)
238123

239124

240-
def _all_users(db):
241-
return {u["user"] for u in db.command("usersInfo").get("users", [])}
242-
243-
244-
def sanitize_cmd(cmd):
245-
cp = cmd.copy()
246-
cp.pop("$clusterTime", None)
247-
cp.pop("$db", None)
248-
cp.pop("$readPreference", None)
249-
cp.pop("lsid", None)
250-
if MONGODB_API_VERSION:
251-
# Stable API parameters
252-
cp.pop("apiVersion", None)
253-
# OP_MSG encoding may move the payload type one field to the
254-
# end of the command. Do the same here.
255-
name = next(iter(cp))
256-
try:
257-
identifier = message._FIELD_MAP[name]
258-
docs = cp.pop(identifier)
259-
cp[identifier] = docs
260-
except KeyError:
261-
pass
262-
return cp
263-
264-
265-
def sanitize_reply(reply):
266-
cp = reply.copy()
267-
cp.pop("$clusterTime", None)
268-
cp.pop("operationTime", None)
269-
return cp
270-
271-
272-
def print_thread_tracebacks() -> None:
273-
"""Print all Python thread tracebacks."""
274-
for thread_id, frame in sys._current_frames().items():
275-
sys.stderr.write(f"\n--- Traceback for thread {thread_id} ---\n")
276-
traceback.print_stack(frame, file=sys.stderr)
277-
278-
279-
def print_thread_stacks(pid: int) -> None:
280-
"""Print all C-level thread stacks for a given process id."""
281-
if sys.platform == "darwin":
282-
cmd = ["lldb", "--attach-pid", f"{pid}", "--batch", "--one-line", '"thread backtrace all"']
283-
else:
284-
cmd = ["gdb", f"--pid={pid}", "--batch", '--eval-command="thread apply all bt"']
285-
286-
try:
287-
res = subprocess.run(
288-
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding="utf-8"
289-
)
290-
except Exception as exc:
291-
sys.stderr.write(f"Could not print C-level thread stacks because {cmd[0]} failed: {exc}")
292-
else:
293-
sys.stderr.write(res.stdout)
294-
295-
296125
# Global knobs to speed up the test suite.
297126
global_knobs = client_knobs(events_queue_frequency=0.05)
298127

299128

300-
def _get_executors(topology):
301-
executors = []
302-
for server in topology._servers.values():
303-
# Some MockMonitor do not have an _executor.
304-
if hasattr(server._monitor, "_executor"):
305-
executors.append(server._monitor._executor)
306-
if hasattr(server._monitor, "_rtt_monitor"):
307-
executors.append(server._monitor._rtt_monitor._executor)
308-
executors.append(topology._Topology__events_executor)
309-
if topology._srv_monitor:
310-
executors.append(topology._srv_monitor._executor)
311-
312-
return [e for e in executors if e is not None]
313-
314-
315-
def print_running_topology(topology):
316-
running = [e for e in _get_executors(topology) if not e._stopped]
317-
if running:
318-
print(
319-
"WARNING: found Topology with running threads:\n"
320-
f" Threads: {running}\n"
321-
f" Topology: {topology}\n"
322-
f" Creation traceback:\n{topology._settings._stack}"
323-
)
324-
325-
326-
def test_cases(suite):
327-
"""Iterator over all TestCases within a TestSuite."""
328-
for suite_or_case in suite._tests:
329-
if isinstance(suite_or_case, unittest.TestCase):
330-
# unittest.TestCase
331-
yield suite_or_case
332-
else:
333-
# unittest.TestSuite
334-
yield from test_cases(suite_or_case)
335-
336-
337-
# Helper method to workaround https://bugs.python.org/issue21724
338-
def clear_warning_registry():
339-
"""Clear the __warningregistry__ for all modules."""
340-
for _, module in list(sys.modules.items()):
341-
if hasattr(module, "__warningregistry__"):
342-
module.__warningregistry__ = {} # type:ignore[attr-defined]
343-
344-
345-
class SystemCertsPatcher:
346-
def __init__(self, ca_certs):
347-
if (
348-
ssl.OPENSSL_VERSION.lower().startswith("libressl")
349-
and sys.platform == "darwin"
350-
and not _ssl.IS_PYOPENSSL
351-
):
352-
raise SkipTest(
353-
"LibreSSL on OSX doesn't support setting CA certificates "
354-
"using SSL_CERT_FILE environment variable."
355-
)
356-
self.original_certs = os.environ.get("SSL_CERT_FILE")
357-
# Tell OpenSSL where CA certificates live.
358-
os.environ["SSL_CERT_FILE"] = ca_certs
359-
360-
def disable(self):
361-
if self.original_certs is None:
362-
os.environ.pop("SSL_CERT_FILE")
363-
else:
364-
os.environ["SSL_CERT_FILE"] = self.original_certs
365-
366-
367129
if _IS_SYNC:
368130
PARENT = threading.Thread
369131
else:

0 commit comments

Comments
 (0)