159 lines
5.1 KiB
JavaScript
159 lines
5.1 KiB
JavaScript
/**
|
|
* Control the proxy protocol server.
|
|
*/
|
|
|
|
import {getPython3Binary} from "jstests/libs/python.js";
|
|
|
|
export class ProxyProtocolServer {
|
|
/**
|
|
* Create a new proxy protocol server.
|
|
*/
|
|
constructor(ingress_port, egress_port, version) {
|
|
this.python = getPython3Binary();
|
|
|
|
print("Using python interpreter: " + this.python);
|
|
this.web_server_py = "jstests/sharding/libs/proxy_protocol_server.py";
|
|
|
|
this.pid = undefined;
|
|
this.ingress_port = ingress_port;
|
|
this.egress_port = egress_port;
|
|
|
|
assert(version === 1 || version === 2);
|
|
this.version = version;
|
|
|
|
this.ingress_address = "127.0.0.1";
|
|
this.egress_address = "127.0.0.1";
|
|
}
|
|
|
|
/**
|
|
* Get the ingress port - the port over which a client wishing to appear proxied should connect.
|
|
*
|
|
* @return {number} ingress port number
|
|
*/
|
|
getIngressPort() {
|
|
return this.ingress_port;
|
|
}
|
|
|
|
/**
|
|
* The the ingress connection string which can be passed to `new Mongo(...)`.
|
|
*/
|
|
getIngressString() {
|
|
return `${this.ingress_address}:${this.ingress_port}`;
|
|
}
|
|
|
|
/**
|
|
* The the egress connection string pointing to the output.
|
|
*/
|
|
getEgressString() {
|
|
return `${this.egress_address}:${this.egress_port}`;
|
|
}
|
|
|
|
/**
|
|
* Get the egress port - the port that mongos should be listening on for proxied connections.
|
|
*
|
|
* @return {number} egress port number
|
|
*/
|
|
getEgressPort() {
|
|
return this.egress_port;
|
|
}
|
|
|
|
/**
|
|
* Start the server.
|
|
*/
|
|
start() {
|
|
const ingressInterface = this.ingress_address + ":" + this.ingress_port;
|
|
|
|
const args = [
|
|
this.python,
|
|
"-u", // unbuffered output
|
|
this.web_server_py,
|
|
"--service",
|
|
ingressInterface,
|
|
this.egress_address + ":" + this.egress_port + "?pp=v" + this.version,
|
|
];
|
|
|
|
clearRawMongoProgramOutput();
|
|
this.pid = _startMongoProgram({args});
|
|
|
|
// Wait for the web server to listen on the configured port.
|
|
let checkProgramResult, checkLogResult;
|
|
const timeoutMillis = 30_000;
|
|
const retryIntervalMillis = 500;
|
|
const expectedLogPattern = `Now listening on ${ingressInterface}`;
|
|
assert.soon(
|
|
() => {
|
|
checkProgramResult = checkProgram(this.pid);
|
|
checkLogResult = rawMongoProgramOutput(expectedLogPattern);
|
|
return checkProgramResult["alive"] && checkLogResult !== "";
|
|
},
|
|
() => {
|
|
if (!checkProgramResult["alive"]) {
|
|
// TODO(SERVER-110719) the fuser and ps here act as diagnostics for future cases of
|
|
// port collision. After more occurences of "address in use", we'll be able to
|
|
// figure out which program is using the same port, and this can be removed.
|
|
jsTestLog("Printing info from ports " + this.ingress_port);
|
|
const commands = [
|
|
["/bin/sh", "-c", "fuser -v -n tcp " + this.ingress_port],
|
|
["/bin/sh", "-c", "ps -ef | grep " + this.ingress_port],
|
|
];
|
|
commands.map((args) => _startMongoProgram({args})).map((pid) => waitProgram(pid));
|
|
}
|
|
return {checkProgramResult, checkLogResult, expectedLogPattern};
|
|
},
|
|
timeoutMillis,
|
|
retryIntervalMillis,
|
|
);
|
|
|
|
print("Proxy Protocol Server sucessfully started.");
|
|
}
|
|
|
|
/**
|
|
* Get the proxy server port used to connect to the egress port provided.
|
|
* That is, return port3 in the following:
|
|
*
|
|
* [client] port1 <-----> ingress_port [proxy] port3 <------> egress_port [mongo(s|d)]
|
|
*/
|
|
getServerPort() {
|
|
const tools = [
|
|
{
|
|
args: ["ss", "-nt", `dst 127.0.0.1:${this.egress_port}`],
|
|
regex: `127.0.0.1:(\\d+)\\s+127.0.0.1:${this.egress_port}`,
|
|
},
|
|
{
|
|
args: ["netstat", "-nt"],
|
|
regex: `127.0.0.1:(\\d+)\\s+127.0.0.1:${this.egress_port}\\s*ESTABLISHED`,
|
|
},
|
|
];
|
|
|
|
for (const {args, regex} of tools) {
|
|
clearRawMongoProgramOutput();
|
|
const exitStatus = waitProgram(_startMongoProgram({args: args}));
|
|
if (exitStatus !== 0) {
|
|
print(`Attempt to use command "${args[0]}" failed with exit status ${exitStatus}`);
|
|
continue;
|
|
}
|
|
|
|
const match = rawMongoProgramOutput(".*").match(regex);
|
|
if (match === null) {
|
|
throw Error(`The output of ${args[0]} did not contain a connection to egress port ${this.egress_port}`);
|
|
}
|
|
|
|
return parseInt(match[1], 10);
|
|
}
|
|
|
|
const commandsJson = JSON.stringify(tools.map((tool) => tool.args[0]));
|
|
throw Error(
|
|
`Could not find connection to egress port ${
|
|
this.egress_port
|
|
}: all of the following commands failed: ${commandsJson}`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Stop the server.
|
|
*/
|
|
stop() {
|
|
stopMongoProgramByPid(this.pid);
|
|
}
|
|
}
|