Skip to content

Commit

Permalink
Merge branch 'main' into NODE-5906/optimize-toArray-to-batches
Browse files Browse the repository at this point in the history
  • Loading branch information
aditi-khare-mongoDB committed Aug 6, 2024
2 parents 2f3154d + 54efb7d commit 773452a
Show file tree
Hide file tree
Showing 17 changed files with 266 additions and 31 deletions.
Empty file modified .evergreen/run-typescript.sh
100644 → 100755
Empty file.
20 changes: 17 additions & 3 deletions src/client-side-encryption/auto_encrypter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type MongoCryptConstructor,
type MongoCryptOptions
} from 'mongodb-client-encryption';
import * as net from 'net';

import { deserialize, type Document, serialize } from '../bson';
import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
Expand All @@ -11,6 +12,7 @@ import { getMongoDBClientEncryption } from '../deps';
import { MongoRuntimeError } from '../error';
import { MongoClient, type MongoClientOptions } from '../mongo_client';
import { MongoDBCollectionNamespace } from '../utils';
import { autoSelectSocketOptions } from './client_encryption';
import * as cryptoCallbacks from './crypto_callbacks';
import { MongoCryptInvalidArgumentError } from './errors';
import { MongocryptdManager } from './mongocryptd_manager';
Expand Down Expand Up @@ -297,10 +299,20 @@ export class AutoEncrypter {
serverSelectionTimeoutMS: 10000
};

if (options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') {
if (
(options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') &&
!net.getDefaultAutoSelectFamily
) {
// Only set family if autoSelectFamily options are not supported.
clientOptions.family = 4;
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: TS complains as this always returns true on versions where it is present.
if (net.getDefaultAutoSelectFamily) {
Object.assign(clientOptions, autoSelectSocketOptions(this._client.options));
}

this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions);
}
}
Expand Down Expand Up @@ -379,7 +391,8 @@ export class AutoEncrypter {
promoteValues: false,
promoteLongs: false,
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

return deserialize(await stateMachine.execute(this, context), {
Expand All @@ -399,7 +412,8 @@ export class AutoEncrypter {
const stateMachine = new StateMachine({
...options,
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

return await stateMachine.execute(this, context);
Expand Down
38 changes: 32 additions & 6 deletions src/client-side-encryption/client_encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { type Collection } from '../collection';
import { type FindCursor } from '../cursor/find_cursor';
import { type Db } from '../db';
import { getMongoDBClientEncryption } from '../deps';
import { type MongoClient } from '../mongo_client';
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
import { type Filter, type WithId } from '../mongo_types';
import { type CreateCollectionOptions } from '../operations/create_collection';
import { type DeleteResult } from '../operations/delete';
Expand All @@ -28,7 +28,11 @@ import {
type KMSProviders,
refreshKMSCredentials
} from './providers/index';
import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine';
import {
type ClientEncryptionSocketOptions,
type CSFLEKMSTlsOptions,
StateMachine
} from './state_machine';

/**
* @public
Expand Down Expand Up @@ -199,7 +203,8 @@ export class ClientEncryption {

const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

const dataKey = deserialize(await stateMachine.execute(this, context)) as DataKey;
Expand Down Expand Up @@ -256,7 +261,8 @@ export class ClientEncryption {
const context = this._mongoCrypt.makeRewrapManyDataKeyContext(filterBson, keyEncryptionKeyBson);
const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

const { v: dataKeys } = deserialize(await stateMachine.execute(this, context));
Expand Down Expand Up @@ -637,7 +643,8 @@ export class ClientEncryption {

const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});

const { v } = deserialize(await stateMachine.execute(this, context));
Expand Down Expand Up @@ -715,7 +722,8 @@ export class ClientEncryption {
const valueBuffer = serialize({ v: value });
const stateMachine = new StateMachine({
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
tlsOptions: this._tlsOptions,
socketOptions: autoSelectSocketOptions(this._client.options)
});
const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);

Expand Down Expand Up @@ -957,3 +965,21 @@ export interface RangeOptions {
sparsity: Long;
precision?: number;
}

/**
* Get the socket options from the client.
* @param baseOptions - The mongo client options.
* @returns ClientEncryptionSocketOptions
*/
export function autoSelectSocketOptions(
baseOptions: MongoClientOptions
): ClientEncryptionSocketOptions {
const options: ClientEncryptionSocketOptions = { autoSelectFamily: true };
if ('autoSelectFamily' in baseOptions) {
options.autoSelectFamily = baseOptions.autoSelectFamily;
}
if ('autoSelectFamilyAttemptTimeout' in baseOptions) {
options.autoSelectFamilyAttemptTimeout = baseOptions.autoSelectFamilyAttemptTimeout;
}
return options;
}
34 changes: 28 additions & 6 deletions src/client-side-encryption/state_machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { type ProxyOptions } from '../cmap/connection';
import { getSocks, type SocksLib } from '../deps';
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
import { BufferPool, MongoDBCollectionNamespace, promiseWithResolvers } from '../utils';
import { type DataKey } from './client_encryption';
import { autoSelectSocketOptions, type DataKey } from './client_encryption';
import { MongoCryptError } from './errors';
import { type MongocryptdManager } from './mongocryptd_manager';
import { type KMSProviders } from './providers';
Expand Down Expand Up @@ -114,6 +114,16 @@ export type CSFLEKMSTlsOptions = {
[key: string]: ClientEncryptionTlsOptions | undefined;
};

/**
* @public
*
* Socket options to use for KMS requests.
*/
export type ClientEncryptionSocketOptions = Pick<
MongoClientOptions,
'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
>;

/**
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
Expand Down Expand Up @@ -153,6 +163,9 @@ export type StateMachineOptions = {

/** TLS options for KMS requests, if set. */
tlsOptions: CSFLEKMSTlsOptions;

/** Socket specific options we support. */
socketOptions: ClientEncryptionSocketOptions;
} & Pick<BSONSerializeOptions, 'promoteLongs' | 'promoteValues'>;

/**
Expand Down Expand Up @@ -289,10 +302,17 @@ export class StateMachine {
async kmsRequest(request: MongoCryptKMSRequest): Promise<void> {
const parsedUrl = request.endpoint.split(':');
const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
const options: tls.ConnectionOptions & { host: string; port: number } = {
const socketOptions = autoSelectSocketOptions(this.options.socketOptions || {});
const options: tls.ConnectionOptions & {
host: string;
port: number;
autoSelectFamily?: boolean;
autoSelectFamilyAttemptTimeout?: number;
} = {
host: parsedUrl[0],
servername: parsedUrl[0],
port
port,
...socketOptions
};
const message = request.message;
const buffer = new BufferPool();
Expand Down Expand Up @@ -351,10 +371,12 @@ export class StateMachine {

try {
if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
netSocket.connect({
const netSocketOptions = {
host: this.options.proxyOptions.proxyHost,
port: this.options.proxyOptions.proxyPort || 1080
});
port: this.options.proxyOptions.proxyPort || 1080,
...socketOptions
};
netSocket.connect(netSocketOptions);
await willConnect;

try {
Expand Down
4 changes: 3 additions & 1 deletion src/cmap/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export async function performInitialHandshake(
} catch (error) {
if (error instanceof MongoError) {
error.addErrorLabel(MongoErrorLabel.HandshakeError);
if (needsRetryableWriteLabel(error, response.maxWireVersion)) {
if (needsRetryableWriteLabel(error, response.maxWireVersion, conn.description.type)) {
error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
}
}
Expand Down Expand Up @@ -269,6 +269,8 @@ export const LEGAL_TLS_SOCKET_OPTIONS = [

/** @public */
export const LEGAL_TCP_SOCKET_OPTIONS = [
'autoSelectFamily',
'autoSelectFamilyAttemptTimeout',
'family',
'hints',
'localAddress',
Expand Down
7 changes: 7 additions & 0 deletions src/connection_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,13 @@ export const OPTIONS = {
autoEncryption: {
type: 'record'
},
autoSelectFamily: {
type: 'boolean',
default: true
},
autoSelectFamilyAttemptTimeout: {
type: 'uint'
},
bsonRegExp: {
type: 'boolean'
},
Expand Down
19 changes: 15 additions & 4 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Document } from './bson';
import type { ServerType } from './sdam/common';
import type { TopologyVersion } from './sdam/server_description';
import type { TopologyDescription } from './sdam/topology_description';

Expand Down Expand Up @@ -1226,7 +1227,11 @@ const RETRYABLE_READ_ERROR_CODES = new Set<number>([
// see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
const RETRYABLE_WRITE_ERROR_CODES = RETRYABLE_READ_ERROR_CODES;

export function needsRetryableWriteLabel(error: Error, maxWireVersion: number): boolean {
export function needsRetryableWriteLabel(
error: Error,
maxWireVersion: number,
serverType: ServerType
): boolean {
// pre-4.4 server, then the driver adds an error label for every valid case
// execute operation will only inspect the label, code/message logic is handled here
if (error instanceof MongoNetworkError) {
Expand All @@ -1246,11 +1251,17 @@ export function needsRetryableWriteLabel(error: Error, maxWireVersion: number):
}

if (error instanceof MongoWriteConcernError) {
return RETRYABLE_WRITE_ERROR_CODES.has(error.result.writeConcernError.code ?? error?.code ?? 0);
if (serverType === 'Mongos' && maxWireVersion < 9) {
// use original top-level code from server response
return RETRYABLE_WRITE_ERROR_CODES.has(error.result.code ?? 0);
}
return RETRYABLE_WRITE_ERROR_CODES.has(
error.result.writeConcernError.code ?? Number(error.code) ?? 0
);
}

if (error instanceof MongoError && typeof error.code === 'number') {
return RETRYABLE_WRITE_ERROR_CODES.has(error.code);
if (error instanceof MongoError) {
return RETRYABLE_WRITE_ERROR_CODES.has(Number(error.code));
}

const isNotWritablePrimaryError = LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE.test(error.message);
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ export type {
LocalKMSProviderConfiguration
} from './client-side-encryption/providers/index';
export type {
ClientEncryptionSocketOptions,
ClientEncryptionTlsOptions,
CSFLEKMSTlsOptions,
StateMachineExecutable
Expand Down
2 changes: 1 addition & 1 deletion src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export type SupportedTLSSocketOptions = Pick<

/** @public */
export type SupportedSocketOptions = Pick<
TcpNetConnectOpts,
TcpNetConnectOpts & { autoSelectFamily?: boolean; autoSelectFamilyAttemptTimeout?: number },
(typeof LEGAL_TCP_SOCKET_OPTIONS)[number]
>;

Expand Down
2 changes: 1 addition & 1 deletion src/sdam/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
} else {
if (
(isRetryableWritesEnabled(this.topology) || isTransactionCommand(cmd)) &&
needsRetryableWriteLabel(error, maxWireVersion(this)) &&
needsRetryableWriteLabel(error, maxWireVersion(this), this.description.type) &&
!inActiveTransaction(session, cmd)
) {
error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
Expand Down
53 changes: 53 additions & 0 deletions test/integration/node-specific/mongo_client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from 'chai';
import { once } from 'events';
import * as net from 'net';
import * as sinon from 'sinon';

import {
Expand Down Expand Up @@ -721,4 +722,56 @@ describe('class MongoClient', function () {
});
});
});

context('when connecting', function () {
let netSpy;

beforeEach(function () {
netSpy = sinon.spy(net, 'createConnection');
});

afterEach(function () {
sinon.restore();
});

context('when auto select options are provided', function () {
beforeEach(function () {
client = this.configuration.newClient({
autoSelectFamily: false,
autoSelectFamilyAttemptTimeout: 100
});
});

it('sets the provided options', {
metadata: { requires: { topology: ['single'] } },
test: async function () {
await client.connect();
expect(netSpy).to.have.been.calledWith({
autoSelectFamily: false,
autoSelectFamilyAttemptTimeout: 100,
host: 'localhost',
port: 27017
});
}
});
});

context('when auto select options are not provided', function () {
beforeEach(function () {
client = this.configuration.newClient();
});

it('sets the default options', {
metadata: { requires: { topology: ['single'] } },
test: async function () {
await client.connect();
expect(netSpy).to.have.been.calledWith({
autoSelectFamily: true,
host: 'localhost',
port: 27017
});
}
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ describe('Retryable Writes (unified)', function () {
runUnifiedSuite(loadSpecTests(path.join('retryable-writes', 'unified')), ({ description }) => {
return clientBulkWriteTests.includes(description)
? `TODO(NODE-6257): implement client-level bulk write.`
: description ===
'RetryableWriteError label is not added based on writeConcernError in pre-4.4 mongos response'
? 'TODO(NODE-5720)'
: false;
});
});
5 changes: 4 additions & 1 deletion test/manual/mocharc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"require": "ts-node/register",
"require": [
"ts-node/register",
"test/tools/runner/chai_addons.ts"
],
"reporter": "test/tools/reporter/mongodb_reporter.js",
"failZero": true,
"color": true,
Expand Down
Loading

0 comments on commit 773452a

Please sign in to comment.