Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(NODE-5968): Container and Kubernetes Awareness #4005

Merged
merged 16 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/cmap/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
type ConnectionOptions,
CryptoConnection
} from './connection';
import type { ClientMetadata } from './handshake/client_metadata';
import {
MAX_SUPPORTED_SERVER_VERSION,
MAX_SUPPORTED_WIRE_VERSION,
Expand Down Expand Up @@ -183,7 +182,7 @@ export interface HandshakeDocument extends Document {
ismaster?: boolean;
hello?: boolean;
helloOk?: boolean;
client: ClientMetadata;
client: Document;
baileympearson marked this conversation as resolved.
Show resolved Hide resolved
compression: string[];
saslSupportedMechs?: string;
loadBalanced?: boolean;
Expand All @@ -200,11 +199,12 @@ export async function prepareHandshakeDocument(
const options = authContext.options;
const compressors = options.compressors ? options.compressors : [];
const { serverApi } = authContext.connection;
const clientMetadata: Document = await options.extendedMetadata;

const handshakeDoc: HandshakeDocument = {
[serverApi?.version || options.loadBalanced === true ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
helloOk: true,
client: options.metadata,
client: clientMetadata,
compression: compressors
};

Expand Down
2 changes: 2 additions & 0 deletions src/cmap/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export interface ConnectionOptions
cancellationToken?: CancellationToken;
metadata: ClientMetadata;
/** @internal */
extendedMetadata: Promise<Document>;
/** @internal */
mongoLogger?: MongoLogger | undefined;
}

Expand Down
3 changes: 1 addition & 2 deletions src/cmap/connection_pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
maxIdleTimeMS: options.maxIdleTimeMS ?? 0,
waitQueueTimeoutMS: options.waitQueueTimeoutMS ?? 0,
minPoolSizeCheckFrequencyMS: options.minPoolSizeCheckFrequencyMS ?? 100,
autoEncrypter: options.autoEncrypter,
metadata: options.metadata
autoEncrypter: options.autoEncrypter
});

if (this.options.minPoolSize > this.options.maxPoolSize) {
Expand Down
58 changes: 54 additions & 4 deletions src/cmap/handshake/client_metadata.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { promises as fs } from 'fs';
import * as os from 'os';
import * as process from 'process';

import { BSON, Int32 } from '../../bson';
import { BSON, type Document, Int32 } from '../../bson';
import { MongoInvalidArgumentError } from '../../error';
import type { MongoOptions } from '../../mongo_client';

Expand Down Expand Up @@ -71,13 +72,13 @@ export class LimitedSizeDocument {
return true;
}

toObject(): ClientMetadata {
toObject(): Document {
return BSON.deserialize(BSON.serialize(this.document), {
promoteLongs: false,
promoteBuffers: false,
promoteValues: false,
useBigInt64: false
}) as ClientMetadata;
});
}
}

Expand Down Expand Up @@ -152,8 +153,57 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe
}
}
}
return metadataDocument.toObject() as ClientMetadata;
}

let dockerPromise: Promise<boolean>;
/** @internal */
async function getContainerMetadata() {
const containerMetadata: Record<string, any> = {};
dockerPromise ??= fs.access('/.dockerenv').then(
() => true,
() => false
);
const isDocker = await dockerPromise;

const { KUBERNETES_SERVICE_HOST = '' } = process.env;
const isKubernetes = KUBERNETES_SERVICE_HOST.length > 0 ? true : false;

if (isDocker) containerMetadata.runtime = 'docker';
if (isKubernetes) containerMetadata.orchestrator = 'kubernetes';

return containerMetadata;
}

/**
* @internal
* Re-add each metadata value.
* Attempt to add new env container metadata, but keep old data if it does not fit.
*/
export async function addContainerMetadata(originalMetadata: ClientMetadata) {
const containerMetadata = await getContainerMetadata();
if (Object.keys(containerMetadata).length === 0) return originalMetadata;

const extendedMetadata = new LimitedSizeDocument(512);

const extendedEnvMetadata = { ...originalMetadata?.env, container: containerMetadata };

for (const [key, val] of Object.entries(originalMetadata)) {
if (key !== 'env') {
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
extendedMetadata.ifItFitsItSits(key, val);
} else {
if (!extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata)) {
// add in old data if newer / extended metadata does not fit
extendedMetadata.ifItFitsItSits('env', val);
}
}
}

if (!('env' in originalMetadata)) {
extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata);
}

return metadataDocument.toObject();
return extendedMetadata.toObject();
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/connection_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { URLSearchParams } from 'url';
import type { Document } from './bson';
import { MongoCredentials } from './cmap/auth/mongo_credentials';
import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './cmap/auth/providers';
import { makeClientMetadata } from './cmap/handshake/client_metadata';
import { addContainerMetadata, makeClientMetadata } from './cmap/handshake/client_metadata';
import { Compressor, type CompressorName } from './cmap/wire_protocol/compression';
import { Encrypter } from './encrypter';
import {
Expand Down Expand Up @@ -552,6 +552,10 @@ export function parseOptions(

mongoOptions.metadata = makeClientMetadata(mongoOptions);

mongoOptions.extendedMetadata = addContainerMetadata(mongoOptions.metadata).catch(() => {
/* rejections will be handled later */
});

return mongoOptions;
}

Expand Down
2 changes: 2 additions & 0 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,8 @@ export interface MongoOptions
dbName: string;
metadata: ClientMetadata;
/** @internal */
extendedMetadata: Promise<Document>;
/** @internal */
autoEncrypter?: AutoEncrypter;
proxyHost?: string;
proxyPort?: number;
Expand Down
1 change: 1 addition & 0 deletions src/sdam/topology.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
directConnection: boolean;
loadBalanced: boolean;
metadata: ClientMetadata;
extendedMetadata: Promise<Document>;
serverMonitoringMode: ServerMonitoringMode;
/** MongoDB server API version */
serverApi?: ServerApi;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect } from 'chai';

import {
addContainerMetadata,
connect,
Connection,
type ConnectionOptions,
Expand Down Expand Up @@ -50,7 +51,8 @@ describe('Connection', function () {
...commonConnectOptions,
connectionType: Connection,
...this.configuration.options,
metadata: makeClientMetadata({ driverInfo: {} })
metadata: makeClientMetadata({ driverInfo: {} }),
extendedMetadata: addContainerMetadata(makeClientMetadata({ driverInfo: {} }))
};

let conn;
Expand All @@ -72,7 +74,8 @@ describe('Connection', function () {
connectionType: Connection,
...this.configuration.options,
monitorCommands: true,
metadata: makeClientMetadata({ driverInfo: {} })
metadata: makeClientMetadata({ driverInfo: {} }),
extendedMetadata: addContainerMetadata(makeClientMetadata({ driverInfo: {} }))
};

let conn;
Expand Down
9 changes: 8 additions & 1 deletion test/tools/cmap_spec_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { clearTimeout, setTimeout } from 'timers';
import { promisify } from 'util';

import {
addContainerMetadata,
CMAP_EVENTS,
type Connection,
ConnectionPool,
Expand Down Expand Up @@ -369,6 +370,7 @@ async function runCmapTest(test: CmapTest, threadContext: ThreadContext) {
}

const metadata = makeClientMetadata({ appName: poolOptions.appName, driverInfo: {} });
const extendedMetadata = addContainerMetadata(metadata);
delete poolOptions.appName;

const operations = test.operations;
Expand All @@ -380,7 +382,12 @@ async function runCmapTest(test: CmapTest, threadContext: ThreadContext) {
const mainThread = threadContext.getThread(MAIN_THREAD_KEY);
mainThread.start();

threadContext.createPool({ ...poolOptions, metadata, minPoolSizeCheckFrequencyMS });
threadContext.createPool({
...poolOptions,
metadata,
extendedMetadata,
minPoolSizeCheckFrequencyMS
});
// yield control back to the event loop so that the ConnectionPoolCreatedEvent
// has a chance to be fired before any synchronously-emitted events from
// the queued operations
Expand Down