Skip to content

Commit c554a7a

Browse files
authored
feat(NODE-3011): Load Balancer Support (#2909)
1 parent 3c60245 commit c554a7a

Some content is hidden

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

56 files changed

+1514
-345
lines changed

.evergreen/config.yml

+53
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,44 @@ functions:
134134
MONGODB_API_VERSION="${MONGODB_API_VERSION}" \
135135
NODE_VERSION=${NODE_VERSION} SKIP_DEPS=${SKIP_DEPS|1} NO_EXIT=${NO_EXIT|1} \
136136
bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
137+
start-load-balancer:
138+
- command: shell.exec
139+
params:
140+
script: |
141+
DRIVERS_TOOLS=${DRIVERS_TOOLS} MONGODB_URI=${MONGODB_URI} \
142+
bash ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh start
143+
- command: expansions.update
144+
params:
145+
file: lb-expansion.yml
146+
stop-load-balancer:
147+
- command: shell.exec
148+
params:
149+
script: |
150+
DRIVERS_TOOLS=${DRIVERS_TOOLS} MONGODB_URI=${MONGODB_URI} \
151+
bash ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh stop
152+
run-lb-tests:
153+
- command: shell.exec
154+
type: test
155+
params:
156+
working_dir: src
157+
timeout_secs: 60
158+
script: |
159+
${PREPARE_SHELL}
160+
161+
MONGODB_URI="${MONGODB_URI}" \
162+
AUTH=${AUTH} \
163+
SSL=${SSL} \
164+
UNIFIED=${UNIFIED} \
165+
MONGODB_API_VERSION="${MONGODB_API_VERSION}" \
166+
NODE_VERSION=${NODE_VERSION} \
167+
SINGLE_MONGOS_LB_URI="${SINGLE_MONGOS_LB_URI}" \
168+
MULTI_MONGOS_LB_URI="${MULTI_MONGOS_LB_URI}" \
169+
TOPOLOGY="${TOPOLOGY}" \
170+
SKIP_DEPS=${SKIP_DEPS|1} \
171+
NO_EXIT=${NO_EXIT|1} \
172+
TEST_NPM_SCRIPT="check:load-balancer" \
173+
FAKE_MONGODB_SERVICE_ID="true" \
174+
bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
137175
run checks:
138176
- command: shell.exec
139177
type: test
@@ -937,6 +975,20 @@ tasks:
937975
- func: install dependencies
938976
- func: bootstrap mongohoused
939977
- func: run data lake tests
978+
- name: test-load-balancer
979+
tags:
980+
- latest
981+
- sharded_cluster
982+
- load_balancer
983+
commands:
984+
- func: install dependencies
985+
- func: bootstrap mongo-orchestration
986+
vars:
987+
VERSION: '5.0'
988+
TOPOLOGY: sharded_cluster
989+
- func: start-load-balancer
990+
- func: run-lb-tests
991+
- func: stop-load-balancer
940992
- name: test-auth-kerberos
941993
tags:
942994
- auth
@@ -1760,6 +1812,7 @@ buildvariants:
17601812
- test-latest-server-v1-api
17611813
- test-atlas-connectivity
17621814
- test-atlas-data-lake
1815+
- test-load-balancer
17631816
- test-auth-kerberos
17641817
- test-auth-ldap
17651818
- test-ocsp-valid-cert-server-staples

.evergreen/config.yml.in

+41
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,47 @@ functions:
155155
NODE_VERSION=${NODE_VERSION} SKIP_DEPS=${SKIP_DEPS|1} NO_EXIT=${NO_EXIT|1} \
156156
bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
157157

158+
"start-load-balancer":
159+
- command: shell.exec
160+
params:
161+
script: |
162+
DRIVERS_TOOLS=${DRIVERS_TOOLS} MONGODB_URI=${MONGODB_URI} \
163+
bash ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh start
164+
- command: expansions.update
165+
params:
166+
file: lb-expansion.yml
167+
168+
"stop-load-balancer":
169+
- command: shell.exec
170+
params:
171+
script: |
172+
DRIVERS_TOOLS=${DRIVERS_TOOLS} MONGODB_URI=${MONGODB_URI} \
173+
bash ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh stop
174+
175+
"run-lb-tests":
176+
- command: shell.exec
177+
type: test
178+
params:
179+
working_dir: src
180+
timeout_secs: 60
181+
script: |
182+
${PREPARE_SHELL}
183+
184+
MONGODB_URI="${MONGODB_URI}" \
185+
AUTH=${AUTH} \
186+
SSL=${SSL} \
187+
UNIFIED=${UNIFIED} \
188+
MONGODB_API_VERSION="${MONGODB_API_VERSION}" \
189+
NODE_VERSION=${NODE_VERSION} \
190+
SINGLE_MONGOS_LB_URI="${SINGLE_MONGOS_LB_URI}" \
191+
MULTI_MONGOS_LB_URI="${MULTI_MONGOS_LB_URI}" \
192+
TOPOLOGY="${TOPOLOGY}" \
193+
SKIP_DEPS=${SKIP_DEPS|1} \
194+
NO_EXIT=${NO_EXIT|1} \
195+
TEST_NPM_SCRIPT="check:load-balancer" \
196+
FAKE_MONGODB_SERVICE_ID="true" \
197+
bash ${PROJECT_DIRECTORY}/.evergreen/run-tests.sh
198+
158199
"run checks":
159200
- command: shell.exec
160201
type: test

.evergreen/generate_evergreen_tasks.js

+24-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ const OPERATING_SYSTEMS = [
4242
}));
4343

4444
// TODO: NODE-3060: enable skipped tests on windows
45-
const WINDOWS_SKIP_TAGS = new Set(['atlas-connect', 'auth']);
45+
const WINDOWS_SKIP_TAGS = new Set(['atlas-connect', 'auth', 'load_balancer']);
46+
const MACOS_SKIP_TAGS = new Set(['load_balancer']);
4647

4748
const TASKS = [];
4849
const SINGLETON_TASKS = [];
@@ -107,6 +108,23 @@ TASKS.push(...[
107108
{ func: 'run data lake tests' }
108109
]
109110
},
111+
{
112+
name: 'test-load-balancer',
113+
tags: ['latest', 'sharded_cluster', 'load_balancer'],
114+
commands: [
115+
{ func: 'install dependencies' },
116+
{
117+
func: 'bootstrap mongo-orchestration',
118+
vars: {
119+
VERSION: '5.0',
120+
TOPOLOGY: 'sharded_cluster'
121+
}
122+
},
123+
{ func: 'start-load-balancer' },
124+
{ func: 'run-lb-tests' },
125+
{ func: 'stop-load-balancer' }
126+
]
127+
},
110128
{
111129
name: 'test-auth-kerberos',
112130
tags: ['auth', 'kerberos'],
@@ -429,11 +447,12 @@ const getTaskList = (() => {
429447
.filter(task => {
430448
if (task.name.match(/^aws/)) return false;
431449

432-
// skip unsupported tasks on windows
450+
// skip unsupported tasks on windows or macos
433451
if (
434-
os.match(/^windows/) &&
435-
task.tags &&
436-
task.tags.filter(tag => WINDOWS_SKIP_TAGS.has(tag)).length
452+
task.tags && (
453+
(os.match(/^windows/) && task.tags.filter(tag => WINDOWS_SKIP_TAGS.has(tag)).length) ||
454+
(os.match(/^macos/) && task.tags.filter(tag => MACOS_SKIP_TAGS.has(tag)).length)
455+
)
437456
) {
438457
return false;
439458
}

.evergreen/run-tests.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ else
4949
. $DRIVERS_TOOLS/.evergreen/csfle/set-temp-creds.sh
5050
fi
5151

52-
MONGODB_API_VERSION=${MONGODB_API_VERSION} MONGODB_UNIFIED_TOPOLOGY=${UNIFIED} MONGODB_URI=${MONGODB_URI} npm run ${TEST_NPM_SCRIPT}
52+
SINGLE_MONGOS_LB_URI=${SINGLE_MONGOS_LB_URI} MULTI_MONGOS_LB_URI=${MULTI_MONGOS_LB_URI} MONGODB_API_VERSION=${MONGODB_API_VERSION} MONGODB_UNIFIED_TOPOLOGY=${UNIFIED} MONGODB_URI=${MONGODB_URI} npm run ${TEST_NPM_SCRIPT}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"check:ts": "tsc -v && tsc --noEmit",
122122
"check:atlas": "mocha --config \"test/manual/mocharc.json\" test/manual/atlas_connectivity.test.js",
123123
"check:adl": "mocha test/manual/data_lake.test.js",
124+
"check:load-balancer": "mocha test/manual/load-balancer.test.js",
124125
"check:ocsp": "mocha --config \"test/manual/mocharc.json\" test/manual/ocsp_support.test.js",
125126
"check:kerberos": "mocha --config \"test/manual/mocharc.json\" test/manual/kerberos.test.js",
126127
"check:tls": "mocha --config \"test/manual/mocharc.json\" test/manual/tls_support.test.js",

src/cmap/command_monitoring_events.ts

+31-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { GetMore, KillCursor, Msg, WriteProtocolMessageType } from './commands';
22
import { calculateDurationInMs, deepCopy } from '../utils';
3-
import type { ConnectionPool } from './connection_pool';
43
import type { Connection } from './connection';
5-
import type { Document } from '../bson';
4+
import type { Document, ObjectId } from '../bson';
65

76
/**
87
* An event indicating the start of a given
@@ -17,6 +16,7 @@ export class CommandStartedEvent {
1716
command: Document;
1817
address: string;
1918
connectionId?: string | number;
19+
serviceId?: ObjectId;
2020

2121
/**
2222
* Create a started event
@@ -25,10 +25,10 @@ export class CommandStartedEvent {
2525
* @param pool - the pool that originated the command
2626
* @param command - the command
2727
*/
28-
constructor(pool: Connection | ConnectionPool, command: WriteProtocolMessageType) {
28+
constructor(connection: Connection, command: WriteProtocolMessageType) {
2929
const cmd = extractCommand(command);
3030
const commandName = extractCommandName(cmd);
31-
const { address, connectionId } = extractConnectionDetails(pool);
31+
const { address, connectionId, serviceId } = extractConnectionDetails(connection);
3232

3333
// TODO: remove in major revision, this is not spec behavior
3434
if (SENSITIVE_COMMANDS.has(commandName)) {
@@ -38,11 +38,17 @@ export class CommandStartedEvent {
3838

3939
this.address = address;
4040
this.connectionId = connectionId;
41+
this.serviceId = serviceId;
4142
this.requestId = command.requestId;
4243
this.databaseName = databaseName(command);
4344
this.commandName = commandName;
4445
this.command = maybeRedact(commandName, cmd, cmd);
4546
}
47+
48+
/* @internal */
49+
get hasServiceId(): boolean {
50+
return !!this.serviceId;
51+
}
4652
}
4753

4854
/**
@@ -57,6 +63,7 @@ export class CommandSucceededEvent {
5763
duration: number;
5864
commandName: string;
5965
reply: unknown;
66+
serviceId?: ObjectId;
6067

6168
/**
6269
* Create a succeeded event
@@ -68,22 +75,28 @@ export class CommandSucceededEvent {
6875
* @param started - a high resolution tuple timestamp of when the command was first sent, to calculate duration
6976
*/
7077
constructor(
71-
pool: Connection | ConnectionPool,
78+
connection: Connection,
7279
command: WriteProtocolMessageType,
7380
reply: Document | undefined,
7481
started: number
7582
) {
7683
const cmd = extractCommand(command);
7784
const commandName = extractCommandName(cmd);
78-
const { address, connectionId } = extractConnectionDetails(pool);
85+
const { address, connectionId, serviceId } = extractConnectionDetails(connection);
7986

8087
this.address = address;
8188
this.connectionId = connectionId;
89+
this.serviceId = serviceId;
8290
this.requestId = command.requestId;
8391
this.commandName = commandName;
8492
this.duration = calculateDurationInMs(started);
8593
this.reply = maybeRedact(commandName, cmd, extractReply(command, reply));
8694
}
95+
96+
/* @internal */
97+
get hasServiceId(): boolean {
98+
return !!this.serviceId;
99+
}
87100
}
88101

89102
/**
@@ -98,6 +111,8 @@ export class CommandFailedEvent {
98111
duration: number;
99112
commandName: string;
100113
failure: Error;
114+
serviceId?: ObjectId;
115+
101116
/**
102117
* Create a failure event
103118
*
@@ -108,23 +123,29 @@ export class CommandFailedEvent {
108123
* @param started - a high resolution tuple timestamp of when the command was first sent, to calculate duration
109124
*/
110125
constructor(
111-
pool: Connection | ConnectionPool,
126+
connection: Connection,
112127
command: WriteProtocolMessageType,
113128
error: Error | Document,
114129
started: number
115130
) {
116131
const cmd = extractCommand(command);
117132
const commandName = extractCommandName(cmd);
118-
const { address, connectionId } = extractConnectionDetails(pool);
133+
const { address, connectionId, serviceId } = extractConnectionDetails(connection);
119134

120135
this.address = address;
121136
this.connectionId = connectionId;
137+
this.serviceId = serviceId;
122138

123139
this.requestId = command.requestId;
124140
this.commandName = commandName;
125141
this.duration = calculateDurationInMs(started);
126142
this.failure = maybeRedact(commandName, cmd, error) as Error;
127143
}
144+
145+
/* @internal */
146+
get hasServiceId(): boolean {
147+
return !!this.serviceId;
148+
}
128149
}
129150

130151
/** Commands that we want to redact because of the sensitive nature of their contents */
@@ -300,13 +321,14 @@ function extractReply(command: WriteProtocolMessageType, reply?: Document) {
300321
return deepCopy(reply.result ? reply.result : reply);
301322
}
302323

303-
function extractConnectionDetails(connection: Connection | ConnectionPool) {
324+
function extractConnectionDetails(connection: Connection) {
304325
let connectionId;
305326
if ('id' in connection) {
306327
connectionId = connection.id;
307328
}
308329
return {
309330
address: connection.address,
331+
serviceId: connection.serviceId,
310332
connectionId
311333
};
312334
}

src/cmap/connect.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ import {
2020
MIN_SUPPORTED_SERVER_VERSION
2121
} from './wire_protocol/constants';
2222
import type { Document } from '../bson';
23+
import { Int32 } from '../bson';
2324

2425
import type { Socket, SocketConnectOpts } from 'net';
2526
import type { TLSSocket, ConnectionOptions as TLSConnectionOpts } from 'tls';
26-
import { Int32 } from '../bson';
27+
28+
const FAKE_MONGODB_SERVICE_ID =
29+
typeof process.env.FAKE_MONGODB_SERVICE_ID === 'string' &&
30+
process.env.FAKE_MONGODB_SERVICE_ID.toLowerCase() === 'true';
2731

2832
/** @public */
2933
export type Stream = Socket | TLSSocket;
@@ -133,6 +137,21 @@ function performInitialHandshake(
133137
return;
134138
}
135139

140+
if (options.loadBalanced) {
141+
// TODO: Durran: Remove when server support exists. (NODE-3431)
142+
if (FAKE_MONGODB_SERVICE_ID) {
143+
response.serviceId = response.topologyVersion.processId;
144+
}
145+
if (!response.serviceId) {
146+
return callback(
147+
new MongoDriverError(
148+
'Driver attempted to initialize in load balancing mode, ' +
149+
'but the server does not support this mode.'
150+
)
151+
);
152+
}
153+
}
154+
136155
// NOTE: This is metadata attached to the connection while porting away from
137156
// handshake being done in the `Server` class. Likely, it should be
138157
// relocated, or at very least restructured.
@@ -172,6 +191,7 @@ export interface HandshakeDocument extends Document {
172191
client: ClientMetadata;
173192
compression: string[];
174193
saslSupportedMechs?: string;
194+
loadBalanced: boolean;
175195
}
176196

177197
function prepareHandshakeDocument(authContext: AuthContext, callback: Callback<HandshakeDocument>) {
@@ -183,7 +203,8 @@ function prepareHandshakeDocument(authContext: AuthContext, callback: Callback<H
183203
[serverApi?.version ? 'hello' : 'ismaster']: true,
184204
helloOk: true,
185205
client: options.metadata || makeClientMetadata(options),
186-
compression: compressors
206+
compression: compressors,
207+
loadBalanced: options.loadBalanced
187208
};
188209

189210
const credentials = authContext.credentials;

0 commit comments

Comments
 (0)