diff --git a/.evergreen/ci_matrix_constants.js b/.evergreen/ci_matrix_constants.js index fad80fc90c0..86f3c12c16e 100644 --- a/.evergreen/ci_matrix_constants.js +++ b/.evergreen/ci_matrix_constants.js @@ -17,7 +17,8 @@ const DEFAULT_OS = 'rhel80-large'; const WINDOWS_OS = 'windows-vsCurrent-large'; const MACOS_OS = 'macos-1100'; const UBUNTU_OS = 'ubuntu1804-large'; -const UBUNTU_20_OS = 'ubuntu2004-small' +const UBUNTU_20_OS = 'ubuntu2004-small'; +const UBUNTU_22_OS = 'ubuntu2204-large'; const DEBIAN_OS = 'debian11-small'; module.exports = { @@ -34,5 +35,6 @@ module.exports = { MACOS_OS, UBUNTU_OS, UBUNTU_20_OS, + UBUNTU_22_OS, DEBIAN_OS }; diff --git a/.evergreen/config.in.yml b/.evergreen/config.in.yml index 1216a6e9589..15dd7d2de86 100644 --- a/.evergreen/config.in.yml +++ b/.evergreen/config.in.yml @@ -85,13 +85,15 @@ functions: params: script: | ${PREPARE_SHELL} - DRIVERS_TOOLS="${DRIVERS_TOOLS}" bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/build-mongohouse-local.sh + DRIVERS_TOOLS="${DRIVERS_TOOLS}" bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/pull-mongohouse-image.sh - command: shell.exec params: background: true script: | ${PREPARE_SHELL} - DRIVERS_TOOLS="${DRIVERS_TOOLS}" bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-local.sh + DRIVERS_TOOLS="${DRIVERS_TOOLS}" bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-image.sh + sleep 1 + docker ps "bootstrap kms servers": - command: subprocess.exec @@ -615,7 +617,7 @@ functions: "iam_auth_ecs_secret_access_key" : "${iam_auth_ecs_secret_access_key}", "iam_auth_ecs_account_arn": "arn:aws:iam::557821124784:user/authtest_fargate_user", "iam_auth_ecs_cluster": "${iam_auth_ecs_cluster}", - "iam_auth_ecs_task_definition": "${iam_auth_ecs_task_definition}", + "iam_auth_ecs_task_definition": "${iam_auth_ecs_task_definition_ubuntu2004}", "iam_auth_ecs_subnet_a": "${iam_auth_ecs_subnet_a}", "iam_auth_ecs_subnet_b": "${iam_auth_ecs_subnet_b}", "iam_auth_ecs_security_group": "${iam_auth_ecs_security_group}", @@ -664,7 +666,7 @@ functions: silent: true script: | cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - alias urlencode='python -c "import sys, urllib as ul; print ul.quote_plus(sys.argv[1])"' + alias urlencode='python3 -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote_plus(sys.argv[1]))"' USER=$(urlencode ${iam_auth_ecs_account}) PASS=$(urlencode ${iam_auth_ecs_secret_access_key}) export MONGODB_URI="mongodb://$USER:$PASS@localhost:27017/aws?authMechanism=MONGODB-AWS" @@ -696,12 +698,13 @@ functions: silent: true script: | cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - alias urlencode='python -c "import sys, urllib as ul; print ul.quote_plus(sys.argv[1])"' - USER=$(jq -r '.AccessKeyId' ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json) + alias urlencode='python3 -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote_plus(sys.argv[1]))"' + alias jsonkey='python3 -c "import json,sys;sys.stdout.write(json.load(sys.stdin)[sys.argv[1]])" < ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json' + USER=$(jsonkey AccessKeyId) USER=$(urlencode $USER) - PASS=$(jq -r '.SecretAccessKey' ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json) + PASS=$(jsonkey SecretAccessKey) PASS=$(urlencode $PASS) - SESSION_TOKEN=$(jq -r '.SessionToken' ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json) + SESSION_TOKEN=$(jsonkey SessionToken) SESSION_TOKEN=$(urlencode $SESSION_TOKEN) export MONGODB_URI="mongodb://$USER:$PASS@localhost:27017/aws?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:$SESSION_TOKEN" EOF @@ -1087,6 +1090,13 @@ functions: - ${PROJECT_DIRECTORY}/.evergreen/run-benchmarks.sh tasks: + - name: 'test-atlas-data-lake' + tags: ["datalake", "mongohouse"] + commands: + - func: 'install dependencies' + - func: 'bootstrap mongohoused' + - func: 'run data lake tests' + - name: "test-serverless" tags: ["serverless"] commands: diff --git a/.evergreen/config.yml b/.evergreen/config.yml index bc1eedd1a78..56f47ec0007 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -59,13 +59,15 @@ functions: params: script: | ${PREPARE_SHELL} - DRIVERS_TOOLS="${DRIVERS_TOOLS}" bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/build-mongohouse-local.sh + DRIVERS_TOOLS="${DRIVERS_TOOLS}" bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/pull-mongohouse-image.sh - command: shell.exec params: background: true script: | ${PREPARE_SHELL} - DRIVERS_TOOLS="${DRIVERS_TOOLS}" bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-local.sh + DRIVERS_TOOLS="${DRIVERS_TOOLS}" bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-image.sh + sleep 1 + docker ps bootstrap kms servers: - command: subprocess.exec params: @@ -574,7 +576,7 @@ functions: "iam_auth_ecs_secret_access_key" : "${iam_auth_ecs_secret_access_key}", "iam_auth_ecs_account_arn": "arn:aws:iam::557821124784:user/authtest_fargate_user", "iam_auth_ecs_cluster": "${iam_auth_ecs_cluster}", - "iam_auth_ecs_task_definition": "${iam_auth_ecs_task_definition}", + "iam_auth_ecs_task_definition": "${iam_auth_ecs_task_definition_ubuntu2004}", "iam_auth_ecs_subnet_a": "${iam_auth_ecs_subnet_a}", "iam_auth_ecs_subnet_b": "${iam_auth_ecs_subnet_b}", "iam_auth_ecs_security_group": "${iam_auth_ecs_security_group}", @@ -621,7 +623,7 @@ functions: silent: true script: | cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - alias urlencode='python -c "import sys, urllib as ul; print ul.quote_plus(sys.argv[1])"' + alias urlencode='python3 -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote_plus(sys.argv[1]))"' USER=$(urlencode ${iam_auth_ecs_account}) PASS=$(urlencode ${iam_auth_ecs_secret_access_key}) export MONGODB_URI="mongodb://$USER:$PASS@localhost:27017/aws?authMechanism=MONGODB-AWS" @@ -652,12 +654,13 @@ functions: silent: true script: | cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - alias urlencode='python -c "import sys, urllib as ul; print ul.quote_plus(sys.argv[1])"' - USER=$(jq -r '.AccessKeyId' ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json) + alias urlencode='python3 -c "import sys, urllib.parse as ulp; sys.stdout.write(ulp.quote_plus(sys.argv[1]))"' + alias jsonkey='python3 -c "import json,sys;sys.stdout.write(json.load(sys.stdin)[sys.argv[1]])" < ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json' + USER=$(jsonkey AccessKeyId) USER=$(urlencode $USER) - PASS=$(jq -r '.SecretAccessKey' ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json) + PASS=$(jsonkey SecretAccessKey) PASS=$(urlencode $PASS) - SESSION_TOKEN=$(jq -r '.SessionToken' ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json) + SESSION_TOKEN=$(jsonkey SessionToken) SESSION_TOKEN=$(urlencode $SESSION_TOKEN) export MONGODB_URI="mongodb://$USER:$PASS@localhost:27017/aws?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:$SESSION_TOKEN" EOF @@ -1030,6 +1033,14 @@ functions: args: - ${PROJECT_DIRECTORY}/.evergreen/run-benchmarks.sh tasks: + - name: test-atlas-data-lake + tags: + - datalake + - mongohouse + commands: + - func: install dependencies + - func: bootstrap mongohoused + - func: run data lake tests - name: test-serverless tags: - serverless @@ -1682,11 +1693,6 @@ tasks: commands: - func: install dependencies - func: run atlas tests - - name: test-atlas-data-lake - commands: - - func: install dependencies - - func: bootstrap mongohoused - - func: run data lake tests - name: test-5.0-load-balanced tags: - latest @@ -3944,7 +3950,6 @@ buildvariants: - test-3.6-sharded_cluster - test-latest-server-v1-api - test-atlas-connectivity - - test-atlas-data-lake - test-5.0-load-balanced - test-6.0-load-balanced - test-latest-load-balanced @@ -3997,7 +4002,6 @@ buildvariants: - test-3.6-sharded_cluster - test-latest-server-v1-api - test-atlas-connectivity - - test-atlas-data-lake - test-5.0-load-balanced - test-6.0-load-balanced - test-latest-load-balanced @@ -4048,7 +4052,6 @@ buildvariants: - test-3.6-sharded_cluster - test-latest-server-v1-api - test-atlas-connectivity - - test-atlas-data-lake - test-5.0-load-balanced - test-6.0-load-balanced - test-latest-load-balanced @@ -4099,7 +4102,6 @@ buildvariants: - test-3.6-sharded_cluster - test-latest-server-v1-api - test-atlas-connectivity - - test-atlas-data-lake - test-5.0-load-balanced - test-6.0-load-balanced - test-latest-load-balanced @@ -4149,7 +4151,6 @@ buildvariants: - test-3.6-sharded_cluster - test-latest-server-v1-api - test-atlas-connectivity - - test-atlas-data-lake - test-5.0-load-balanced - test-6.0-load-balanced - test-latest-load-balanced @@ -4198,7 +4199,6 @@ buildvariants: - test-3.6-replica_set - test-3.6-sharded_cluster - test-latest-server-v1-api - - test-atlas-data-lake - test-socks5 - test-socks5-tls - test-tls-support-latest @@ -4241,7 +4241,6 @@ buildvariants: - test-3.6-replica_set - test-3.6-sharded_cluster - test-latest-server-v1-api - - test-atlas-data-lake - test-socks5 - test-socks5-tls - test-tls-support-latest @@ -4284,7 +4283,6 @@ buildvariants: - test-3.6-replica_set - test-3.6-sharded_cluster - test-latest-server-v1-api - - test-atlas-data-lake - test-socks5 - test-socks5-tls - test-tls-support-latest @@ -4344,12 +4342,11 @@ buildvariants: run_on: rhel80-large tasks: - download-and-merge-coverage - - name: ubuntu1804-test-mongodb-aws + - name: ubuntu2004-test-mongodb-aws display_name: MONGODB-AWS Auth test - run_on: ubuntu1804-large + run_on: ubuntu2004-small expansions: - NODE_LTS_VERSION: 14 - NPM_VERSION: 9 + NODE_LTS_VERSION: 20 tasks: - aws-latest-auth-test-run-aws-auth-test-with-regular-aws-credentials - aws-latest-auth-test-run-aws-auth-test-with-assume-role-credentials @@ -4427,6 +4424,13 @@ buildvariants: aws-4.4-auth-test-run-aws-auth-test-AssumeRoleWithWebIdentity-with-AWS_ROLE_SESSION_NAME-unset-no-peer-dependencies - >- aws-4.4-auth-test-run-aws-auth-test-AssumeRoleWithWebIdentity-with-AWS_ROLE_SESSION_NAME-set-no-peer-dependencies + - name: ubuntu2204-test-atlas-data-lake + display_name: Atlas Data Lake Tests + run_on: ubuntu2204-large + expansions: + NODE_LTS_VERSION: 20 + tasks: + - test-atlas-data-lake - name: rhel8-custom-dependency-tests display_name: Custom Dependency Version Test run_on: rhel80-large diff --git a/.evergreen/generate_evergreen_tasks.js b/.evergreen/generate_evergreen_tasks.js index 9bd66366381..4482ffe9f7a 100644 --- a/.evergreen/generate_evergreen_tasks.js +++ b/.evergreen/generate_evergreen_tasks.js @@ -16,7 +16,8 @@ const { MACOS_OS, UBUNTU_OS, UBUNTU_20_OS, - DEBIAN_OS + DEBIAN_OS, + UBUNTU_22_OS } = require('./ci_matrix_constants'); const OPERATING_SYSTEMS = [ @@ -125,14 +126,6 @@ TASKS.push( tags: ['atlas-connect'], commands: [{ func: 'install dependencies' }, { func: 'run atlas tests' }] }, - { - name: 'test-atlas-data-lake', - commands: [ - { func: 'install dependencies' }, - { func: 'bootstrap mongohoused' }, - { func: 'run data lake tests' } - ] - }, { name: 'test-5.0-load-balanced', tags: ['latest', 'sharded_cluster', 'load_balancer'], @@ -599,16 +592,25 @@ BUILD_VARIANTS.push({ // special case for MONGODB-AWS authentication BUILD_VARIANTS.push({ - name: 'ubuntu1804-test-mongodb-aws', + name: 'ubuntu2004-test-mongodb-aws', display_name: 'MONGODB-AWS Auth test', - run_on: UBUNTU_OS, + run_on: UBUNTU_20_OS, expansions: { - NODE_LTS_VERSION: LOWEST_LTS, - NPM_VERSION: 9 + NODE_LTS_VERSION: LATEST_LTS }, tasks: AWS_AUTH_TASKS }); +BUILD_VARIANTS.push({ + name: 'ubuntu2204-test-atlas-data-lake', + display_name: 'Atlas Data Lake Tests', + run_on: UBUNTU_22_OS, + expansions: { + NODE_LTS_VERSION: LATEST_LTS + }, + tasks: ['test-atlas-data-lake'] +}); + const oneOffFuncAsTasks = []; for (const version of ['5.0', 'rapid', 'latest']) { diff --git a/HISTORY.md b/HISTORY.md index dbf8a906619..c5e09b31ea0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [5.9.2](https://github.com/mongodb/node-mongodb-native/compare/v5.9.1...v5.9.2) (2023-11-16) + + +### Bug Fixes + +* **NODE-5750:** RTTPinger always sends legacy hello ([#3922](https://github.com/mongodb/node-mongodb-native/issues/3922)) ([8e56872](https://github.com/mongodb/node-mongodb-native/commit/8e56872fd7a79c3d1cb0f215b55320c535cd6787)) + ## [5.9.1](https://github.com/mongodb/node-mongodb-native/compare/v5.9.0...v5.9.1) (2023-10-18) diff --git a/package-lock.json b/package-lock.json index fb20f227ffc..981024a35b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mongodb", - "version": "5.9.1", + "version": "5.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mongodb", - "version": "5.9.1", + "version": "5.9.2", "license": "Apache-2.0", "dependencies": { "bson": "^5.5.0", diff --git a/package.json b/package.json index 15e230b55d5..7f286ce815b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mongodb", - "version": "5.9.1", + "version": "5.9.2", "description": "The official MongoDB driver for Node.js", "main": "lib/index.js", "files": [ diff --git a/src/sdam/monitor.ts b/src/sdam/monitor.ts index eec2c30ed7e..8712eccdc79 100644 --- a/src/sdam/monitor.ts +++ b/src/sdam/monitor.ts @@ -26,8 +26,6 @@ const kConnection = Symbol('connection'); /** @internal */ const kCancellationToken = Symbol('cancellationToken'); /** @internal */ -const kRTTPinger = Symbol('rttPinger'); -/** @internal */ const kRoundTripTime = Symbol('roundTripTime'); const STATE_IDLE = 'idle'; @@ -81,7 +79,7 @@ export class Monitor extends TypedEventEmitter { [kCancellationToken]: CancellationToken; /** @internal */ [kMonitorId]?: MonitorInterval; - [kRTTPinger]?: RTTPinger; + rttPinger?: RTTPinger; get connection(): Connection | undefined { return this[kConnection]; @@ -198,8 +196,8 @@ function resetMonitorState(monitor: Monitor) { monitor[kMonitorId]?.stop(); monitor[kMonitorId] = undefined; - monitor[kRTTPinger]?.close(); - monitor[kRTTPinger] = undefined; + monitor.rttPinger?.close(); + monitor.rttPinger = undefined; monitor[kCancellationToken].emit('cancel'); @@ -252,8 +250,8 @@ function checkServer(monitor: Monitor, callback: Callback) { } : { socketTimeoutMS: connectTimeoutMS }; - if (isAwaitable && monitor[kRTTPinger] == null) { - monitor[kRTTPinger] = new RTTPinger( + if (isAwaitable && monitor.rttPinger == null) { + monitor.rttPinger = new RTTPinger( monitor[kCancellationToken], Object.assign( { heartbeatFrequencyMS: monitor.options.heartbeatFrequencyMS }, @@ -272,9 +270,10 @@ function checkServer(monitor: Monitor, callback: Callback) { hello.isWritablePrimary = hello[LEGACY_HELLO_COMMAND]; } - const rttPinger = monitor[kRTTPinger]; const duration = - isAwaitable && rttPinger ? rttPinger.roundTripTime : calculateDurationInMs(start); + isAwaitable && monitor.rttPinger + ? monitor.rttPinger.roundTripTime + : calculateDurationInMs(start); monitor.emit( Server.SERVER_HEARTBEAT_SUCCEEDED, @@ -290,8 +289,8 @@ function checkServer(monitor: Monitor, callback: Callback) { ); start = now(); } else { - monitor[kRTTPinger]?.close(); - monitor[kRTTPinger] = undefined; + monitor.rttPinger?.close(); + monitor.rttPinger = undefined; callback(undefined, hello); } @@ -384,7 +383,7 @@ export interface RTTPingerOptions extends ConnectionOptions { /** @internal */ export class RTTPinger { /** @internal */ - [kConnection]?: Connection; + connection?: Connection; /** @internal */ [kCancellationToken]: CancellationToken; /** @internal */ @@ -394,7 +393,7 @@ export class RTTPinger { closed: boolean; constructor(cancellationToken: CancellationToken, options: RTTPingerOptions) { - this[kConnection] = undefined; + this.connection = undefined; this[kCancellationToken] = cancellationToken; this[kRoundTripTime] = 0; this.closed = false; @@ -411,8 +410,8 @@ export class RTTPinger { this.closed = true; clearTimeout(this[kMonitorId]); - this[kConnection]?.destroy({ force: true }); - this[kConnection] = undefined; + this.connection?.destroy({ force: true }); + this.connection = undefined; } } @@ -431,8 +430,8 @@ function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) { return; } - if (rttPinger[kConnection] == null) { - rttPinger[kConnection] = conn; + if (rttPinger.connection == null) { + rttPinger.connection = conn; } rttPinger[kRoundTripTime] = calculateDurationInMs(start); @@ -442,11 +441,11 @@ function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) { ); } - const connection = rttPinger[kConnection]; + const connection = rttPinger.connection; if (connection == null) { connect(options, (err, conn) => { if (err) { - rttPinger[kConnection] = undefined; + rttPinger.connection = undefined; rttPinger[kRoundTripTime] = 0; return; } @@ -457,15 +456,16 @@ function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) { return; } - connection.command(ns('admin.$cmd'), { [LEGACY_HELLO_COMMAND]: 1 }, undefined, err => { - if (err) { - rttPinger[kConnection] = undefined; + const commandName = + connection.serverApi?.version || connection.helloOk ? 'hello' : LEGACY_HELLO_COMMAND; + connection.commandAsync(ns('admin.$cmd'), { [commandName]: 1 }, undefined).then( + () => measureAndReschedule(), + () => { + rttPinger.connection?.destroy({ force: true }); + rttPinger.connection = undefined; rttPinger[kRoundTripTime] = 0; - return; } - - measureAndReschedule(); - }); + ); } /** diff --git a/src/sdam/server.ts b/src/sdam/server.ts index f76f8c9a8c0..9c2c5cbc0f1 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -74,9 +74,6 @@ const stateTransition = makeStateMachine({ [STATE_CLOSING]: [STATE_CLOSING, STATE_CLOSED] }); -/** @internal */ -const kMonitor = Symbol('monitor'); - /** @internal */ export type ServerOptions = Omit & MonitorOptions; @@ -119,7 +116,7 @@ export class Server extends TypedEventEmitter { serverApi?: ServerApi; hello?: Document; commandAsync: (ns: MongoDBNamespace, cmd: Document, options: CommandOptions) => Promise; - [kMonitor]: Monitor | null; + monitor: Monitor | null; /** @event */ static readonly SERVER_HEARTBEAT_STARTED = SERVER_HEARTBEAT_STARTED; @@ -175,22 +172,21 @@ export class Server extends TypedEventEmitter { }); if (this.loadBalanced) { - this[kMonitor] = null; + this.monitor = null; // monitoring is disabled in load balancing mode return; } // create the monitor // TODO(NODE-4144): Remove new variable for type narrowing - const monitor = new Monitor(this, this.s.options); - this[kMonitor] = monitor; + this.monitor = new Monitor(this, this.s.options); for (const event of HEARTBEAT_EVENTS) { - monitor.on(event, (e: any) => this.emit(event, e)); + this.monitor.on(event, (e: any) => this.emit(event, e)); } - monitor.on('resetServer', (error: MongoError) => markServerUnknown(this, error)); - monitor.on(Server.SERVER_HEARTBEAT_SUCCEEDED, (event: ServerHeartbeatSucceededEvent) => { + this.monitor.on('resetServer', (error: MongoError) => markServerUnknown(this, error)); + this.monitor.on(Server.SERVER_HEARTBEAT_SUCCEEDED, (event: ServerHeartbeatSucceededEvent) => { this.emit( Server.DESCRIPTION_RECEIVED, new ServerDescription(this.description.hostAddress, event.reply, { @@ -246,7 +242,7 @@ export class Server extends TypedEventEmitter { // a load balancer. It never transitions out of this state and // has no monitor. if (!this.loadBalanced) { - this[kMonitor]?.connect(); + this.monitor?.connect(); } else { stateTransition(this, STATE_CONNECTED); this.emit(Server.CONNECT, this); @@ -272,7 +268,7 @@ export class Server extends TypedEventEmitter { stateTransition(this, STATE_CLOSING); if (!this.loadBalanced) { - this[kMonitor]?.close(); + this.monitor?.close(); } this.pool.close(options, err => { @@ -290,7 +286,7 @@ export class Server extends TypedEventEmitter { */ requestCheck(): void { if (!this.loadBalanced) { - this[kMonitor]?.requestCheck(); + this.monitor?.requestCheck(); } } @@ -465,7 +461,7 @@ function markServerUnknown(server: Server, error?: MongoServerError) { } if (error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError)) { - server[kMonitor]?.reset(); + server.monitor?.reset(); } server.emit( diff --git a/test/integration/connection-monitoring-and-pooling/rtt_pinger.test.ts b/test/integration/connection-monitoring-and-pooling/rtt_pinger.test.ts new file mode 100644 index 00000000000..af584aabe20 --- /dev/null +++ b/test/integration/connection-monitoring-and-pooling/rtt_pinger.test.ts @@ -0,0 +1,179 @@ +import { expect } from 'chai'; +import * as semver from 'semver'; +import * as sinon from 'sinon'; + +import { + type Connection, + LEGACY_HELLO_COMMAND, + type MongoClient, + type RTTPinger +} from '../../mongodb'; +import { sleep } from '../../tools/utils'; + +/** + * RTTPingers are only created after getting a hello from the server that defines topologyVersion + * Each monitor is reaching out to a different node and rttPinger's are created async as a result. + * + * This function checks for rttPingers and sleeps if none are found. + */ +async function getRTTPingers(client: MongoClient) { + type RTTPingerConnection = Omit & { connection: Connection }; + const pingers = (rtt => rtt?.connection != null) as (r?: RTTPinger) => r is RTTPingerConnection; + + if (!client.topology) expect.fail('Must provide a connected client'); + + // eslint-disable-next-line no-constant-condition + while (true) { + const servers = client.topology.s.servers.values(); + const rttPingers = Array.from(servers, s => s.monitor?.rttPinger).filter(pingers); + + if (rttPingers.length !== 0) { + return rttPingers; + } + + await sleep(5); + } +} + +describe('class RTTPinger', () => { + afterEach(() => sinon.restore()); + + beforeEach(async function () { + if (!this.currentTest) return; + if (this.configuration.isLoadBalanced) { + this.currentTest.skipReason = 'No monitoring in LB mode, test not relevant'; + return this.skip(); + } + if (semver.gte('4.4.0', this.configuration.version)) { + this.currentTest.skipReason = + 'Test requires streaming monitoring, needs to be on MongoDB 4.4+'; + return this.skip(); + } + }); + + context('when serverApi is enabled', () => { + let serverApiClient: MongoClient; + + beforeEach(async function () { + if (!this.currentTest) return; + + if (semver.gte('5.0.0', this.configuration.version)) { + this.currentTest.skipReason = 'Test requires serverApi, needs to be on MongoDB 5.0+'; + return this.skip(); + } + + serverApiClient = this.configuration.newClient( + {}, + { serverApi: { version: '1', strict: true }, heartbeatFrequencyMS: 10 } + ); + }); + + afterEach(async () => { + await serverApiClient?.close(); + }); + + it('measures rtt with a hello command', async function () { + await serverApiClient.connect(); + const rttPingers = await getRTTPingers(serverApiClient); + + const spies = rttPingers.map(rtt => sinon.spy(rtt.connection, 'command')); + + await sleep(11); // allow for another ping after spies have been made + + expect(spies).to.have.lengthOf.at.least(1); + for (const spy of spies) { + expect(spy).to.have.been.calledWith(sinon.match.any, { hello: 1 }, sinon.match.any); + } + }); + }); + + context('when serverApi is disabled', () => { + let client: MongoClient; + + beforeEach(async function () { + if (!this.currentTest) return; + if (this.configuration.serverApi) { + this.currentTest.skipReason = 'Test requires serverApi to NOT be enabled'; + return this.skip(); + } + + client = this.configuration.newClient({}, { heartbeatFrequencyMS: 10 }); + }); + + afterEach(async () => { + await client?.close(); + }); + + context('connected to a pre-hello server', () => { + it('measures rtt with a LEGACY_HELLO_COMMAND command', async function () { + await client.connect(); + const rttPingers = await getRTTPingers(client); + + // Fake pre-hello server. + // Hello was back-ported to feature versions of the server so we would need to pin + // versions prior to 4.4.2, 4.2.10, 4.0.21, and 3.6.21 to integration test + for (const rtt of rttPingers) rtt.connection.helloOk = false; + + const spies = rttPingers.map(rtt => sinon.spy(rtt.connection, 'command')); + + await sleep(11); // allow for another ping after spies have been made + + expect(spies).to.have.lengthOf.at.least(1); + for (const spy of spies) { + expect(spy).to.have.been.calledWith( + sinon.match.any, + { [LEGACY_HELLO_COMMAND]: 1 }, + sinon.match.any + ); + } + }); + }); + + context('connected to a helloOk server', () => { + it('measures rtt with a hello command', async function () { + await client.connect(); + const rttPingers = await getRTTPingers(client); + + const spies = rttPingers.map(rtt => sinon.spy(rtt.connection, 'command')); + + // We should always be connected to helloOk servers + for (const rtt of rttPingers) expect(rtt.connection).to.have.property('helloOk', true); + + await sleep(11); // allow for another ping after spies have been made + + expect(spies).to.have.lengthOf.at.least(1); + for (const spy of spies) { + expect(spy).to.have.been.calledWith(sinon.match.any, { hello: 1 }, sinon.match.any); + } + }); + }); + }); + + context(`when the RTTPinger's hello command receives any error`, () => { + let client: MongoClient; + beforeEach(async function () { + client = this.configuration.newClient({}, { heartbeatFrequencyMS: 10 }); + }); + + afterEach(async () => { + await client?.close(); + }); + + it('destroys the connection with force=true', async function () { + await client.connect(); + const rttPingers = await getRTTPingers(client); + + for (const rtt of rttPingers) { + sinon.stub(rtt.connection, 'command').yieldsRight(new Error('any error')); + } + const spies = rttPingers.map(rtt => sinon.spy(rtt.connection, 'destroy')); + + await sleep(11); // allow for another ping after spies have been made + + expect(spies).to.have.lengthOf.at.least(1); + for (const spy of spies) { + expect(spy).to.have.been.calledWithExactly({ force: true }); + } + }); + }); +}); diff --git a/test/integration/crud/crud_api.test.ts b/test/integration/crud/crud_api.test.ts index 6238d720032..871b847d2d9 100644 --- a/test/integration/crud/crud_api.test.ts +++ b/test/integration/crud/crud_api.test.ts @@ -105,9 +105,9 @@ describe('CRUD API', function () { const spy = sinon.spy(Collection.prototype, 'find'); const result = await collection.findOne({}); expect(result).to.deep.equal({ _id: 1 }); - expect(events.at(0)).to.be.instanceOf(CommandSucceededEvent); - expect(spy.returnValues.at(0)).to.have.property('closed', true); - expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true); + expect(events[0]).to.be.instanceOf(CommandSucceededEvent); + expect(spy.returnValues[0]).to.have.property('closed', true); + expect(spy.returnValues[0]).to.have.nested.property('session.hasEnded', true); }); }); @@ -149,9 +149,9 @@ describe('CRUD API', function () { const spy = sinon.spy(Collection.prototype, 'find'); const error = await collection.findOne({}).catch(error => error); expect(error).to.be.instanceOf(MongoServerError); - expect(events.at(0)).to.be.instanceOf(CommandFailedEvent); - expect(spy.returnValues.at(0)).to.have.property('closed', true); - expect(spy.returnValues.at(0)).to.have.nested.property('session.hasEnded', true); + expect(events[0]).to.be.instanceOf(CommandFailedEvent); + expect(spy.returnValues[0]).to.have.property('closed', true); + expect(spy.returnValues[0]).to.have.nested.property('session.hasEnded', true); }); }); }); diff --git a/test/unit/sdam/topology.test.js b/test/unit/sdam/topology.test.js index 6efcb9a7d5e..31fe2e05914 100644 --- a/test/unit/sdam/topology.test.js +++ b/test/unit/sdam/topology.test.js @@ -354,9 +354,8 @@ describe('Topology (unit)', function () { afterEach(() => { // The srv event starts a monitor that we need to clean up for (const [, server] of topology.s.servers) { - const kMonitor = getSymbolFrom(server, 'monitor'); - const kMonitorId = getSymbolFrom(server[kMonitor], 'monitorId'); - server[kMonitor][kMonitorId].stop(); + const kMonitorId = getSymbolFrom(server.monitor, 'monitorId'); + server.monitor[kMonitorId].stop(); } });