diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 46c86103ad..753c1ac28b 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -42,3 +42,23 @@ post: - func: "upload mo artifacts" - func: "upload test results" - func: "cleanup" + +tasks: + - name: resync_specs + commands: + - command: subprocess.exec + params: + binary: bash + include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] + args: + - .evergreen/scripts/resync-all-specs.sh + working_dir: src + +buildvariants: + - name: resync_specs + display_name: "Resync Specs" + run_on: rhel80-small + cron: '0 16 * * MON' + patchable: false + tasks: + - name: resync_specs diff --git a/.evergreen/patch/diff.patch b/.evergreen/patch/diff.patch new file mode 100644 index 0000000000..f0a5a09166 --- /dev/null +++ b/.evergreen/patch/diff.patch @@ -0,0 +1,1327 @@ +diff --git a/test/load_balancer/cursors.json b/test/load_balancer/cursors.json +index 27aaddd5..b11bf2c6 100644 +--- a/test/load_balancer/cursors.json ++++ b/test/load_balancer/cursors.json +@@ -385,7 +385,7 @@ + ] + }, + { +- "description": "pinned connections are returned after an network error during getMore", ++ "description": "pinned connections are not returned after an network error during getMore", + "operations": [ + { + "name": "failPoint", +@@ -449,7 +449,7 @@ + "object": "testRunner", + "arguments": { + "client": "client0", +- "connections": 0 ++ "connections": 1 + } + }, + { +@@ -677,7 +677,7 @@ + ] + }, + { +- "description": "pinned connections are returned to the pool after a non-network error on getMore", ++ "description": "pinned connections are not returned to the pool after a non-network error on getMore", + "operations": [ + { + "name": "failPoint", +@@ -733,7 +733,7 @@ + "object": "testRunner", + "arguments": { + "client": "client0", +- "connections": 0 ++ "connections": 1 + } + }, + { +diff --git a/test/server_selection_logging/operation-id.json b/test/server_selection_logging/operation-id.json +index ccc26231..72ebff60 100644 +--- a/test/server_selection_logging/operation-id.json ++++ b/test/server_selection_logging/operation-id.json +@@ -197,7 +197,7 @@ + } + }, + { +- "level": "debug", ++ "level": "info", + "component": "serverSelection", + "data": { + "message": "Waiting for suitable server to become available", +@@ -383,7 +383,7 @@ + } + }, + { +- "level": "debug", ++ "level": "info", + "component": "serverSelection", + "data": { + "message": "Waiting for suitable server to become available", +diff --git a/test/server_selection_logging/replica-set.json b/test/server_selection_logging/replica-set.json +index 830b1ea5..5eba784b 100644 +--- a/test/server_selection_logging/replica-set.json ++++ b/test/server_selection_logging/replica-set.json +@@ -184,7 +184,7 @@ + } + }, + { +- "level": "debug", ++ "level": "info", + "component": "serverSelection", + "data": { + "message": "Waiting for suitable server to become available", +diff --git a/test/server_selection_logging/sharded.json b/test/server_selection_logging/sharded.json +index 346c050f..d42fba91 100644 +--- a/test/server_selection_logging/sharded.json ++++ b/test/server_selection_logging/sharded.json +@@ -193,7 +193,7 @@ + } + }, + { +- "level": "debug", ++ "level": "info", + "component": "serverSelection", + "data": { + "message": "Waiting for suitable server to become available", +diff --git a/test/client-side-encryption/spec/unified/fle2v2-BypassQueryAnalysis.json b/test/client-side-encryption/spec/unified/fle2v2-BypassQueryAnalysis.json +new file mode 100644 +index 00000000..0817508f +--- /dev/null ++++ b/test/client-side-encryption/spec/unified/fle2v2-BypassQueryAnalysis.json +@@ -0,0 +1,322 @@ ++{ ++ "description": "fle2v2-BypassQueryAnalysis", ++ "schemaVersion": "1.23", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "7.0.0", ++ "serverless": "forbid", ++ "csfle": true, ++ "topologies": [ ++ "replicaset", ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "autoEncryptOpts": { ++ "kmsProviders": { ++ "local": { ++ "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" ++ } ++ }, ++ "keyVaultNamespace": "keyvault.datakeys", ++ "bypassQueryAnalysis": true ++ }, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "encryptedDB", ++ "client": "client0", ++ "databaseName": "default" ++ } ++ }, ++ { ++ "collection": { ++ "id": "encryptedColl", ++ "database": "encryptedDB", ++ "collectionName": "default" ++ } ++ }, ++ { ++ "client": { ++ "id": "client1" ++ } ++ }, ++ { ++ "database": { ++ "id": "unencryptedDB", ++ "client": "client1", ++ "databaseName": "default" ++ } ++ }, ++ { ++ "collection": { ++ "id": "unencryptedColl", ++ "database": "unencryptedDB", ++ "collectionName": "default" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "keyvault", ++ "collectionName": "datakeys", ++ "documents": [ ++ { ++ "_id": { ++ "$binary": { ++ "base64": "EjRWeBI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "keyMaterial": { ++ "$binary": { ++ "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", ++ "subType": "00" ++ } ++ }, ++ "creationDate": { ++ "$date": { ++ "$numberLong": "1648914851981" ++ } ++ }, ++ "updateDate": { ++ "$date": { ++ "$numberLong": "1648914851981" ++ } ++ }, ++ "status": { ++ "$numberInt": "0" ++ }, ++ "masterKey": { ++ "provider": "local" ++ } ++ } ++ ] ++ }, ++ { ++ "databaseName": "default", ++ "collectionName": "default", ++ "documents": [], ++ "createOptions": { ++ "encryptedFields": { ++ "fields": [ ++ { ++ "keyId": { ++ "$binary": { ++ "base64": "EjRWeBI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "path": "encryptedIndexed", ++ "bsonType": "string", ++ "queries": { ++ "queryType": "equality", ++ "contention": { ++ "$numberLong": "0" ++ } ++ } ++ }, ++ { ++ "keyId": { ++ "$binary": { ++ "base64": "q83vqxI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "path": "encryptedUnindexed", ++ "bsonType": "string" ++ } ++ ] ++ } ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "BypassQueryAnalysis decrypts", ++ "operations": [ ++ { ++ "object": "encryptedColl", ++ "name": "insertOne", ++ "arguments": { ++ "document": { ++ "_id": 1, ++ "encryptedIndexed": { ++ "$binary": { ++ "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", ++ "subType": "06" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "object": "encryptedColl", ++ "name": "find", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "encryptedIndexed": "123" ++ } ++ ] ++ }, ++ { ++ "object": "unencryptedColl", ++ "name": "find", ++ "arguments": { ++ "filter": {} ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "encryptedIndexed": { ++ "$$type": "binData" ++ }, ++ "__safeContent__": [ ++ { ++ "$binary": { ++ "base64": "31eCYlbQoVboc5zwC8IoyJVSkag9PxREka8dkmbXJeY=", ++ "subType": "00" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "command": { ++ "listCollections": 1, ++ "filter": { ++ "name": "default" ++ } ++ }, ++ "commandName": "listCollections" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "insert": "default", ++ "documents": [ ++ { ++ "_id": 1, ++ "encryptedIndexed": { ++ "$binary": { ++ "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", ++ "subType": "06" ++ } ++ } ++ } ++ ], ++ "ordered": true, ++ "encryptionInformation": { ++ "type": 1, ++ "schema": { ++ "default.default": { ++ "escCollection": "enxcol_.default.esc", ++ "ecocCollection": "enxcol_.default.ecoc", ++ "fields": [ ++ { ++ "keyId": { ++ "$binary": { ++ "base64": "EjRWeBI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "path": "encryptedIndexed", ++ "bsonType": "string", ++ "queries": { ++ "queryType": "equality", ++ "contention": { ++ "$numberLong": "0" ++ } ++ } ++ }, ++ { ++ "keyId": { ++ "$binary": { ++ "base64": "q83vqxI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "path": "encryptedUnindexed", ++ "bsonType": "string" ++ } ++ ] ++ } ++ } ++ } ++ }, ++ "commandName": "insert" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "default", ++ "filter": { ++ "_id": 1 ++ } ++ }, ++ "commandName": "find" ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "command": { ++ "find": "datakeys", ++ "filter": { ++ "$or": [ ++ { ++ "_id": { ++ "$in": [ ++ { ++ "$binary": { ++ "base64": "EjRWeBI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "keyAltNames": { ++ "$in": [] ++ } ++ } ++ ] ++ }, ++ "$db": "keyvault", ++ "readConcern": { ++ "level": "majority" ++ } ++ }, ++ "commandName": "find" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/client-side-encryption/spec/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json b/test/client-side-encryption/spec/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json +new file mode 100644 +index 00000000..b5f848c0 +--- /dev/null ++++ b/test/client-side-encryption/spec/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json +@@ -0,0 +1,256 @@ ++{ ++ "description": "fle2v2-EncryptedFields-vs-EncryptedFieldsMap", ++ "schemaVersion": "1.23", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "7.0.0", ++ "serverless": "forbid", ++ "csfle": true, ++ "topologies": [ ++ "replicaset", ++ "sharded", ++ "load-balanced" ++ ] ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "autoEncryptOpts": { ++ "kmsProviders": { ++ "local": { ++ "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" ++ } ++ }, ++ "keyVaultNamespace": "keyvault.datakeys", ++ "encryptedFieldsMap": { ++ "default.default": { ++ "fields": [] ++ } ++ } ++ }, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "encryptedDB", ++ "client": "client0", ++ "databaseName": "default" ++ } ++ }, ++ { ++ "collection": { ++ "id": "encryptedColl", ++ "database": "encryptedDB", ++ "collectionName": "default" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "keyvault", ++ "collectionName": "datakeys", ++ "documents": [ ++ { ++ "_id": { ++ "$binary": { ++ "base64": "q83vqxI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "keyMaterial": { ++ "$binary": { ++ "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", ++ "subType": "00" ++ } ++ }, ++ "creationDate": { ++ "$date": { ++ "$numberLong": "1648914851981" ++ } ++ }, ++ "updateDate": { ++ "$date": { ++ "$numberLong": "1648914851981" ++ } ++ }, ++ "status": { ++ "$numberInt": "0" ++ }, ++ "masterKey": { ++ "provider": "local" ++ } ++ } ++ ] ++ }, ++ { ++ "databaseName": "default", ++ "collectionName": "default", ++ "documents": [], ++ "createOptions": { ++ "encryptedFields": { ++ "fields": [ ++ { ++ "keyId": { ++ "$binary": { ++ "base64": "EjRWeBI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "path": "encryptedIndexed", ++ "bsonType": "string", ++ "queries": { ++ "queryType": "equality", ++ "contention": { ++ "$numberLong": "0" ++ } ++ } ++ }, ++ { ++ "keyId": { ++ "$binary": { ++ "base64": "q83vqxI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "path": "encryptedUnindexed", ++ "bsonType": "string" ++ } ++ ] ++ } ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "encryptedFieldsMap is preferred over remote encryptedFields", ++ "operations": [ ++ { ++ "object": "encryptedColl", ++ "name": "insertOne", ++ "arguments": { ++ "document": { ++ "_id": 1, ++ "encryptedUnindexed": { ++ "$binary": { ++ "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", ++ "subType": "06" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "object": "encryptedColl", ++ "name": "find", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "encryptedUnindexed": "value123" ++ } ++ ] ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "databaseName": "default", ++ "commandName": "insert", ++ "command": { ++ "insert": "default", ++ "documents": [ ++ { ++ "_id": 1, ++ "encryptedUnindexed": { ++ "$binary": { ++ "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", ++ "subType": "06" ++ } ++ } ++ } ++ ], ++ "ordered": true ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "databaseName": "default", ++ "commandName": "find", ++ "command": { ++ "find": "default", ++ "filter": { ++ "_id": 1 ++ } ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "databaseName": "keyvault", ++ "commandName": "find", ++ "command": { ++ "find": "datakeys", ++ "filter": { ++ "$or": [ ++ { ++ "_id": { ++ "$in": [ ++ { ++ "$binary": { ++ "base64": "q83vqxI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "keyAltNames": { ++ "$in": [] ++ } ++ } ++ ] ++ }, ++ "$db": "keyvault", ++ "readConcern": { ++ "level": "majority" ++ } ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "default", ++ "databaseName": "default", ++ "documents": [ ++ { ++ "_id": 1, ++ "encryptedUnindexed": { ++ "$binary": { ++ "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", ++ "subType": "06" ++ } ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/client-side-encryption/spec/unified/localSchema.json b/test/client-side-encryption/spec/unified/localSchema.json +new file mode 100644 +index 00000000..a7acccac +--- /dev/null ++++ b/test/client-side-encryption/spec/unified/localSchema.json +@@ -0,0 +1,342 @@ ++{ ++ "description": "localSchema", ++ "schemaVersion": "1.23", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "4.1.10", ++ "csfle": true ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "autoEncryptOpts": { ++ "schemaMap": { ++ "default.default": { ++ "properties": { ++ "encrypted_w_altname": { ++ "encrypt": { ++ "keyId": "/altname", ++ "bsonType": "string", ++ "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" ++ } ++ }, ++ "encrypted_string": { ++ "encrypt": { ++ "keyId": [ ++ { ++ "$binary": { ++ "base64": "AAAAAAAAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ } ++ ], ++ "bsonType": "string", ++ "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" ++ } ++ }, ++ "random": { ++ "encrypt": { ++ "keyId": [ ++ { ++ "$binary": { ++ "base64": "AAAAAAAAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ } ++ ], ++ "bsonType": "string", ++ "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" ++ } ++ }, ++ "encrypted_string_equivalent": { ++ "encrypt": { ++ "keyId": [ ++ { ++ "$binary": { ++ "base64": "AAAAAAAAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ } ++ ], ++ "bsonType": "string", ++ "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" ++ } ++ } ++ }, ++ "bsonType": "object" ++ } ++ }, ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "aws": { ++ "accessKeyId": { ++ "$$placeholder": 1 ++ }, ++ "secretAccessKey": { ++ "$$placeholder": 1 ++ }, ++ "sessionToken": { ++ "$$placeholder": 1 ++ } ++ } ++ } ++ }, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "client": { ++ "id": "client1", ++ "autoEncryptOpts": { ++ "schemaMap": { ++ "default.default": { ++ "properties": { ++ "test": { ++ "bsonType": "string" ++ } ++ }, ++ "bsonType": "object", ++ "required": [ ++ "test" ++ ] ++ } ++ }, ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "aws": { ++ "accessKeyId": { ++ "$$placeholder": 1 ++ }, ++ "secretAccessKey": { ++ "$$placeholder": 1 ++ }, ++ "sessionToken": { ++ "$$placeholder": 1 ++ } ++ } ++ } ++ }, ++ "observeEvents": [ ++ "commandStartedEvent" ++ ] ++ } ++ }, ++ { ++ "database": { ++ "id": "encryptedDB", ++ "client": "client0", ++ "databaseName": "default" ++ } ++ }, ++ { ++ "collection": { ++ "id": "encryptedColl", ++ "database": "encryptedDB", ++ "collectionName": "default" ++ } ++ }, ++ { ++ "database": { ++ "id": "encryptedDB2", ++ "client": "client1", ++ "databaseName": "default" ++ } ++ }, ++ { ++ "collection": { ++ "id": "encryptedColl2", ++ "database": "encryptedDB2", ++ "collectionName": "default" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "keyvault", ++ "collectionName": "datakeys", ++ "documents": [ ++ { ++ "status": 1, ++ "_id": { ++ "$binary": { ++ "base64": "AAAAAAAAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ }, ++ "masterKey": { ++ "provider": "aws", ++ "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", ++ "region": "us-east-1" ++ }, ++ "updateDate": { ++ "$date": { ++ "$numberLong": "1552949630483" ++ } ++ }, ++ "keyMaterial": { ++ "$binary": { ++ "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", ++ "subType": "00" ++ } ++ }, ++ "creationDate": { ++ "$date": { ++ "$numberLong": "1552949630483" ++ } ++ }, ++ "keyAltNames": [ ++ "altname", ++ "another_altname" ++ ] ++ } ++ ] ++ }, ++ { ++ "databaseName": "default", ++ "collectionName": "default", ++ "documents": [] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "A local schema should override", ++ "operations": [ ++ { ++ "object": "encryptedColl", ++ "name": "insertOne", ++ "arguments": { ++ "document": { ++ "_id": 1, ++ "encrypted_string": "string0" ++ } ++ } ++ }, ++ { ++ "object": "encryptedColl", ++ "name": "find", ++ "arguments": { ++ "filter": { ++ "_id": 1 ++ } ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "encrypted_string": "string0" ++ } ++ ] ++ } ++ ], ++ "expectEvents": [ ++ { ++ "client": "client0", ++ "events": [ ++ { ++ "commandStartedEvent": { ++ "databaseName": "keyvault", ++ "commandName": "find", ++ "command": { ++ "find": "datakeys", ++ "filter": { ++ "$or": [ ++ { ++ "_id": { ++ "$in": [ ++ { ++ "$binary": { ++ "base64": "AAAAAAAAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ } ++ ] ++ } ++ }, ++ { ++ "keyAltNames": { ++ "$in": [] ++ } ++ } ++ ] ++ }, ++ "readConcern": { ++ "level": "majority" ++ } ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "insert", ++ "command": { ++ "insert": "default", ++ "documents": [ ++ { ++ "_id": 1, ++ "encrypted_string": { ++ "$binary": { ++ "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", ++ "subType": "06" ++ } ++ } ++ } ++ ], ++ "ordered": true ++ } ++ } ++ }, ++ { ++ "commandStartedEvent": { ++ "commandName": "find", ++ "command": { ++ "find": "default", ++ "filter": { ++ "_id": 1 ++ } ++ } ++ } ++ } ++ ] ++ } ++ ], ++ "outcome": [ ++ { ++ "collectionName": "default", ++ "databaseName": "default", ++ "documents": [ ++ { ++ "_id": 1, ++ "encrypted_string": { ++ "$binary": { ++ "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", ++ "subType": "06" ++ } ++ } ++ } ++ ] ++ } ++ ] ++ }, ++ { ++ "description": "A local schema with no encryption is an error", ++ "operations": [ ++ { ++ "object": "encryptedColl2", ++ "name": "insertOne", ++ "arguments": { ++ "document": { ++ "_id": 1, ++ "encrypted_string": "string0" ++ } ++ }, ++ "expectError": { ++ "isClientError": true ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/client-side-encryption/spec/unified/maxWireVersion.json b/test/client-side-encryption/spec/unified/maxWireVersion.json +new file mode 100644 +index 00000000..d0af75ac +--- /dev/null ++++ b/test/client-side-encryption/spec/unified/maxWireVersion.json +@@ -0,0 +1,101 @@ ++{ ++ "description": "maxWireVersion", ++ "schemaVersion": "1.23", ++ "runOnRequirements": [ ++ { ++ "maxServerVersion": "4.0.99", ++ "csfle": true ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "autoEncryptOpts": { ++ "kmsProviders": { ++ "aws": {} ++ }, ++ "keyVaultNamespace": "keyvault.datakeys", ++ "extraOptions": { ++ "mongocryptdBypassSpawn": true ++ } ++ } ++ } ++ }, ++ { ++ "database": { ++ "id": "database0", ++ "client": "client0", ++ "databaseName": "default" ++ } ++ }, ++ { ++ "collection": { ++ "id": "collection0", ++ "database": "database0", ++ "collectionName": "default" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "keyvault", ++ "collectionName": "datakeys", ++ "documents": [ ++ { ++ "status": 1, ++ "_id": { ++ "$binary": { ++ "base64": "AAAAAAAAAAAAAAAAAAAAAA==", ++ "subType": "04" ++ } ++ }, ++ "masterKey": { ++ "provider": "aws", ++ "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", ++ "region": "us-east-1" ++ }, ++ "updateDate": { ++ "$date": { ++ "$numberLong": "1552949630483" ++ } ++ }, ++ "keyMaterial": { ++ "$binary": { ++ "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", ++ "subType": "00" ++ } ++ }, ++ "creationDate": { ++ "$date": { ++ "$numberLong": "1552949630483" ++ } ++ }, ++ "keyAltNames": [ ++ "altname", ++ "another_altname" ++ ] ++ } ++ ] ++ } ++ ], ++ "tests": [ ++ { ++ "description": "operation fails with maxWireVersion < 8", ++ "operations": [ ++ { ++ "name": "insertOne", ++ "object": "collection0", ++ "arguments": { ++ "document": { ++ "encrypted_string": "string0" ++ } ++ }, ++ "expectError": { ++ "errorContains": "Auto-encryption requires a minimum MongoDB version of 4.2" ++ } ++ } ++ ] ++ } ++ ] ++} +diff --git a/test/unified-test-format/valid-pass/poc-queryable-encryption.json b/test/unified-test-format/valid-pass/poc-queryable-encryption.json +new file mode 100644 +index 00000000..9788977c +--- /dev/null ++++ b/test/unified-test-format/valid-pass/poc-queryable-encryption.json +@@ -0,0 +1,188 @@ ++{ ++ "description": "poc-queryable-encryption", ++ "schemaVersion": "1.23", ++ "runOnRequirements": [ ++ { ++ "minServerVersion": "7.0", ++ "csfle": true ++ } ++ ], ++ "createEntities": [ ++ { ++ "client": { ++ "id": "client0", ++ "autoEncryptOpts": { ++ "keyVaultNamespace": "keyvault.datakeys", ++ "kmsProviders": { ++ "local": { ++ "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" ++ } ++ } ++ } ++ } ++ }, ++ { ++ "database": { ++ "id": "encryptedDB", ++ "client": "client0", ++ "databaseName": "poc-queryable-encryption" ++ } ++ }, ++ { ++ "collection": { ++ "id": "encryptedColl", ++ "database": "encryptedDB", ++ "collectionName": "encrypted" ++ } ++ }, ++ { ++ "client": { ++ "id": "client1" ++ } ++ }, ++ { ++ "database": { ++ "id": "unencryptedDB", ++ "client": "client1", ++ "databaseName": "poc-queryable-encryption" ++ } ++ }, ++ { ++ "collection": { ++ "id": "unencryptedColl", ++ "database": "unencryptedDB", ++ "collectionName": "encrypted" ++ } ++ } ++ ], ++ "initialData": [ ++ { ++ "databaseName": "keyvault", ++ "collectionName": "datakeys", ++ "documents": [ ++ { ++ "_id": { ++ "$binary": { ++ "base64": "EjRWeBI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "keyMaterial": { ++ "$binary": { ++ "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", ++ "subType": "00" ++ } ++ }, ++ "creationDate": { ++ "$date": { ++ "$numberLong": "1641024000000" ++ } ++ }, ++ "updateDate": { ++ "$date": { ++ "$numberLong": "1641024000000" ++ } ++ }, ++ "status": 1, ++ "masterKey": { ++ "provider": "local" ++ } ++ } ++ ] ++ }, ++ { ++ "databaseName": "poc-queryable-encryption", ++ "collectionName": "encrypted", ++ "documents": [], ++ "createOptions": { ++ "encryptedFields": { ++ "fields": [ ++ { ++ "keyId": { ++ "$binary": { ++ "base64": "EjRWeBI0mHYSNBI0VniQEg==", ++ "subType": "04" ++ } ++ }, ++ "path": "encryptedInt", ++ "bsonType": "int", ++ "queries": { ++ "queryType": "equality", ++ "contention": { ++ "$numberLong": "0" ++ } ++ } ++ } ++ ] ++ } ++ } ++ } ++ ], ++ "tests": [ ++ { ++ "description": "insert, replace, and find with queryable encryption", ++ "operations": [ ++ { ++ "object": "encryptedColl", ++ "name": "insertOne", ++ "arguments": { ++ "document": { ++ "_id": 1, ++ "encryptedInt": 11 ++ } ++ } ++ }, ++ { ++ "object": "encryptedColl", ++ "name": "replaceOne", ++ "arguments": { ++ "filter": { ++ "encryptedInt": 11 ++ }, ++ "replacement": { ++ "encryptedInt": 22 ++ } ++ } ++ }, ++ { ++ "object": "encryptedColl", ++ "name": "find", ++ "arguments": { ++ "filter": { ++ "encryptedInt": 22 ++ } ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "encryptedInt": 22 ++ } ++ ] ++ }, ++ { ++ "object": "unencryptedColl", ++ "name": "find", ++ "arguments": { ++ "filter": {} ++ }, ++ "expectResult": [ ++ { ++ "_id": 1, ++ "encryptedInt": { ++ "$$type": "binData" ++ }, ++ "__safeContent__": [ ++ { ++ "$binary": { ++ "base64": "rhS16TJojgDDBtbluxBokvcotP1mQTGeYpNt8xd3MJQ=", ++ "subType": "00" ++ } ++ } ++ ] ++ } ++ ] ++ } ++ ] ++ } ++ ] ++} diff --git a/.evergreen/patch/new.patch b/.evergreen/patch/new.patch new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.evergreen/patch/update.patch b/.evergreen/patch/update.patch new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.evergreen/resync-specs.sh b/.evergreen/resync-specs.sh index d7dfafbba9..9895a16c36 100755 --- a/.evergreen/resync-specs.sh +++ b/.evergreen/resync-specs.sh @@ -45,9 +45,12 @@ then fi # Ensure the JSON files are up to date. -cd $SPECS/source -make -cd - +if ! [ -n "${CI:-}" ] +then + cd $SPECS/source + make + cd - +fi # cpjson unified-test-format/tests/invalid unified-test-format/invalid # * param1: Path to spec tests dir in specifications repo # * param2: Path to where the corresponding tests live in Python. diff --git a/.evergreen/scripts/create-pr.sh b/.evergreen/scripts/create-pr.sh new file mode 100755 index 0000000000..9298d76963 --- /dev/null +++ b/.evergreen/scripts/create-pr.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +tools="../drivers-evergreen-tools" +git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $tools + +pushd $tools/.evergreen/github_app || exit + +owner="mongodb" +repo="mongo-python-driver" + +# Bootstrap the app. +echo "bootstrapping" +source utils.sh +bootstrap drivers/comment-bot + +# Run the app. +source ./secrets-export.sh + +# Get a github access token for the git checkout. +echo "Getting github token..." + +token=$(bash ./get-access-token.sh $repo $owner) +if [ -z "${token}" ]; then + echo "Failed to get github access token!" + popd || exit + exit 1 +fi +echo "Getting github token... done." +popd || exit + +# Make the git checkout and create a new branch. +echo "Creating the git checkout..." +branch="spec-resync-"$(date '+%m-%d-%Y') + +git remote set-url origin https://x-access-token:${token}@github.com/$owner/$repo.git +git checkout -b $branch "origin/master" +git add ./test +git apply -R --allow-empty .evergreen/patch/* +git commit -am "resyncing specs $(date '+%m-%d-%Y')" +echo "Creating the git checkout... done." + +git push origin $branch +resp=$(curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $token" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -d "{\"title\":\"[Spec Resync] $(date '+%m-%d-%Y')\",\"body\":\"$(cat "$1")\",\"head\":\"${branch}\",\"base\":\"master\"}" \ + --url https://api.github.com/repos/$owner/$repo/pulls) +echo $resp | jq '.html_url' +echo "Creating the PR... done." + +rm -rf $tools diff --git a/.evergreen/scripts/resync-all-specs.py b/.evergreen/scripts/resync-all-specs.py new file mode 100644 index 0000000000..b94547777b --- /dev/null +++ b/.evergreen/scripts/resync-all-specs.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import argparse +import os +import pathlib +import subprocess +from subprocess import CalledProcessError + + +def resync_specs(directory: pathlib.Path, errored: dict[str, str]) -> list[str]: + """Actually sync the specs""" + for spec in os.scandir(directory): + if not spec.is_dir(): + continue + + if spec.name in ["asynchronous"]: + continue + try: + subprocess.run( + ["bash", "./.evergreen/resync-specs.sh", spec.name], # noqa: S603, S607 + capture_output=True, + text=True, + check=True, + ) + except CalledProcessError as exc: + errored[spec.name] = exc.stderr + + process = subprocess.run( + ["git diff --name-only | awk -F'/' '{print $2}' | sort | uniq"], # noqa: S607 + shell=True, # noqa: S602 + capture_output=True, + text=True, + check=True, + ) + # return successfully synced specs + return process.stdout.strip().split() + + +def check_new_spec_directories(directory: pathlib.Path) -> list[str]: + """Check to see if there are any directories in the spec repo that don't exist in pymongo/test""" + spec_dir = pathlib.Path(os.environ["MDB_SPECS"]) / "source" + spec_set = { + entry.name.replace("-", "_") + for entry in os.scandir(spec_dir) + if entry.is_dir() + and (pathlib.Path(entry.path) / "tests").is_dir() + and len(list(os.scandir(pathlib.Path(entry.path) / "tests"))) > 1 + } + test_set = {entry.name.replace("-", "_") for entry in os.scandir(directory) if entry.is_dir()} + known_mappings = { + "ocsp_support": "ocsp", + "client_side_operations_timeout": "csot", + "mongodb_handshake": "handshake", + "load_balancers": "load_balancer", + "atlas_data_lake_testing": "atlas", + "connection_monitoring_and_pooling": "connection_monitoring", + "command_logging_and_monitoring": "command_logging", + "initial_dns_seedlist_discovery": "srv_seedlist", + "server_discovery_and_monitoring": "sdam_monitoring", + } + + for k, v in known_mappings.items(): + if k in spec_set: + spec_set.remove(k) + spec_set.add(v) + return list(spec_set - test_set) + + +def write_summary(succeeded: list[str], errored: dict[str, str], new: list[str]) -> None: + """Generate the PR description""" + pr_body = "" + if len(succeeded) > 0: + pr_body += "The following specs were changed:\n- " + pr_body += "\n -".join(new) + pr_body += "\n" + if len(errored) > 0: + pr_body += "\n\nThe following spec syncs encountered errors:" + for k, v in errored.items(): + pr_body += f"\n- {k}\n```{v}\n```" + pr_body += "\n" + if len(new) > 0: + pr_body += "\n\nThe following directories are in the specification repository and not in our test directory:" + pr_body += "\n -".join(new) + pr_body += "\n" + + if pr_body != "": + with open("spec_sync.txt", "w") as f: + # replacements made for proper json + f.write(pr_body.replace("\n", "\\n").replace("\t", "\\t")) + + +def main(): + directory = pathlib.Path("./test") + errored: dict[str, str] = {} + succeeded = resync_specs(directory, errored) + new = check_new_spec_directories(directory) + write_summary(succeeded, errored, new) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Python Script to resync all specs and generate summary for PR." + ) + parser.add_argument("filename", help="Name of file for the summary to be written into.") + args = parser.parse_args() + main() diff --git a/.evergreen/scripts/resync-all-specs.sh b/.evergreen/scripts/resync-all-specs.sh new file mode 100755 index 0000000000..f0464ae6f2 --- /dev/null +++ b/.evergreen/scripts/resync-all-specs.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Run spec syncing script and create PR + +# SETUP +SRC_URL="https://github.com/mongodb/specifications.git" +# needs to be set for resunc-specs.sh +SPEC_SRC="$(realpath -s "../specifications")" +SCRIPT="$(realpath -s "./.evergreen/resync-specs.sh")" + +# Clone the spec repo if the directory does not exist +if [[ ! -d $SPEC_SRC ]]; then + git clone $SRC_URL $SPEC_SRC + if [[ $? -ne 0 ]]; then + echo "Error: Failed to clone repository." + exit 1 + fi +fi + +# Set environment variable to the cloned spec repo for resync-specs.sh +export MDB_SPECS="$SPEC_SRC" + +# Check that resync-specs.sh exists and is executable +if [[ ! -x $SCRIPT ]]; then + echo "Error: $SCRIPT not found or is not executable." + exit 1 +fi + +PR_DESC="spec_sync.txt" + +# run python script that actually does all the resyncing +/opt/devtools/bin/python3.11 ./.evergreen/scripts/resync-all-specs.py "$PR_DESC" + + +if [[ -f $PR_DESC ]]; then + # changes were made -> call scrypt to create PR for us + .evergreen/scripts/create-pr.sh "$PR_DESC" + rm "$PR_DESC" +fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca98584602..9a774d2973 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -432,6 +432,35 @@ update in PyMongo. This is primarily helpful if you are implementing a new feature in PyMongo that has spec tests already implemented, or if you are attempting to validate new spec tests in PyMongo. +### Automated Specification Test Resyncing +There is a script (`/.evergreen/scripts/resync-all-specs.sh`) that will +automatically run once a week to resync all the specs with the [specifications +repo](https://github.com/mongodb/specifications). +If there are changes, a PR will be generated by mongodb-drivers-pr-bot. +If any errors occurred, the PR description will display the name of the spec along +with stderr from the `bash resync-spec.sh ` command. + +There are three patch files that contain known test differences between +PyMongo's tests and the specification: +`.evergreen/patch/diff.patch`: tests where PyMongo intentionally behaves differently than the specification. +This file should rarely be modified, as most of these represent fundamental differences in design or behavior. +`.evergreen/patch/new.patch`: tests for new features that have not yet been implemented in PyMongo. +Tests must be removed from this file as part of the PR that implements their feature. +`.evergreen/patch/update.patch`: tests that have been updated in a way that causes them to fail until their +associated driver change is implemented. Tests must be removed from this file as part of the PR that implements +the fix or behavioral change. + +#### Adding to a patch file +Assuming the changes are committed somewhere, to add to any of the +patch files, run `git diff` to show the desired changes and paste the +results into the patch file. + +For example, there are new test files on the most recent commit of the current branch that are failing. +To add those changes to `new.patch`, I would do the following: +```bash +git diff HEAD~1 path/to/new/file >> .evergreen/patch/new.patch +``` + ## Making a Release Follow the [Python Driver Release Process Wiki](https://wiki.corp.mongodb.com/display/DRIVERS/Python+Driver+Release+Process).