serverless-plugin-lambda-otel
Version:
A Serverless Framework plugin for enabling AWS OTEL Lambda layer on Lambda services
303 lines (243 loc) • 9.98 kB
JavaScript
/* eslint-disable max-len */
const AddLambdaOTEL = require('./index');
const versions = require('./layerVersions');
const nodejsVersion = versions['nodejs'];
const collectorVersion = versions['collector'];
test('AddLambdaOTEL associates latest ARN', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-${nodejsVersion}:1`]);
});
test('AddLambdaOTEL doesnt add layer when disabled by default', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.custom.lambdaOTEL.enable = false;
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers).toBeUndefined();
expect(plugin.serverless.service.functions.myFunction2.layers).toBeUndefined();
});
test('AddLambdaOTEL doesnt add layer when disabled for specific function', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.functions.myFunction.otelEnable = false;
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers).toBeUndefined();
expect(plugin.serverless.service.functions.myFunction2.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-${nodejsVersion}:1`]);
});
test('AddLambdaOTEL adds OTEL ENV variable to enabled functions for nodejs and java', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.environment).toBeDefined();
expect(plugin.serverless.service.functions.myFunction.environment.AWS_LAMBDA_EXEC_WRAPPER)
.toEqual('/opt/otel-handler');
});
test('AddLambdaOTEL adds OTEL ENV variable to enabled functions for python', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.functions.myPythonFunction = {
handler: 'handler.hello',
runtime: 'python2.7',
architecture: 'arm64',
};
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.environment).toBeDefined();
expect(plugin.serverless.service.functions.myFunction.environment.AWS_LAMBDA_EXEC_WRAPPER)
.toEqual('/opt/otel-handler');
expect(plugin.serverless.service.functions.myPythonFunction.environment).toBeDefined();
expect(plugin.serverless.service.functions.myPythonFunction.environment.AWS_LAMBDA_EXEC_WRAPPER)
.toEqual('/opt/otel-instrument');
});
test('AddLambdaOTEL associates latest ARN for golang global', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.provider.runtime = 'go1.x';
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-collector-amd64-ver-${collectorVersion}:1`]);
});
test('AddLambdaOTEL associates latest ARN for golang in specific function', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.functions.myGolangFunction = {
handler: 'handler.hello',
runtime: 'go1.x',
architecture: 'arm64',
};
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-${nodejsVersion}:1`]);
expect(plugin.serverless.service.functions.myGolangFunction.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-collector-arm64-ver-${collectorVersion}:1`]);
});
test('generateLayerArn defaults to global provider architecture to associates latest ARN for Arm64', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.provider.architecture = 'arm64';
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-arm64-ver-${nodejsVersion}:1`]);
});
test('generateLayerArn local function architecture overwrites global setting to associates latest ARN for Arm64', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.functions.myFunction.architecture = 'arm64';
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-arm64-ver-${nodejsVersion}:1`]);
});
test('generateLayerArn supports multi architecture setting to associate latest ARN', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.functions.myArm64Function = {
handler: 'handler.hello',
architecture: 'arm64',
};
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-${nodejsVersion}:1`]);
expect(plugin.serverless.service.functions.myArm64Function.layers)
.toStrictEqual([`arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-arm64-ver-${nodejsVersion}:1`]);
});
test('AddLambdaOTEL associates correct global layer version', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.custom.lambdaOTEL.version = '2-2-2';
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers)
.toStrictEqual(['arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-2-2-2:1']);
});
test('AddLambdaOTEL associates correct specific function layer version', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.custom.lambdaOTEL.version = '2-2-2';
serverless.service.functions.myFunction.otelVersion = '3-3-3';
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.serverless.service.functions.myFunction.layers)
.toStrictEqual(['arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-3-3-3:1']);
});
test('AddLambdaOTEL throws invalid otel version argument', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.custom.lambdaOTEL.version = 9999;
const plugin = new AddLambdaOTEL(serverless);
// act
const task = () => plugin.instrumentFunctions();
// assert
await expect(task).rejects
.toThrow('custom.lambdaOTEL.version must be string');
});
test('AddLambdaOTEL throws for inexistent version', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.custom.lambdaOTEL.version = '9-9-9';
const plugin = new AddLambdaOTEL(serverless);
// act
const task = () => plugin.instrumentFunctions();
// assert
await expect(task).rejects
.toThrow(/.*doesn\'t exist. Check the provided version/);
});
test('AddLambdaOTEL adds IAM policy', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.service.provider.iamManagedPolicies)
.toStrictEqual(['arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess']);
});
test('AddLambdaOTEL doesnt add IAM policy', async () => {
// arrange
const serverless = createMockServerless('us-east-1');
serverless.service.custom.lambdaOTEL.xrayPolicy = false;
const plugin = new AddLambdaOTEL(serverless);
// act
await plugin.instrumentFunctions();
// assert
expect(plugin.service.provider.iamManagedPolicies)
.toBeUndefined();
});
const createMockServerless = (region) => {
const awsProvider = {
getRegion: () => region,
request: async (service, method, param) => {
// mock this to only validate a specific Arn
if (param.Arn == `arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-2-2-2:1` ||
param.Arn == `arn:aws:lambda:us-east-1:901920570463:layer:aws-otel-nodejs-amd64-ver-3-3-3:1`) {
return {LayerVersionArn: param.Arn};
} else {
const customError = new Error();
customError.code = 'AccessDeniedException';
throw customError;
}
},
};
return {
getProvider: () => awsProvider,
configSchemaHandler: {
defineFunctionProperties: () => jest.fn(),
defineCustomProperties: () => jest.fn(),
},
service: {
provider: {
name: 'aws',
runtime: 'nodejs12.x',
architecture: 'x86_64',
},
custom: {
lambdaOTEL: {
enable: true,
},
},
functions: {
myFunction: {
handler: 'handler.hello',
},
myFunction2: {
handler: 'handler.hello',
},
},
},
};
};