Skip to content

Commit

Permalink
Merge pull request #135 from alexcasalboni/134-support-state-waiter
Browse files Browse the repository at this point in the history
Implement function update waiter (states expansion)
  • Loading branch information
alexcasalboni committed Aug 16, 2021
2 parents 0c40a12 + c5787f0 commit 31498a5
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 5 deletions.
2 changes: 1 addition & 1 deletion lambda/initializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions lambda/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
*/
Expand Down
2 changes: 1 addition & 1 deletion template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
17 changes: 15 additions & 2 deletions test/unit/test-lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ var setLambdaPowerCounter,
getLambdaPowerCounter,
publishLambdaVersionCounter,
createLambdaAliasCounter,
updateLambdaAliasCounter;
updateLambdaAliasCounter,
waitForFunctionUpdateCounter;

// utility to invoke handler (success case)
const invokeForSuccess = async(handler, event) => {
Expand Down Expand Up @@ -68,7 +69,8 @@ var getLambdaAliasStub,
deleteLambdaVersionStub,
invokeLambdaStub,
invokeLambdaProcessorStub,
deleteLambdaAliasStub;
deleteLambdaAliasStub,
waitForFunctionUpdateStub;

/** unit tests below **/

Expand All @@ -80,6 +82,7 @@ describe('Lambda Functions', async() => {
publishLambdaVersionCounter = 0;
createLambdaAliasCounter = 0;
updateLambdaAliasCounter = 0;
waitForFunctionUpdateCounter = 0;

sandBox.stub(utils, 'regionFromARN')
.callsFake((arn) => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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() => {
Expand All @@ -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() => {
Expand All @@ -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() => {
Expand All @@ -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() => {
Expand All @@ -246,6 +258,7 @@ describe('Lambda Functions', async() => {
throw error;
});
await invokeForFailure(handler, { lambdaARN: 'arnOK', num: 5 });
expect(waitForFunctionUpdateCounter).to.be(1);
});

});
Expand Down
16 changes: 15 additions & 1 deletion test/unit/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ 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', {});
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
Expand All @@ -43,6 +47,7 @@ describe('Lambda Utils', () => {
utils.deleteLambdaAlias,
utils.invokeLambda,
utils.invokeLambdaWithProcessors,
utils.waitForFunctionUpdate,
];

// just returns the utility name for convenience
Expand Down Expand Up @@ -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' +
Expand Down

0 comments on commit 31498a5

Please sign in to comment.