Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): fix rate limit errors when deploying cloudwatch log groups #822

Merged
merged 4 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/aws-rfdk/lib/core/lib/exporting-log-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import {
Code,
Runtime,
SingletonFunction,
CfnFunction,
} from 'aws-cdk-lib/aws-lambda';
import {
ILogGroup,
LogGroup,
LogRetention,
RetentionDays,
} from 'aws-cdk-lib/aws-logs';
import { Stack } from 'aws-cdk-lib/core';
import { Construct } from 'constructs';

/**
Expand Down Expand Up @@ -111,10 +113,25 @@ export class ExportingLogGroup extends Construct {
threshold: 1,
});

// Define log retention retry options to reduce the risk of the rate exceed error
// as the default create log group TPS is only 5. Make sure to set the timeout of log retention function
// to be greater than total retry time. That's because if the function that is used for a custom resource
// doesn't exit properly, it'd end up in retries and may take cloud formation an hour to realize that
// the custom resource failed.
const logRetention = new LogRetention(this, 'LogRetention', {
logGroupName: props.logGroupName,
retention: retentionInDays,
logRetentionRetryOptions: {
base: Duration.millis(200),
maxRetries: 7,
ddneilson marked this conversation as resolved.
Show resolved Hide resolved
},
});
const logRetentionFunctionLogicalId = 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a';
jericht marked this conversation as resolved.
Show resolved Hide resolved
jusiskin marked this conversation as resolved.
Show resolved Hide resolved
const logRetentionFunction = Stack.of(this).node.tryFindChild(logRetentionFunctionLogicalId);
if (logRetentionFunction) {
const cfnFunction = logRetentionFunction.node.defaultChild as CfnFunction;
cfnFunction.addPropertyOverride('Timeout', 30);
}
jusiskin marked this conversation as resolved.
Show resolved Hide resolved

this.logGroup = LogGroup.fromLogGroupArn(scope, `${props.logGroupName}LogGroup`, logRetention.logGroupArn);
this.logGroup.grant(exportLogsFunction, 'logs:CreateExportTask');
Expand Down
22 changes: 21 additions & 1 deletion packages/aws-rfdk/lib/core/lib/log-group-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Duration, Stack } from 'aws-cdk-lib';
import { CfnFunction } from 'aws-cdk-lib/aws-lambda';
import {
ILogGroup,
LogGroup,
Expand Down Expand Up @@ -52,7 +54,12 @@ export class LogGroupFactory {
const fullLogGroupName = props?.logGroupPrefix ? `${props.logGroupPrefix}${logGroupName}` : logGroupName;
const retention = props?.retention ? props.retention : LogGroupFactory.DEFAULT_LOG_RETENTION;

return props?.bucketName
// Define log retention retry options to reduce the risk of the rate exceed error
// as the default create log group TPS is only 5. Make sure to set the timeout of log retention function
// to be greater than total retry time. That's because if the function that is used for a custom resource
// doesn't exit properly, it'd end up in retries and may take cloud formation an hour to realize that
// the custom resource failed.
const logGroup = props?.bucketName
? new ExportingLogGroup(scope, logWrapperId, {
bucketName: props.bucketName,
logGroupName: fullLogGroupName,
Expand All @@ -64,7 +71,20 @@ export class LogGroupFactory {
new LogRetention(scope, logWrapperId, {
logGroupName: fullLogGroupName,
retention,
logRetentionRetryOptions: {
base: Duration.millis(200),
maxRetries: 7,
},
}).logGroupArn);

const logRetentionFunctionLogicalId = 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a';
jericht marked this conversation as resolved.
Show resolved Hide resolved
const logRetentionFunction = Stack.of(scope).node.tryFindChild(logRetentionFunctionLogicalId);
if (logRetentionFunction) {
const cfnFunction = logRetentionFunction.node.defaultChild as CfnFunction;
cfnFunction.addPropertyOverride('Timeout', 30);
}

return logGroup;
}

/**
Expand Down
8 changes: 8 additions & 0 deletions packages/aws-rfdk/lib/core/test/exporting-log-group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ test('default exporting log group is created correctly', () => {
],
},
LogGroupName: 'logGroup',
SdkRetry: {
maxRetries: 7,
base: 200,
},
RetentionInDays: 3,
});
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
Expand Down Expand Up @@ -147,6 +151,10 @@ test('custom set retention is created correctly', () => {
],
},
LogGroupName: 'logGroup',
SdkRetry: {
maxRetries: 7,
base: 200,
},
RetentionInDays: 7,
});
Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 2);
Expand Down
98 changes: 98 additions & 0 deletions packages/aws-rfdk/lib/core/test/log-group-factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,24 @@ describe('log group', () => {
// THEN
Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', {
LogGroupName: 'testLogGroup',
SdkRetry: {
maxRetries: 7,
base: 200,
},
RetentionInDays: 3,
});

expect(Object.keys(Template.fromStack(stack).findResources('AWS::Lambda::Function', {
Properties: {
Role: {
'Fn::GetAtt': [
'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB',
'Arn',
],
},
Timeout: 30,
},
})).length).toEqual(1);
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', Match.not({
Role: {
'Fn::GetAtt': [
Expand All @@ -44,8 +60,25 @@ describe('log group', () => {
// THEN
Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', {
LogGroupName: 'prefix-testLogGroup',
SdkRetry: {
maxRetries: 7,
base: 200,
},
RetentionInDays: 3,
});

expect(Object.keys(Template.fromStack(stack).findResources('AWS::Lambda::Function', {
Properties: {
Role: {
'Fn::GetAtt': [
'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB',
'Arn',
],
},
Timeout: 30,
},
})).length).toEqual(1);

Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', Match.not({
Role: {
'Fn::GetAtt': [
Expand All @@ -67,8 +100,25 @@ describe('log group', () => {
// THEN
Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', {
LogGroupName: 'testLogGroup',
SdkRetry: {
maxRetries: 7,
base: 200,
},
RetentionInDays: 7,
});

expect(Object.keys(Template.fromStack(stack).findResources('AWS::Lambda::Function', {
Properties: {
Role: {
'Fn::GetAtt': [
'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB',
'Arn',
],
},
Timeout: 30,
},
})).length).toEqual(1);

Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', Match.not({
Role: {
'Fn::GetAtt': [
Expand All @@ -92,9 +142,25 @@ describe('exporting log group', () => {
// THEN
Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', {
LogGroupName: 'testLogGroup',
SdkRetry: {
maxRetries: 7,
base: 200,
},
RetentionInDays: 3,
});

expect(Object.keys(Template.fromStack(stack).findResources('AWS::Lambda::Function', {
Properties: {
Role: {
'Fn::GetAtt': [
'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB',
'Arn',
],
},
Timeout: 30,
},
})).length).toEqual(1);

Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
Role: {
'Fn::GetAtt': [
Expand Down Expand Up @@ -124,9 +190,25 @@ describe('exporting log group', () => {
// THEN
Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', {
LogGroupName: 'prefix-testLogGroup',
SdkRetry: {
maxRetries: 7,
base: 200,
},
RetentionInDays: 3,
});

expect(Object.keys(Template.fromStack(stack).findResources('AWS::Lambda::Function', {
Properties: {
Role: {
'Fn::GetAtt': [
'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB',
'Arn',
],
},
Timeout: 30,
},
})).length).toEqual(1);

Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
Role: {
'Fn::GetAtt': [
Expand Down Expand Up @@ -157,9 +239,25 @@ describe('exporting log group', () => {
// THEN
Template.fromStack(stack).hasResourceProperties('Custom::LogRetention', {
LogGroupName: 'testLogGroup',
SdkRetry: {
maxRetries: 7,
base: 200,
},
RetentionInDays: 7,
});

expect(Object.keys(Template.fromStack(stack).findResources('AWS::Lambda::Function', {
Properties: {
Role: {
'Fn::GetAtt': [
'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB',
'Arn',
],
},
Timeout: 30,
},
})).length).toEqual(1);

Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
Role: {
'Fn::GetAtt': [
Expand Down