Skip to content

Commit

Permalink
Address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurenceWarne committed Sep 22, 2024
1 parent 209fc7d commit 6b23261
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class SnsToUrlStack extends cdk.Stack {
{
deliveryPolicy: {
healthyRetryPolicy: {
minDelayTarget: 20,
maxDelayTarget: 21,
minDelayTarget: cdk.Duration.seconds(20),
maxDelayTarget: cdk.Duration.seconds(21),
numRetries: 10,
},
throttlePolicy: {
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk-lib/aws-sns-subscriptions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ myTopic.addSubscription(
{
deliveryPolicy: {
healthyRetryPolicy: {
minDelayTarget: 5,
maxDelayTarget: 10,
minDelayTarget: Duration.seconds(5),
maxDelayTarget: Duration.seconds(10),
numRetries: 6,
backoffFunction: sns.BackoffFunction.EXPONENTIAL,
},
Expand Down
61 changes: 44 additions & 17 deletions packages/aws-cdk-lib/aws-sns/lib/delivery-policy.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { Duration } from '../../core';

/**
* Algorithms which can be used by SNS to calculate the delays associated with all of the retry attempts between the first and last retries in the backoff phase.
*/
export enum BackoffFunction {
/** Arithmetic.
/**
* Arithmetic, see {@link https://docs.aws.amazon.com/images/sns/latest/dg/images/backoff-graph.png|this image} for how this function compares to others
*/
ARITHMETIC = 'ARITHMETIC',
/** Geometric.
/**
* Exponential, see {@link https://docs.aws.amazon.com/images/sns/latest/dg/images/backoff-graph.png|this image} for how this function compares to others
*/
EXPONENTIAL = 'EXPONENTIAL',
/** Linear.
/**
* Geometric, see {@link https://docs.aws.amazon.com/images/sns/latest/dg/images/backoff-graph.png|this image} for how this function compares to others
*/
GEOMETRIC = 'GEOMETRIC',
/** Linear.
/**
* Linear, see {@link https://docs.aws.amazon.com/images/sns/latest/dg/images/backoff-graph.png|this image} for how this function compares to others
*/
LINEAR = 'LINEAR',
}
Expand All @@ -20,7 +26,9 @@ export enum BackoffFunction {
* Options for customising AWS SNS HTTP/S delivery throttling.
*/
export interface ThrottlePolicy {
/** The maximum number of deliveries per second, per subscription.
/**
* The maximum number of deliveries per second, per subscription.
*
* @default - no throttling
*/
readonly maxReceivesPerSecond?: number;
Expand All @@ -30,7 +38,9 @@ export interface ThrottlePolicy {
* Options for customising aspects of the content sent in AWS SNS HTTP/S requests.
*/
export interface RequestPolicy {
/** The content type of the notification being sent to HTTP/S endpoints.
/**
* The content type of the notification being sent to HTTP/S endpoints.
*
* @default - text/plain; charset=UTF-8
*/
readonly headerContentType?: string;
Expand All @@ -40,36 +50,50 @@ export interface RequestPolicy {
* Options for customising the retry policy of the delivery of SNS messages to HTTP/S endpoints.
*/
export interface HealthyRetryPolicy {
/** The minimum delay for a retry (in seconds). Must be at least 1 and not exceed `maxDelayTarget`.
* @default 20
/**
* The minimum delay for a retry. Must be at least one second, not exceed `maxDelayTarget`, and correspond to a whole number of seconds.
*
* @default - 20 seconds
*/
readonly minDelayTarget: number;
/** The maximum delay for a retry (in seconds). Must be at least `minDelayTarget` and less than 3,600.
* @default 20
readonly minDelayTarget: Duration;
/**
* The maximum delay for a retry. Must be at least `minDelayTarget` less than 3,600 seconds, and correspond to a whole number of seconds,
*
* @default - 20 seconds
*/
readonly maxDelayTarget: number;
readonly maxDelayTarget: Duration;

/** The total number of retries, including immediate, pre-backoff, backoff, and post-backoff retries. Must be greater than or equal to zero and not exceed 100.
/**
* The total number of retries, including immediate, pre-backoff, backoff, and post-backoff retries. Must be greater than or equal to zero and not exceed 100.
*
* @default 3
*/
readonly numRetries: number;

/** The number of retries to be done immediately, with no delay between them. Must be zero or greater.
/**
* The number of retries to be done immediately, with no delay between them. Must be zero or greater.
*
* @default 0
*/
readonly numNoDelayRetries?: number;

/** The number of retries in the pre-backoff phase, with the specified minimum delay between them. Must be zero or greater
/**
* The number of retries in the pre-backoff phase, with the specified minimum delay between them. Must be zero or greater
*
* @default 0
*/
readonly numMinDelayRetries?: number;

/** The number of retries in the post-backoff phase, with the maximum delay between them. Must be zero or greater
/**
* The number of retries in the post-backoff phase, with the maximum delay between them. Must be zero or greater
*
* @default 0
*/
readonly numMaxDelayRetries?: number;

/** The model for backoff between retries.
/**
* The model for backoff between retries.
*
* @default - linear
*/
readonly backoffFunction?: BackoffFunction;
Expand All @@ -82,18 +106,21 @@ export interface DeliveryPolicy {

/**
* The retry policy of the delivery of SNS messages to HTTP/S endpoints.
*
* @default - Amazon SNS attempts up to three retries with a delay between failed attempts set at 20 seconds
*/
readonly healthyRetryPolicy?: HealthyRetryPolicy;

/**
* The throttling policy of the delivery of SNS messages to HTTP/S endpoints.
*
* @default - No throttling
*/
readonly throttlePolicy?: ThrottlePolicy;

/**
* The request of the content sent in AWS SNS HTTP/S requests.
*
* @default - The content type is set to 'text/plain; charset=UTF-8'
*/
readonly requestPolicy?: RequestPolicy;
Expand Down
89 changes: 48 additions & 41 deletions packages/aws-cdk-lib/aws-sns/lib/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,43 +144,6 @@ export class Subscription extends Resource {
throw new Error('Subscription role arn is required field for subscriptions with a firehose protocol.');
}

if (props.deliveryPolicy) {
if (props.protocol !== SubscriptionProtocol.HTTP && props.protocol !== SubscriptionProtocol.HTTPS) {
throw new Error('Delivery policy is only supported for HTTP and HTTPS subscriptions');
}
const { healthyRetryPolicy, throttlePolicy } = props.deliveryPolicy;
if (healthyRetryPolicy) {
const delayTargetLimit = 3600;
if (healthyRetryPolicy.minDelayTarget < 1 || healthyRetryPolicy.minDelayTarget > delayTargetLimit) {
throw new Error(`minDelayTarget must be between 1 and ${delayTargetLimit} inclusive`);
}
if (healthyRetryPolicy.maxDelayTarget < 1 || healthyRetryPolicy.maxDelayTarget > delayTargetLimit) {
throw new Error(`maxDelayTarget must be between 1 and ${delayTargetLimit} inclusive`);
}
if (healthyRetryPolicy.minDelayTarget > healthyRetryPolicy.maxDelayTarget) {
throw new Error('minDelayTarget must not exceed maxDelayTarget');
}
const numRetriesLimit = 100;
if (healthyRetryPolicy.numRetries < 0 || healthyRetryPolicy.numRetries > numRetriesLimit) {
throw new Error(`numRetries must be between 0 and ${numRetriesLimit} inclusive`);
}
if (healthyRetryPolicy.numNoDelayRetries && healthyRetryPolicy.numNoDelayRetries < 0) {
throw new Error('numNoDelayRetries must be zero or greater');
}
if (healthyRetryPolicy.numMinDelayRetries && healthyRetryPolicy.numMinDelayRetries < 0) {
throw new Error('numMinDelayRetries must be zero or greater');
}
if (healthyRetryPolicy.numMaxDelayRetries && healthyRetryPolicy.numMaxDelayRetries < 0) {
throw new Error('numMaxDelayRetries must be zero or greater');
}
}
if (throttlePolicy) {
if (throttlePolicy.maxReceivesPerSecond !== undefined && throttlePolicy.maxReceivesPerSecond < 1) {
throw new Error('maxReceivesPerSecond must be greater than zero');
}
}
}

// Format filter policy
const filterPolicy = this.filterPolicyWithMessageBody
? buildFilterPolicyWithMessageBody(this.filterPolicyWithMessageBody)
Expand All @@ -197,16 +160,60 @@ export class Subscription extends Resource {
region: props.region,
redrivePolicy: this.buildDeadLetterConfig(this.deadLetterQueue),
subscriptionRoleArn: props.subscriptionRoleArn,
deliveryPolicy: props.deliveryPolicy ? this.renderDeliveryPolicy(props.deliveryPolicy): undefined,
deliveryPolicy: props.deliveryPolicy ? this.renderDeliveryPolicy(props.deliveryPolicy, props.protocol): undefined,
});

}

private renderDeliveryPolicy(deliveryPolicy: DeliveryPolicy): any {
private renderDeliveryPolicy(deliveryPolicy: DeliveryPolicy, protocol: SubscriptionProtocol): any {
if (protocol !== SubscriptionProtocol.HTTP && protocol !== SubscriptionProtocol.HTTPS) {
throw new Error(`Delivery policy is only supported for HTTP and HTTPS subscriptions, got: ${protocol}`);
}
const { healthyRetryPolicy, throttlePolicy } = deliveryPolicy;
if (healthyRetryPolicy) {
const delayTargetLimitSecs = 3600;
const minDelayTarget = healthyRetryPolicy.minDelayTarget;
const maxDelayTarget = healthyRetryPolicy.maxDelayTarget;
if (minDelayTarget.toMilliseconds() % 1000 !== 0) {
throw new Error(`minDelayTarget must be a whole number of seconds, got: ${minDelayTarget}`);
}
if (maxDelayTarget.toMilliseconds() % 1000 !== 0) {
throw new Error(`maxDelayTarget must be a whole number of seconds, got: ${maxDelayTarget}`);
}
const minDelayTargetSecs = minDelayTarget.toSeconds();
if (minDelayTargetSecs < 1 || minDelayTargetSecs > delayTargetLimitSecs) {
throw new Error(`minDelayTarget must be between 1 and ${delayTargetLimitSecs} seconds inclusive`);
}
const maxDelayTargetSecs = maxDelayTarget.toSeconds();
if (maxDelayTargetSecs < 1 || maxDelayTargetSecs > delayTargetLimitSecs) {
throw new Error(`maxDelayTarget must be between 1 and ${delayTargetLimitSecs} seconds inclusive`);
}
if (minDelayTargetSecs > maxDelayTargetSecs) {
throw new Error('minDelayTarget must not exceed maxDelayTarget');
}
const numRetriesLimit = 100;
if (healthyRetryPolicy.numRetries < 0 || healthyRetryPolicy.numRetries > numRetriesLimit) {
throw new Error(`numRetries must be between 0 and ${numRetriesLimit} inclusive`);
}
if (healthyRetryPolicy.numNoDelayRetries && healthyRetryPolicy.numNoDelayRetries < 0) {
throw new Error('numNoDelayRetries must be zero or greater');
}
if (healthyRetryPolicy.numMinDelayRetries && healthyRetryPolicy.numMinDelayRetries < 0) {
throw new Error('numMinDelayRetries must be zero or greater');
}
if (healthyRetryPolicy.numMaxDelayRetries && healthyRetryPolicy.numMaxDelayRetries < 0) {
throw new Error('numMaxDelayRetries must be zero or greater');
}
}
if (throttlePolicy) {
if (throttlePolicy.maxReceivesPerSecond !== undefined && throttlePolicy.maxReceivesPerSecond < 1) {
throw new Error('maxReceivesPerSecond must be greater than zero');
}
}
return {
healthyRetryPolicy: deliveryPolicy.healthyRetryPolicy ? {
minDelayTarget: deliveryPolicy.healthyRetryPolicy.minDelayTarget,
maxDelayTarget: deliveryPolicy.healthyRetryPolicy.maxDelayTarget,
minDelayTarget: deliveryPolicy.healthyRetryPolicy.minDelayTarget.toSeconds(),
maxDelayTarget: deliveryPolicy.healthyRetryPolicy.maxDelayTarget.toSeconds(),
numRetries: deliveryPolicy.healthyRetryPolicy.numRetries,
numNoDelayRetries: deliveryPolicy.healthyRetryPolicy.numNoDelayRetries,
numMinDelayRetries: deliveryPolicy.healthyRetryPolicy.numMinDelayRetries,
Expand Down
Loading

0 comments on commit 6b23261

Please sign in to comment.