Skip to content

Commit 7e3108b

Browse files
vbabaninrozza
andauthored
Integrate mongodb-crypt module (#1487)
* Integrate mongodb-crypt module into mongo-java-driver as a new Gradle project. * Add runtimeElements to GraalVM script, as this configuration is meant to be used by consumers, to retrieve all the elements necessary to run against this library (imitating transitive dependency resolution). JAVA-5582 Co-authored-by: Ross Lawley <[email protected]>
1 parent b31e7c2 commit 7e3108b

File tree

66 files changed

+5789
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+5789
-3
lines changed

build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ ext {
5555
zstdVersion = '1.5.5-3'
5656
awsSdkV2Version = '2.18.9'
5757
awsSdkV1Version = '1.12.337'
58-
mongoCryptVersion = '1.11.0'
5958
projectReactorVersion = '2022.0.0'
6059
junitBomVersion = '5.10.2'
6160
logbackVersion = '1.3.14'

config/spotbugs/exclude.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,10 @@
260260
<Bug pattern="NP_NONNULL_RETURN_VIOLATION"/>
261261
</Match>
262262

263+
<!-- mongocrypt -->
264+
<Match>
265+
<Class name="com.mongodb.crypt.capi.CAPI$cstring"/>
266+
<Bug pattern="NM_CLASS_NAMING_CONVENTION"/>
267+
</Match>
268+
263269
</FindBugsFilter>

driver-benchmarks/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ sourceSets {
3131

3232
dependencies {
3333
api project(':driver-sync')
34+
api project(':mongodb-crypt')
3435
implementation "ch.qos.logback:logback-classic:$logbackVersion"
3536
}
3637

driver-benchmarks/src/main/com/mongodb/benchmark/benchmarks/BenchmarkSuite.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import com.mongodb.benchmark.framework.BenchmarkResultWriter;
2323
import com.mongodb.benchmark.framework.BenchmarkRunner;
2424
import com.mongodb.benchmark.framework.EvergreenBenchmarkResultWriter;
25+
import com.mongodb.benchmark.framework.MongoCryptBenchmarkRunner;
26+
import com.mongodb.benchmark.framework.MongocryptBecnhmarkResult;
2527
import org.bson.Document;
2628
import org.bson.codecs.Codec;
2729

@@ -56,6 +58,7 @@ public static void main(String[] args) throws Exception {
5658
private static void runBenchmarks()
5759
throws Exception {
5860

61+
runMongoCryptBenchMarks();
5962
runBenchmark(new BsonEncodingBenchmark<>("Flat", "extended_bson/flat_bson.json", DOCUMENT_CODEC));
6063
runBenchmark(new BsonEncodingBenchmark<>("Deep", "extended_bson/deep_bson.json", DOCUMENT_CODEC));
6164
runBenchmark(new BsonEncodingBenchmark<>("Full", "extended_bson/full_bson.json", DOCUMENT_CODEC));
@@ -87,6 +90,17 @@ private static void runBenchmarks()
8790
runBenchmark(new GridFSMultiFileDownloadBenchmark());
8891
}
8992

93+
private static void runMongoCryptBenchMarks() throws InterruptedException {
94+
// This runner has been migrated from libmongocrypt as it is.
95+
List<MongocryptBecnhmarkResult> results = new MongoCryptBenchmarkRunner().run();
96+
97+
for (BenchmarkResultWriter writer : WRITERS) {
98+
for (MongocryptBecnhmarkResult result : results) {
99+
writer.write(result);
100+
}
101+
}
102+
}
103+
90104
private static void runBenchmark(final Benchmark benchmark) throws Exception {
91105
long startTime = System.currentTimeMillis();
92106
BenchmarkResult benchmarkResult = new BenchmarkRunner(benchmark, NUM_WARMUP_ITERATIONS, NUM_ITERATIONS, MIN_TIME_SECONDS,

driver-benchmarks/src/main/com/mongodb/benchmark/framework/BenchmarkResultWriter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@
2121

2222
public interface BenchmarkResultWriter extends Closeable {
2323
void write(BenchmarkResult benchmarkResult);
24+
25+
void write(MongocryptBecnhmarkResult result);
2426
}

driver-benchmarks/src/main/com/mongodb/benchmark/framework/EvergreenBenchmarkResultWriter.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,32 @@ public void write(final BenchmarkResult benchmarkResult) {
6565
jsonWriter.writeEndDocument();
6666
}
6767

68+
@Override
69+
public void write(final MongocryptBecnhmarkResult result) {
70+
jsonWriter.writeStartDocument();
71+
72+
jsonWriter.writeStartDocument("info");
73+
jsonWriter.writeString("test_name", result.getTestName());
74+
75+
jsonWriter.writeStartDocument("args");
76+
jsonWriter.writeInt32("threads", result.getThreadCount());
77+
jsonWriter.writeEndDocument();
78+
jsonWriter.writeEndDocument();
79+
80+
jsonWriter.writeString("created_at", result.getCreatedAt());
81+
jsonWriter.writeString("completed_at", result.getCompletedAt());
82+
jsonWriter.writeStartArray("metrics");
83+
84+
jsonWriter.writeStartDocument();
85+
jsonWriter.writeString("name", result.getMetricName());
86+
jsonWriter.writeString("type", result.getMetricType());
87+
jsonWriter.writeDouble("value", result.getMedianOpsPerSec());
88+
jsonWriter.writeEndDocument();
89+
90+
jsonWriter.writeEndArray();
91+
jsonWriter.writeEndDocument();
92+
}
93+
6894
@Override
6995
public void close() throws IOException {
7096
jsonWriter.writeEndArray();

driver-benchmarks/src/main/com/mongodb/benchmark/framework/MinimalTextBasedBenchmarkResultWriter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ public void write(final BenchmarkResult benchmarkResult) {
3434
benchmarkResult.getElapsedTimeNanosAtPercentile(50) / ONE_BILLION);
3535
}
3636

37+
@Override
38+
public void write(final MongocryptBecnhmarkResult result) {
39+
printStream.printf("%s: %d%n", result.getTestName(),
40+
result.getMedianOpsPerSec());
41+
}
42+
3743
@Override
3844
public void close() {
3945
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package com.mongodb.benchmark.framework;
2+
3+
/*
4+
* Copyright 2023-present MongoDB, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
*/
19+
20+
import com.mongodb.crypt.capi.CAPI;
21+
import com.mongodb.crypt.capi.MongoCrypt;
22+
import com.mongodb.crypt.capi.MongoCryptContext;
23+
import com.mongodb.crypt.capi.MongoCryptOptions;
24+
import com.mongodb.crypt.capi.MongoCrypts;
25+
import com.mongodb.crypt.capi.MongoExplicitEncryptOptions;
26+
import com.mongodb.crypt.capi.MongoLocalKmsProviderOptions;
27+
import org.bson.BsonBinary;
28+
import org.bson.BsonBinarySubType;
29+
import org.bson.BsonDocument;
30+
import org.bson.BsonString;
31+
import org.bson.BsonValue;
32+
import org.bson.RawBsonDocument;
33+
34+
import java.net.URL;
35+
import java.nio.ByteBuffer;
36+
import java.nio.file.Files;
37+
import java.nio.file.Paths;
38+
import java.time.ZoneOffset;
39+
import java.time.ZonedDateTime;
40+
import java.time.format.DateTimeFormatter;
41+
import java.util.ArrayList;
42+
import java.util.Base64;
43+
import java.util.Collections;
44+
import java.util.List;
45+
import java.util.concurrent.CountDownLatch;
46+
import java.util.concurrent.ExecutorService;
47+
import java.util.concurrent.Executors;
48+
import java.util.concurrent.TimeUnit;
49+
50+
public class MongoCryptBenchmarkRunner {
51+
static final int NUM_FIELDS = 1500;
52+
static final int NUM_WARMUP_SECS = 2;
53+
static final int NUM_SECS = 10;
54+
static final byte[] LOCAL_MASTER_KEY = new byte[]{
55+
-99, -108, 75, 13, -109, -48, -59, 68, -91, 114, -3, 50, 27, -108, 48, -112, 35, 53,
56+
115, 124, -16, -10, -62, -12, -38, 35, 86, -25, -113, 4, -52, -6, -34, 117, -76, 81,
57+
-121, -13, -117, -105, -41, 75, 68, 59, -84, 57, -94, -58, 77, -111, 0, 62, -47, -6, 74,
58+
48, -63, -46, -58, 94, -5, -84, 65, -14, 72, 19, 60, -101, 80, -4, -89, 36, 122, 46, 2,
59+
99, -93, -58, 22, 37, 81, 80, 120, 62, 15, -40, 110, -124, -90, -20, -115, 45, 36, 71,
60+
-27, -81
61+
};
62+
63+
private static String getFileAsString(final String fileName) {
64+
try {
65+
URL resource = BenchmarkRunner.class.getResource("/" + fileName);
66+
if (resource == null) {
67+
throw new RuntimeException("Could not find file " + fileName);
68+
}
69+
return new String(Files.readAllBytes(Paths.get(resource.toURI())));
70+
} catch (Throwable t) {
71+
throw new RuntimeException("Could not parse file " + fileName, t);
72+
}
73+
}
74+
75+
private static BsonDocument getResourceAsDocument(final String fileName) {
76+
return BsonDocument.parse(getFileAsString(fileName));
77+
}
78+
79+
private static MongoCrypt createMongoCrypt() {
80+
return MongoCrypts.create(MongoCryptOptions
81+
.builder()
82+
.localKmsProviderOptions(MongoLocalKmsProviderOptions.builder()
83+
.localMasterKey(ByteBuffer.wrap(LOCAL_MASTER_KEY))
84+
.build())
85+
.build());
86+
}
87+
88+
// DecryptTask decrypts a document repeatedly for a specified number of seconds and records ops/sec.
89+
private static class DecryptTask implements Runnable {
90+
public DecryptTask(MongoCrypt mongoCrypt, BsonDocument toDecrypt, int numSecs, CountDownLatch doneSignal) {
91+
this.mongoCrypt = mongoCrypt;
92+
this.toDecrypt = toDecrypt;
93+
this.opsPerSecs = new ArrayList<Long>(numSecs);
94+
this.numSecs = numSecs;
95+
this.doneSignal = doneSignal;
96+
}
97+
98+
public void run() {
99+
for (int i = 0; i < numSecs; i++) {
100+
long opsPerSec = 0;
101+
long start = System.nanoTime();
102+
// Run for one second.
103+
while (System.nanoTime() - start < 1_000_000_000) {
104+
try (MongoCryptContext ctx = mongoCrypt.createDecryptionContext(toDecrypt)) {
105+
assert ctx.getState() == MongoCryptContext.State.READY;
106+
ctx.finish();
107+
opsPerSec++;
108+
}
109+
}
110+
opsPerSecs.add(opsPerSec);
111+
}
112+
doneSignal.countDown();
113+
}
114+
115+
public long getMedianOpsPerSecs() {
116+
if (opsPerSecs.size() == 0) {
117+
throw new IllegalStateException("opsPerSecs is empty. Was `run` called?");
118+
}
119+
Collections.sort(opsPerSecs);
120+
return opsPerSecs.get(numSecs / 2);
121+
}
122+
123+
private MongoCrypt mongoCrypt;
124+
private BsonDocument toDecrypt;
125+
private ArrayList<Long> opsPerSecs;
126+
private int numSecs;
127+
private CountDownLatch doneSignal;
128+
}
129+
130+
public List<MongocryptBecnhmarkResult> run() throws InterruptedException {
131+
System.out.printf("BenchmarkRunner is using libmongocrypt version=%s, NUM_WARMUP_SECS=%d, NUM_SECS=%d%n",
132+
CAPI.mongocrypt_version(null).toString(), NUM_WARMUP_SECS, NUM_SECS);
133+
// `keyDocument` is a Data Encryption Key (DEK) encrypted with the Key Encryption Key (KEK) `LOCAL_MASTER_KEY`.
134+
BsonDocument keyDocument = getResourceAsDocument("keyDocument.json");
135+
try (MongoCrypt mongoCrypt = createMongoCrypt()) {
136+
// `encrypted` will contain encrypted fields.
137+
BsonDocument encrypted = new BsonDocument();
138+
{
139+
for (int i = 0; i < NUM_FIELDS; i++) {
140+
MongoExplicitEncryptOptions options = MongoExplicitEncryptOptions.builder()
141+
.keyId(new BsonBinary(BsonBinarySubType.UUID_STANDARD, Base64.getDecoder().decode("YWFhYWFhYWFhYWFhYWFhYQ==")))
142+
.algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
143+
.build();
144+
BsonDocument toEncrypt = new BsonDocument("v", new BsonString(String.format("value %04d", i)));
145+
try (MongoCryptContext ctx = mongoCrypt.createExplicitEncryptionContext(toEncrypt, options)) {
146+
// If mongocrypt_t has not yet cached the DEK, supply it.
147+
if (MongoCryptContext.State.NEED_MONGO_KEYS == ctx.getState()) {
148+
ctx.addMongoOperationResult(keyDocument);
149+
ctx.completeMongoOperation();
150+
}
151+
assert ctx.getState() == MongoCryptContext.State.READY;
152+
RawBsonDocument result = ctx.finish();
153+
BsonValue encryptedValue = result.get("v");
154+
String key = String.format("key%04d", i);
155+
encrypted.append(key, encryptedValue);
156+
}
157+
}
158+
}
159+
160+
// Warm up benchmark and discard the result.
161+
DecryptTask warmup = new DecryptTask(mongoCrypt, encrypted, NUM_WARMUP_SECS, new CountDownLatch(1));
162+
warmup.run();
163+
164+
// Decrypt `encrypted` and measure ops/sec.
165+
// Check with varying thread counts to measure impact of a shared pool of Cipher instances.
166+
int[] threadCounts = {1, 2, 8, 64};
167+
ArrayList<Long> totalMedianOpsPerSecs = new ArrayList<Long>(threadCounts.length);
168+
ArrayList<String> createdAts = new ArrayList<String>(threadCounts.length);
169+
ArrayList<String> completedAts = new ArrayList<String>(threadCounts.length);
170+
171+
for (int threadCount : threadCounts) {
172+
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
173+
CountDownLatch doneSignal = new CountDownLatch(threadCount);
174+
ArrayList<DecryptTask> decryptTasks = new ArrayList<DecryptTask>(threadCount);
175+
createdAts.add(ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
176+
177+
for (int i = 0; i < threadCount; i++) {
178+
DecryptTask decryptTask = new DecryptTask(mongoCrypt, encrypted, NUM_SECS, doneSignal);
179+
decryptTasks.add(decryptTask);
180+
executorService.submit(decryptTask);
181+
}
182+
183+
// Await completion of all tasks. Tasks are expected to complete shortly after NUM_SECS. Time out `await` if time exceeds 2 * NUM_SECS.
184+
boolean ok = doneSignal.await(NUM_SECS * 2, TimeUnit.SECONDS);
185+
assert ok;
186+
completedAts.add(ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
187+
// Sum the median ops/secs of all tasks to get total throughput.
188+
long totalMedianOpsPerSec = 0;
189+
for (DecryptTask decryptTask : decryptTasks) {
190+
totalMedianOpsPerSec += decryptTask.getMedianOpsPerSecs();
191+
}
192+
System.out.printf("threadCount=%d. Decrypting 1500 fields median ops/sec : %d%n", threadCount, totalMedianOpsPerSec);
193+
totalMedianOpsPerSecs.add(totalMedianOpsPerSec);
194+
executorService.shutdown();
195+
ok = executorService.awaitTermination(NUM_SECS * 2, TimeUnit.SECONDS);
196+
assert ok;
197+
}
198+
199+
// Print the results in JSON that can be accepted by the `perf.send` command.
200+
// See https://docs.devprod.prod.corp.mongodb.com/evergreen/Project-Configuration/Project-Commands#perfsend for the expected `perf.send` input.
201+
List<MongocryptBecnhmarkResult> results = new ArrayList<>(threadCounts.length);
202+
for (int i = 0; i < threadCounts.length; i++) {
203+
int threadCount = threadCounts[i];
204+
long totalMedianOpsPerSec = totalMedianOpsPerSecs.get(i);
205+
String createdAt = createdAts.get(i);
206+
String completedAt = completedAts.get(i);
207+
208+
MongocryptBecnhmarkResult result = new MongocryptBecnhmarkResult(
209+
"java_decrypt_1500",
210+
threadCount,
211+
totalMedianOpsPerSec,
212+
createdAt,
213+
completedAt,
214+
"medianOpsPerSec",
215+
"THROUGHPUT");
216+
217+
results.add(result);
218+
}
219+
System.out.println("Results: " + results);
220+
return results;
221+
}
222+
}
223+
}
224+

0 commit comments

Comments
 (0)