forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(custom-resources): inactive lambda functions fail on invoke (aws#…
…22612) closes aws#20123 All lambda functions can become inactive eventually. This will result in invocations failing. This PR adds logic to wait for functions to become active on a failed invocation. ---- ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
1 parent
319be7b
commit 8fcb0e2
Showing
44 changed files
with
634 additions
and
219 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
packages/@aws-cdk/custom-resources/test/provider-framework/outbound.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import * as aws from 'aws-sdk'; | ||
import { invokeFunction } from '../../lib/provider-framework/runtime/outbound'; | ||
|
||
jest.mock('aws-sdk', () => { | ||
return { | ||
Lambda: class { | ||
public invoke() { | ||
return { promise: () => mockInvoke() }; | ||
} | ||
|
||
public waitFor() { | ||
return { promise: () => mockWaitFor() }; | ||
} | ||
}, | ||
}; | ||
}); | ||
|
||
let mockInvoke: () => Promise<aws.Lambda.InvocationResponse>; | ||
|
||
const req: aws.Lambda.InvocationRequest = { | ||
FunctionName: 'Whatever', | ||
Payload: { | ||
IsThisATest: 'Yes, this is a test', | ||
AreYouSure: 'Yes, I am sure', | ||
}, | ||
}; | ||
|
||
let invokeCount: number = 0; | ||
let expectedFunctionStates: string[] = []; | ||
let receivedFunctionStates: string[] = []; | ||
|
||
const mockWaitFor = async (): Promise<aws.Lambda.GetFunctionResponse> => { | ||
let state = expectedFunctionStates.pop(); | ||
while (state !== 'Active') { | ||
receivedFunctionStates.push(state!); | ||
// If it goes back to inactive it's failed | ||
if (state === 'Inactive') throw new Error('Not today'); | ||
// If failed... it's failed | ||
if (state === 'Failed') throw new Error('Broken'); | ||
// If pending, continue the loop, no other valid options | ||
if (state !== 'Pending') throw new Error('State is confused'); | ||
state = expectedFunctionStates.pop(); | ||
} | ||
receivedFunctionStates.push(state); | ||
return { | ||
Configuration: { | ||
State: 'Active', | ||
}, | ||
}; | ||
}; | ||
|
||
describe('invokeFunction tests', () => { | ||
afterEach(() => { | ||
invokeCount = 0; | ||
expectedFunctionStates = []; | ||
receivedFunctionStates = []; | ||
}); | ||
|
||
// Success cases | ||
test('Inactive function that reactivates does not throw error', async () => { | ||
mockInvoke = async () => { | ||
if (invokeCount == 0) { | ||
invokeCount++; | ||
throw new Error('Better luck next time'); | ||
} | ||
invokeCount++; | ||
return { Payload: req.Payload }; | ||
}; | ||
|
||
expectedFunctionStates.push('Active'); | ||
expectedFunctionStates.push('Pending'); | ||
|
||
expect(await invokeFunction(req)).toEqual({ Payload: req.Payload }); | ||
expect(invokeCount).toEqual(2); | ||
expect(receivedFunctionStates).toEqual(['Pending', 'Active']); | ||
}); | ||
|
||
test('Active function does not run waitFor or retry invoke', async () => { | ||
mockInvoke = async () => { | ||
if (invokeCount == 1) { | ||
invokeCount++; | ||
throw new Error('This should not happen in this test'); | ||
} | ||
invokeCount++; | ||
return { Payload: req.Payload }; | ||
}; | ||
|
||
expectedFunctionStates.push('Active'); | ||
|
||
expect(await invokeFunction(req)).toEqual({ Payload: req.Payload }); | ||
expect(invokeCount).toEqual(1); | ||
expect(receivedFunctionStates).toEqual([]); | ||
}); | ||
|
||
// Failure cases | ||
test('Inactive function that goes back to inactive throws error', async () => { | ||
mockInvoke = async () => { | ||
if (invokeCount == 0) { | ||
invokeCount++; | ||
throw new Error('Better luck next time'); | ||
} | ||
invokeCount++; | ||
return { Payload: req.Payload }; | ||
}; | ||
|
||
expectedFunctionStates.push('Inactive'); | ||
expectedFunctionStates.push('Pending'); | ||
expectedFunctionStates.push('Pending'); | ||
|
||
await expect(invokeFunction(req)).rejects.toThrowError(new Error('Not today')); | ||
expect(invokeCount).toEqual(1); | ||
expect(receivedFunctionStates).toEqual(['Pending', 'Pending', 'Inactive']); | ||
}); | ||
|
||
test('Inactive function that goes to failed throws error', async () => { | ||
mockInvoke = async () => { | ||
if (invokeCount == 0) { | ||
invokeCount++; | ||
throw new Error('Better luck next time'); | ||
} | ||
invokeCount++; | ||
return { Payload: req.Payload }; | ||
}; | ||
|
||
expectedFunctionStates.push('Failed'); | ||
expectedFunctionStates.push('Pending'); | ||
expectedFunctionStates.push('Pending'); | ||
|
||
await expect(invokeFunction(req)).rejects.toThrowError(new Error('Broken')); | ||
expect(invokeCount).toEqual(1); | ||
expect(receivedFunctionStates).toEqual(['Pending', 'Pending', 'Failed']); | ||
}); | ||
|
||
test('Inactive function that returns other value throws error', async () => { | ||
mockInvoke = async () => { | ||
if (invokeCount == 0) { | ||
invokeCount++; | ||
throw new Error('Better luck next time'); | ||
} | ||
invokeCount++; | ||
return { Payload: req.Payload }; | ||
}; | ||
|
||
expectedFunctionStates.push('NewFunctionWhoDis'); | ||
expectedFunctionStates.push('Pending'); | ||
expectedFunctionStates.push('Pending'); | ||
|
||
await expect(invokeFunction(req)).rejects.toThrowError(new Error('State is confused')); | ||
expect(invokeCount).toEqual(1); | ||
expect(receivedFunctionStates).toEqual(['Pending', 'Pending', 'NewFunctionWhoDis']); | ||
}); | ||
|
||
test('Wait for stops on terminal responses', async () => { | ||
mockInvoke = async () => { | ||
if (invokeCount == 0) { | ||
invokeCount++; | ||
throw new Error('Better luck next time'); | ||
} | ||
invokeCount++; | ||
return { Payload: req.Payload }; | ||
}; | ||
|
||
expectedFunctionStates.push('SomethingElse'); | ||
expectedFunctionStates.push('Pending'); | ||
expectedFunctionStates.push('Inactive'); | ||
expectedFunctionStates.push('Pending'); | ||
expectedFunctionStates.push('Pending'); | ||
|
||
await expect(invokeFunction(req)).rejects.toThrowError(new Error('Not today')); | ||
expect(invokeCount).toEqual(1); | ||
expect(receivedFunctionStates).toEqual(['Pending', 'Pending', 'Inactive']); | ||
}); | ||
}); | ||
|
Binary file modified
BIN
-421 Bytes
(100%)
...er.js.snapshot/asset.5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510.zip
Binary file not shown.
45 changes: 0 additions & 45 deletions
45
...apshot/asset.7215c88dd3e638d28329d4538b36cdbfb54233a4d972181795814f8b904d1037/outbound.js
This file was deleted.
Oops, something went wrong.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Oops, something went wrong.