Skip to content

Commit

Permalink
FAAS metadata commit
Browse files Browse the repository at this point in the history
  • Loading branch information
baileympearson committed Apr 3, 2023
1 parent 6d894d6 commit c623356
Show file tree
Hide file tree
Showing 19 changed files with 905 additions and 203 deletions.
7 changes: 4 additions & 3 deletions src/cmap/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
MongoRuntimeError,
needsRetryableWriteLabel
} from '../error';
import { Callback, ClientMetadata, HostAddress, ns } from '../utils';
import { Callback, HostAddress, ns } from '../utils';
import { AuthContext, AuthProvider } from './auth/auth_provider';
import { GSSAPI } from './auth/gssapi';
import { MongoCR } from './auth/mongocr';
Expand All @@ -28,6 +28,7 @@ import { AuthMechanism } from './auth/providers';
import { ScramSHA1, ScramSHA256 } from './auth/scram';
import { X509 } from './auth/x509';
import { CommandOptions, Connection, ConnectionOptions, CryptoConnection } from './connection';
import type { TruncatedClientMetadata } from './handshake/client_metadata';
import {
MAX_SUPPORTED_SERVER_VERSION,
MAX_SUPPORTED_WIRE_VERSION,
Expand Down Expand Up @@ -192,7 +193,7 @@ export interface HandshakeDocument extends Document {
ismaster?: boolean;
hello?: boolean;
helloOk?: boolean;
client: ClientMetadata;
client: TruncatedClientMetadata;
compression: string[];
saslSupportedMechs?: string;
loadBalanced?: boolean;
Expand All @@ -213,7 +214,7 @@ export async function prepareHandshakeDocument(
const handshakeDoc: HandshakeDocument = {
[serverApi?.version ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
helloOk: true,
client: options.metadata,
client: options.truncatedClientMetadata,
compression: compressors
};

Expand Down
5 changes: 4 additions & 1 deletion src/cmap/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { applySession, ClientSession, updateSessionFromResponse } from '../sessi
import {
calculateDurationInMs,
Callback,
ClientMetadata,
HostAddress,
maxWireVersion,
MongoDBNamespace,
Expand All @@ -46,6 +45,7 @@ import {
} from './command_monitoring_events';
import { BinMsg, Msg, Query, Response, WriteProtocolMessageType } from './commands';
import type { Stream } from './connect';
import type { ClientMetadata, TruncatedClientMetadata } from './handshake/client_metadata';
import { MessageStream, OperationDescription } from './message_stream';
import { StreamDescription, StreamDescriptionOptions } from './stream_description';
import { getReadPreference, isSharded } from './wire_protocol/shared';
Expand Down Expand Up @@ -128,6 +128,9 @@ export interface ConnectionOptions
socketTimeoutMS?: number;
cancellationToken?: CancellationToken;
metadata: ClientMetadata;

/** @internal */
truncatedClientMetadata: TruncatedClientMetadata;
}

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

if (this.options.minPoolSize > this.options.maxPoolSize) {
Expand Down
124 changes: 124 additions & 0 deletions src/cmap/handshake/client_metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { calculateObjectSize } from 'bson';
import * as os from 'os';

import type { MongoOptions } from '../../mongo_client';
import { deepCopy, DeepPartial } from '../../utils';
import { applyFaasEnvMetadata } from './faas_provider';

/**
* @public
* @see https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst#hello-command
*/
export interface ClientMetadata {
driver: {
name: string;
version: string;
};
os: {
type: string;
name: NodeJS.Platform;
architecture: string;
version: string;
};
platform: string;
application?: {
name: string;
};

/** Data containing information about the environment, if the driver is running in a FAAS environment. */
env?: {
name: 'aws.lambda' | 'gcp.func' | 'azure.func' | 'vercel';
timeout_sec?: number;
memory_mb?: number;
region?: string;
url?: string;
};
}

/** @internal */
export type TruncatedClientMetadata = DeepPartial<ClientMetadata>;

/**
* @internal
* truncates the client metadata according to the priority outlined here
* https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst#limitations
*/
export function truncateClientMetadata(metadata: ClientMetadata): TruncatedClientMetadata {
const copiedMetadata: TruncatedClientMetadata = deepCopy(metadata);
const truncations: Array<(arg0: TruncatedClientMetadata) => void> = [
m => delete m.platform,
m => {
if (m.env) {
m.env = { name: m.env.name };
}
},
m => {
if (m.os) {
m.os = { type: m.os.type };
}
},
m => delete m.env,
m => delete m.os,
m => delete m.driver,
m => delete m.application
];

for (const truncation of truncations) {
if (calculateObjectSize(copiedMetadata) <= 512) {
return copiedMetadata;
}
truncation(copiedMetadata);
}

return copiedMetadata;
}

/** @public */
export interface ClientMetadataOptions {
driverInfo?: {
name?: string;
version?: string;
platform?: string;
};
appName?: string;
}

// eslint-disable-next-line @typescript-eslint/no-var-requires
const NODE_DRIVER_VERSION = require('../../../package.json').version;

export function makeClientMetadata(
options: Pick<MongoOptions, 'appName' | 'driverInfo'>
): ClientMetadata {
const name = options.driverInfo.name ? `nodejs|${options.driverInfo.name}` : 'nodejs';
const version = options.driverInfo.version
? `${NODE_DRIVER_VERSION}|${options.driverInfo.version}`
: NODE_DRIVER_VERSION;
const platform = options.driverInfo.platform
? `Node.js ${process.version}, ${os.endianness()}|${options.driverInfo.platform}`
: `Node.js ${process.version}, ${os.endianness()}`;

const metadata: ClientMetadata = {
driver: {
name,
version
},
os: {
type: os.type(),
name: process.platform,
architecture: process.arch,
version: os.release()
},
platform
};

if (options.appName) {
// MongoDB requires the appName not exceed a byte length of 128
const name =
Buffer.byteLength(options.appName, 'utf8') <= 128
? options.appName
: Buffer.from(options.appName, 'utf8').subarray(0, 128).toString('utf8');
metadata.application = { name };
}

return applyFaasEnvMetadata(metadata);
}
84 changes: 84 additions & 0 deletions src/cmap/handshake/faas_provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { identity } from '../../utils';
import type { ClientMetadata } from './client_metadata';

export type FAASProvider = 'aws' | 'gcp' | 'azure' | 'vercel' | 'none';

export function determineCloudProvider(): FAASProvider {
const awsPresent = process.env.AWS_EXECUTION_ENV || process.env.AWS_LAMBDA_RUNTIME_API;
const azurePresent = process.env.FUNCTIONS_WORKER_RUNTIME;
const gcpPresent = process.env.K_SERVICE || process.env.FUNCTION_NAME;
const vercelPresent = process.env.VERCEL;

const numberOfProvidersPresent = [awsPresent, azurePresent, gcpPresent, vercelPresent].filter(
identity
).length;

if (numberOfProvidersPresent !== 1) {
return 'none';
}

if (awsPresent) return 'aws';
if (azurePresent) return 'azure';
if (gcpPresent) return 'gcp';
return 'vercel';
}

function applyAzureMetadata(m: ClientMetadata): ClientMetadata {
m.env = { name: 'azure.func' };
return m;
}

function applyGCPMetadata(m: ClientMetadata): ClientMetadata {
m.env = { name: 'gcp.func' };

const memory_mb = Number.parseInt(process.env.FUNCTION_MEMORY_MB ?? '');
if (!Number.isNaN(memory_mb)) {
m.env.memory_mb = memory_mb;
}
const timeout_sec = Number.parseInt(process.env.FUNCTION_TIMEOUT_SEC ?? '');
if (!Number.isNaN(timeout_sec)) {
m.env.timeout_sec = timeout_sec;
}
if (process.env.FUNCTION_REGION) {
m.env.region = process.env.FUNCTION_REGION;
}

return m;
}

function applyAWSMetadata(m: ClientMetadata): ClientMetadata {
m.env = { name: 'aws.lambda' };
if (process.env.AWS_REGION) {
m.env.region = process.env.AWS_REGION;
}
const memory_mb = Number.parseInt(process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE ?? '');
if (!Number.isNaN(memory_mb)) {
m.env.memory_mb = memory_mb;
}
return m;
}

function applyVercelMetadata(m: ClientMetadata): ClientMetadata {
m.env = { name: 'vercel' };
if (process.env.VERCEL_URL) {
m.env.url = process.env.VERCEL_URL;
}
if (process.env.VERCEL_REGION) {
m.env.region = process.env.VERCEL_REGION;
}
return m;
}

export function applyFaasEnvMetadata(metadata: ClientMetadata): ClientMetadata {
const handlerMap: Record<FAASProvider, (m: ClientMetadata) => ClientMetadata> = {
aws: applyAWSMetadata,
gcp: applyGCPMetadata,
azure: applyAzureMetadata,
vercel: applyVercelMetadata,
none: identity
};
const cloudProvider = determineCloudProvider();

const faasMetadataProvider = handlerMap[cloudProvider];
return faasMetadataProvider(metadata);
}
4 changes: 3 additions & 1 deletion src/connection_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,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, truncateClientMetadata } from './cmap/handshake/client_metadata';
import { Compressor, CompressorName } from './cmap/wire_protocol/compression';
import { Encrypter } from './encrypter';
import {
Expand All @@ -32,7 +33,6 @@ import {
emitWarningOnce,
HostAddress,
isRecord,
makeClientMetadata,
parseInteger,
setDifference
} from './utils';
Expand Down Expand Up @@ -543,6 +543,8 @@ export function parseOptions(
);

mongoOptions.metadata = makeClientMetadata(mongoOptions);
Object.freeze(mongoOptions.metadata);
mongoOptions.truncatedClientMetadata = truncateClientMetadata(mongoOptions.metadata);

return mongoOptions;
}
Expand Down
8 changes: 6 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ export type {
WaitQueueMember,
WithConnectionCallback
} from './cmap/connection_pool';
export type {
ClientMetadata,
ClientMetadataOptions,
TruncatedClientMetadata
} from './cmap/handshake/client_metadata';
export type {
MessageStream,
MessageStreamOptions,
Expand Down Expand Up @@ -463,8 +468,7 @@ export type { Transaction, TransactionOptions, TxnState } from './transactions';
export type {
BufferPool,
Callback,
ClientMetadata,
ClientMetadataOptions,
DeepPartial,
EventEmitterWithState,
HostAddress,
List,
Expand Down
17 changes: 15 additions & 2 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { AuthMechanismProperties, MongoCredentials } from './cmap/auth/mong
import type { AuthMechanism } from './cmap/auth/providers';
import type { LEGAL_TCP_SOCKET_OPTIONS, LEGAL_TLS_SOCKET_OPTIONS } from './cmap/connect';
import type { Connection } from './cmap/connection';
import type { ClientMetadata, TruncatedClientMetadata } from './cmap/handshake/client_metadata';
import type { CompressorName } from './cmap/wire_protocol/compression';
import { parseOptions, resolveSRVRecord } from './connection_string';
import { MONGO_CLIENT_EVENTS } from './constants';
Expand All @@ -24,7 +25,7 @@ import { readPreferenceServerSelector } from './sdam/server_selection';
import type { SrvPoller } from './sdam/srv_polling';
import { Topology, TopologyEvents } from './sdam/topology';
import { ClientSession, ClientSessionOptions, ServerSessionPool } from './sessions';
import { ClientMetadata, HostAddress, MongoDBNamespace, ns, resolveOptions } from './utils';
import { HostAddress, MongoDBNamespace, ns, resolveOptions } from './utils';
import type { W, WriteConcern, WriteConcernSettings } from './write_concern';

/** @public */
Expand Down Expand Up @@ -711,12 +712,24 @@ export interface MongoOptions
compressors: CompressorName[];
writeConcern: WriteConcern;
dbName: string;
metadata: ClientMetadata;
autoEncrypter?: AutoEncrypter;
proxyHost?: string;
proxyPort?: number;
proxyUsername?: string;
proxyPassword?: string;

metadata: ClientMetadata;

/**
* @internal
* `metadata` truncated to be less than 512 bytes, if necessary, to attach to handshakes.
* `metadata` is left untouched because it is public and to provide users a document they
* inspect to confirm their metadata was parsed correctly.
*
* If `metadata` `<=` 512 bytes, these fields are the same but the driver only uses `truncatedMetadata`.
*/
truncatedClientMetadata: TruncatedClientMetadata;

/** @internal */
connectionType?: typeof Connection;

Expand Down
Loading

0 comments on commit c623356

Please sign in to comment.