From 4f4d8e7df0a4fe835c25f7725a919ab79f1eb105 Mon Sep 17 00:00:00 2001 From: Alex Casalboni Date: Wed, 14 Jul 2021 14:49:17 +0200 Subject: [PATCH 1/5] Update SDK and mock lib version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 04c2cb8..b209d56 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,8 @@ }, "dependencies": {}, "devDependencies": { - "aws-sdk": "^2.697.0", - "aws-sdk-mock": "^1.7.0", + "aws-sdk": "^2.945.0", + "aws-sdk-mock": "^5.2.1", "coveralls": "^3.0.3", "eslint": "^6.1.0", "eslint-config-strongloop": "^2.1.0", From 68f423dc668c9d6096c7d36e9a5c77beed028874 Mon Sep 17 00:00:00 2001 From: Alex Casalboni Date: Wed, 14 Jul 2021 14:49:55 +0200 Subject: [PATCH 2/5] Implement function state waiter --- lambda/utils.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lambda/utils.js b/lambda/utils.js index 767b0de..7086f43 100644 --- a/lambda/utils.js +++ b/lambda/utils.js @@ -61,6 +61,10 @@ module.exports.verifyAliasExistance = async(lambdaARN, alias) => { module.exports.createPowerConfiguration = async(lambdaARN, value, alias) => { try { await utils.setLambdaPower(lambdaARN, value); + + // wait for functoin update to complete + await utils.waitForFunctionUpdate(lambdaARN); + const {Version} = await utils.publishLambdaVersion(lambdaARN); const aliasExists = await utils.verifyAliasExistance(lambdaARN, alias); if (aliasExists) { @@ -79,6 +83,23 @@ module.exports.createPowerConfiguration = async(lambdaARN, value, alias) => { } }; +/** + * Wait for the function's LastUpdateStatus to become Successful. + * Documentation: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#functionUpdated-waiter + * Why is this needed? https://aws.amazon.com/blogs/compute/coming-soon-expansion-of-aws-lambda-states-to-all-functions/ + */ + module.exports.waitForFunctionUpdate = async(lambdaARN) => { + console.log('Waiting for update to complete'); + const params = { + FunctionName: lambdaARN, + $waiter: { // override delay (5s by default) + delay: 0.5 + } + }; + const lambda = utils.lambdaClientFromARN(lambdaARN); + return lambda.waitFor('functionUpdated', params).promise(); +}; + /** * Retrieve a given Lambda Function's memory size (always $LATEST version) */ From 6537f3519040db142bc4225686f8efae1dc54a4f Mon Sep 17 00:00:00 2001 From: Alex Casalboni Date: Wed, 14 Jul 2021 14:50:22 +0200 Subject: [PATCH 3/5] Update tests (waiter) --- test/unit/test-lambda.js | 17 +++++++++++++++-- test/unit/test-utils.js | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/test/unit/test-lambda.js b/test/unit/test-lambda.js index c7a21ca..e71ab4d 100644 --- a/test/unit/test-lambda.js +++ b/test/unit/test-lambda.js @@ -28,7 +28,8 @@ var setLambdaPowerCounter, getLambdaPowerCounter, publishLambdaVersionCounter, createLambdaAliasCounter, - updateLambdaAliasCounter; + updateLambdaAliasCounter, + waitForFunctionUpdateCounter; // utility to invoke handler (success case) const invokeForSuccess = async(handler, event) => { @@ -68,7 +69,8 @@ var getLambdaAliasStub, deleteLambdaVersionStub, invokeLambdaStub, invokeLambdaProcessorStub, - deleteLambdaAliasStub; + deleteLambdaAliasStub, + waitForFunctionUpdateStub; /** unit tests below **/ @@ -80,6 +82,7 @@ describe('Lambda Functions', async() => { publishLambdaVersionCounter = 0; createLambdaAliasCounter = 0; updateLambdaAliasCounter = 0; + waitForFunctionUpdateCounter = 0; sandBox.stub(utils, 'regionFromARN') .callsFake((arn) => { @@ -120,6 +123,11 @@ describe('Lambda Functions', async() => { updateLambdaAliasCounter++; return 'OK'; }); + waitForFunctionUpdateStub = sandBox.stub(utils, 'waitForFunctionUpdate') + .callsFake(async() => { + waitForFunctionUpdateCounter++; + return 'OK'; + }); }); afterEach('Global mock utilities afterEach', () => { @@ -198,6 +206,7 @@ describe('Lambda Functions', async() => { expect(getLambdaPowerCounter).to.be(1); expect(publishLambdaVersionCounter).to.be(powerValues.length); expect(createLambdaAliasCounter).to.be(powerValues.length); + expect(waitForFunctionUpdateCounter).to.be(powerValues.length); }); it('should update an alias if it already exists', async() => { @@ -215,6 +224,7 @@ describe('Lambda Functions', async() => { await invokeForSuccess(handler, { lambdaARN: 'arnOK', num: 5 }); expect(updateLambdaAliasCounter).to.be(1); expect(createLambdaAliasCounter).to.be(powerValues.length - 1); + expect(waitForFunctionUpdateCounter).to.be(powerValues.length); }); it('should update an alias if it already exists (2)', async() => { @@ -226,6 +236,7 @@ describe('Lambda Functions', async() => { }); await invokeForSuccess(handler, { lambdaARN: 'arnOK', num: 5 }); expect(createLambdaAliasCounter).to.be(powerValues.length * 10); + expect(waitForFunctionUpdateCounter).to.be(powerValues.length); }); it('should explode if something goes wrong during power set', async() => { @@ -235,6 +246,7 @@ describe('Lambda Functions', async() => { throw new Error('Something went wrong'); }); await invokeForFailure(handler, { lambdaARN: 'arnOK', num: 5 }); + expect(waitForFunctionUpdateCounter).to.be(0); }); it('should fail is something goes wrong with the initialization API calls', async() => { @@ -246,6 +258,7 @@ describe('Lambda Functions', async() => { throw error; }); await invokeForFailure(handler, { lambdaARN: 'arnOK', num: 5 }); + expect(waitForFunctionUpdateCounter).to.be(1); }); }); diff --git a/test/unit/test-utils.js b/test/unit/test-utils.js index afa6811..961c3ae 100644 --- a/test/unit/test-utils.js +++ b/test/unit/test-utils.js @@ -22,7 +22,7 @@ const sandBox = sinon.createSandbox(); // AWS SDK mocks AWS.mock('Lambda', 'getAlias', {}); -AWS.mock('Lambda', 'getFunctionConfiguration', {MemorySize: 1024}); +AWS.mock('Lambda', 'getFunctionConfiguration', {MemorySize: 1024, State: 'Active', LastUpdateStatus: 'Successful'}); AWS.mock('Lambda', 'updateFunctionConfiguration', {}); AWS.mock('Lambda', 'publishVersion', {}); AWS.mock('Lambda', 'deleteFunction', {}); @@ -30,6 +30,10 @@ AWS.mock('Lambda', 'createAlias', {}); AWS.mock('Lambda', 'deleteAlias', {}); AWS.mock('Lambda', 'invoke', {}); +// note: waiters aren't correctly mocked by aws-sdk-mock (for now) +// https://github.com/dwyl/aws-sdk-mock/issues/173 +AWS.mock('Lambda', 'waitFor', {}); + describe('Lambda Utils', () => { // I'm dynamically generating tests for all these utilities @@ -43,6 +47,7 @@ describe('Lambda Utils', () => { utils.deleteLambdaAlias, utils.invokeLambda, utils.invokeLambdaWithProcessors, + utils.waitForFunctionUpdate, ]; // just returns the utility name for convenience @@ -127,6 +132,15 @@ describe('Lambda Utils', () => { }); }); + describe('waitForFunctionUpdate', () => { + + it('should return if LastUpdateStatus is successful', async() => { + // TODO: remove waitFor mock and test this properly + await utils.waitForFunctionUpdate('arn:aws:lambda:us-east-1:XXX:function:YYY'); + }); + + }); + describe('extractDuration', () => { const log = 'START RequestId: 55bc566d-1e2c-11e7-93e6-6705ceb4c1cc Version: $LATEST\n' + From c4e7541520dd4c07c7463ca56a1242709c261a94 Mon Sep 17 00:00:00 2001 From: Alex Casalboni Date: Wed, 14 Jul 2021 15:29:28 +0200 Subject: [PATCH 4/5] typo in comment --- lambda/initializer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda/initializer.js b/lambda/initializer.js index b951f63..bfd6a3c 100644 --- a/lambda/initializer.js +++ b/lambda/initializer.js @@ -16,7 +16,7 @@ module.exports.handler = async(event, context) => { // fetch initial $LATEST value so we can reset it later const initialPower = await utils.getLambdaPower(lambdaARN); - // reminder: configuration updates must run sequencially + // reminder: configuration updates must run sequentially // (otherwise you get a ResourceConflictException) for (let value of powerValues){ const alias = 'RAM' + value; From c5787f0e795814eda51046de3e9eacf4686ab8be Mon Sep 17 00:00:00 2001 From: Alex Casalboni Date: Mon, 16 Aug 2021 16:34:06 +0200 Subject: [PATCH 5/5] Upgrade function runtime to Node.js 14.x --- template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template.yml b/template.yml index 44590f8..061fa80 100644 --- a/template.yml +++ b/template.yml @@ -45,7 +45,7 @@ Conditions: Globals: Function: - Runtime: nodejs12.x + Runtime: nodejs14.x MemorySize: 128 Timeout: !Ref totalExecutionTimeout PermissionsBoundary: !If [UsePermissionsBoundary, !Ref permissionsBoundary, !Ref AWS::NoValue]