SERVER-112868 Introduce option to ReplSetTest and ShardingTest for opening a maintenance port (#43334)

Co-authored-by: Steve McClure <steve.mcclure@mongodb.com>
GitOrigin-RevId: 3e18489bb2ecb4da1167b51c2832a3d01e54a8c1
This commit is contained in:
Allison Easton 2025-11-04 10:01:16 +01:00 committed by MongoDB Bot
parent c2c67e0bb2
commit df9c6590ef
4 changed files with 144 additions and 0 deletions

View File

@ -68,6 +68,8 @@ export class ReplSetTest {
* ShardingTest, the seed is generated as part of ShardingTest.
* @param {boolean} [opts.useAutoBootstrapProcedure] If true, follow the procedure for
* auto-bootstrapped replica sets.
* @param {boolean} [opts.useMaintenancePorts=false] If true, then a maintenance port will be
* opened for each node in the replica set.
* @param {number} [opts.timeoutMS] Timeout value in milliseconds.
*/
constructor(opts) {
@ -306,6 +308,14 @@ export class ReplSetTest {
return this.ports[n];
}
getMaintenancePort(n) {
const translatedN = this.getNodeId(n);
if (this._maintenancePorts?.[translatedN] < 1) {
throw new Error("Maintenance port not set for node");
}
return this._maintenancePorts[translatedN];
}
getDbPath(node) {
// Get a replica set node (check for use of bridge).
const n = this.getNodeId(node);
@ -1007,6 +1017,10 @@ export class ReplSetTest {
this._unbridgedPorts.push(this._allocatePortForBridge());
}
if (this._useMaintenancePorts) {
this._maintenancePorts.push(this._allocatePortForNode());
}
if (jsTestOptions().shellGRPC) {
const nextPort = this._allocatePortForNode();
jsTest.log.info("ReplSetTest Next gRPC port: " + nextPort);
@ -1036,6 +1050,10 @@ export class ReplSetTest {
this._unbridgedPorts.splice(nodeId, 1);
this._unbridgedNodes.splice(nodeId, 1);
}
if (this._maintenancePorts) {
this._maintenancePorts.splice(nodeId, 1);
}
}
/**
@ -2793,6 +2811,9 @@ export class ReplSetTest {
port: this._useBridge ? this._unbridgedPorts[n] : this.ports[n],
dbpath: "$set-$node",
};
if (this._maintenancePorts?.[n] > 0) {
defaults.maintenancePort = this._maintenancePorts[n];
}
if (jsTestOptions().shellGRPC) {
defaults.grpcPort = this.grpcPorts[n];
}
@ -3537,6 +3558,11 @@ function _constructStartNewInstances(rst, opts) {
rst._bridgeOptions = opts.bridgeOptions || {};
rst._useMaintenancePorts = opts.useMaintenancePorts ?? false;
if (rst._useMaintenancePorts) {
assert(!rst._useBridge, "useMaintenancePorts is not supported when using MongoBridge.");
}
rst._causalConsistency = opts.causallyConsistent || false;
rst._configSettings = opts.settings || false;
@ -3618,6 +3644,9 @@ function _constructStartNewInstances(rst, opts) {
rst._unbridgedNodes = [];
} else {
rst.ports = opts.ports || Array.from({length: numNodes}, rst._allocatePortForNode);
if (rst._useMaintenancePorts) {
rst._maintenancePorts = Array.from({length: numNodes}, rst._allocatePortForNode);
}
}
for (let i = 0; i < numNodes; i++) {
@ -3629,6 +3658,12 @@ function _constructStartNewInstances(rst, opts) {
rst.ports[i] = nodeOpts.port;
}
}
if (nodeOpts?.hasOwnProperty("maintenancePort")) {
if (!rst._maintenancePorts) {
rst._maintenancePorts = Array(numNodes).fill(-1);
}
rst._maintenancePorts[i] = nodeOpts.maintenancePort;
}
}
if (jsTestOptions().shellGRPC) {

View File

@ -929,6 +929,8 @@ export class ShardingTest {
* @property {Object} [bridgeOptions={}] Options to apply to all mongobridge processes.
* @property {Object} [rsOptions] Same as the `rs` parameter to ShardingTest constructor. Can be
* used to specify options that are common all replica members.
* @property {boolean} [useMaintenancePorts=false] If true, then a maintenance port will be
* specified for each node in the cluster.
*
* // replica Set only:
* @property {boolean} [useHostname] if true, use hostname of machine, otherwise use localhost
@ -1116,6 +1118,7 @@ export class ShardingTest {
otherParams.useBridge = otherParams.useBridge || false;
otherParams.bridgeOptions = otherParams.bridgeOptions || {};
otherParams.causallyConsistent = otherParams.causallyConsistent || false;
otherParams.useMaintenancePorts = otherParams.useMaintenancePorts ?? false;
if (jsTestOptions().networkMessageCompressors) {
otherParams.bridgeOptions["networkMessageCompressors"] =
@ -1142,6 +1145,7 @@ export class ShardingTest {
"useBridge cannot be true when using TLS. Add the requires_mongobridge tag to the test to ensure it will be skipped on variants that use TLS.",
);
}
this._useMaintenancePorts = otherParams.useMaintenancePorts;
this._unbridgedMongos = [];
let _allocatePortForMongos;
@ -1221,6 +1225,7 @@ export class ShardingTest {
host: hostName,
useBridge: otherParams.useBridge,
bridgeOptions: otherParams.bridgeOptions,
useMaintenancePorts: otherParams.useMaintenancePorts,
keyFile: this.keyFile,
waitForKeys: false,
name: testName + "-configRS",
@ -1359,6 +1364,7 @@ export class ShardingTest {
useHostName: otherParams.useHostname,
useBridge: otherParams.useBridge,
bridgeOptions: otherParams.bridgeOptions,
useMaintenancePorts: otherParams.useMaintenancePorts,
keyFile: this.keyFile,
protocolVersion: protocolVersion,
waitForKeys: false,
@ -1643,6 +1649,9 @@ export class ShardingTest {
}
options.port = options.port || _allocatePortForMongos();
if (this._useMaintenancePorts || options.hasOwnProperty("maintenancePort")) {
options.maintenancePort = options.hasOwnProperty("maintenancePort") ? options.maintenancePort : _allocatePortForMongos();
}
if (jsTestOptions().shellGRPC) {
options.grpcPort = options.grpcPort || _allocatePortForMongos();
}

View File

@ -0,0 +1,94 @@
/**
* Test that the maintenance port option is specified correctly in a ReplSetTest and a ShardingTest.
*
* TODO (SERVER-112674): Extend the integration to expose connections on both the main and
* maintenance ports and add replace the testing coverage via logging with connection based tests.
*
* @tags: [
* requires_replication,
* requires_sharding,
* featureFlagDedicatedPortForMaintenanceOperations,
* ]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";
import {describe, it} from "jstests/libs/mochalite.js";
describe("Tests for maintenance port usage within JS test helpers", function () {
const checkMaintenancePortSet = (conn, port) => {
assert.eq(port, conn.maintenancePort);
assert(
checkLog.checkContainsOnceJson(conn, 21951, {
options: (opts) => {
return opts.net.maintenancePort == port;
},
}),
);
};
// TODO (SERVER-112674): Remove this. For now, we set the log verbosity very low to ensure we still see the startup logs
const verbosityOptions = {setParameter: {logComponentVerbosity: {verbosity: 0}}};
it("Starting up a replica set with maintenance ports", () => {
const rs = new ReplSetTest({nodes: 3, useMaintenancePorts: true, nodeOptions: verbosityOptions});
rs.startSet();
rs.initiate();
rs.nodes.forEach((conn) => {
let maintenancePort = rs.getMaintenancePort(conn);
checkMaintenancePortSet(conn, maintenancePort);
});
rs.stopSet();
});
it("Starting up a replica set with a specific node having a maintenance port", () => {
const rs = new ReplSetTest({nodes: [{}, {maintenancePort: 27021}, {}], nodeOptions: verbosityOptions});
rs.startSet();
rs.initiate();
let countFoundPorts = 0;
rs.nodes.forEach((conn) => {
try {
assert.eq(27021, rs.getMaintenancePort(conn));
checkMaintenancePortSet(conn, 27021);
countFoundPorts += 1;
} catch (e) {
// This should throw if the maintenance port is not set for the node.
assert(!conn.maintenancePort);
}
});
assert.eq(countFoundPorts, 1);
rs.stopSet();
});
it("Starting up a sharded cluster with maintenance ports", () => {
const st = new ShardingTest({
shards: 1,
mongos: 2,
useMaintenancePorts: true,
other: {nodeOptions: verbosityOptions, configOptions: verbosityOptions, mongosOptions: verbosityOptions},
});
st.configRS.nodes.forEach((conn) => {
let maintenancePort = st.configRS.getMaintenancePort(conn);
checkMaintenancePortSet(conn, maintenancePort);
});
st.rs0.nodes.forEach((conn) => {
let maintenancePort = st.rs0.getMaintenancePort(conn);
checkMaintenancePortSet(conn, maintenancePort);
});
st._mongos.forEach((conn) => {
let maintenancePort = conn.maintenancePort;
checkMaintenancePortSet(conn, maintenancePort);
});
st.stop();
});
});

View File

@ -961,6 +961,9 @@ MongoRunner.runMongod = function (opts) {
mongod.name = mongod.hostNoPort + ":" + mongod.commandLine.port;
mongod.host = mongod.hostNoPort + ":" + connectPort;
mongod.port = parseInt(connectPort);
if (mongod.commandLine.maintenancePort > 0) {
mongod.maintenancePort = mongod.commandLine.maintenancePort;
}
mongod.runId = runId || ObjectId();
mongod.dbpath = fullOptions.dbpath;
mongod.savedOptions = MongoRunner.savedOptions[mongod.runId];
@ -1005,6 +1008,9 @@ MongoRunner.runMongos = function (opts) {
mongos.name = MongoRunner.getMongosName(mongos.commandLine.port, useHostName);
mongos.host = MongoRunner.getMongosName(connectPort, useHostName);
mongos.port = parseInt(connectPort);
if (mongos.commandLine.maintenancePort > 0) {
mongos.maintenancePort = mongos.commandLine.maintenancePort;
}
mongos.runId = runId || ObjectId();
mongos.savedOptions = MongoRunner.savedOptions[mongos.runId];
mongos.fullOptions = fullOptions;