-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat(appmesh): add route retry policies #13353
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
Changes from 2 commits
6c35500
d567f3d
d1944aa
c83c0f8
a092ea1
c0e99f8
340d1c0
e3cee3a
9db78e2
297ecaf
30d433a
b1dd174
c1d82c1
c4993ac
e683201
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -320,6 +320,40 @@ router.addRoute('route-http', { | |
| }); | ||
| ``` | ||
|
|
||
| Add an http2 route with retries: | ||
|
|
||
| ```ts | ||
| router.addRoute('route-http2-retry', { | ||
| routeSpec: appmesh.RouteSpec.http2({ | ||
| weightedTargets: [{ virtualNode: node }], | ||
| retryPolicy: { | ||
| httpRetryEvents: [appmesh.HttpRetryEvent.CLIENT_ERROR], | ||
| tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], | ||
| maxRetries: 5, | ||
| perRetryTimeout: cdk.Duration.seconds(1), | ||
| }, | ||
| }), | ||
| }); | ||
| ``` | ||
|
|
||
| Add a gRPC route with retries: | ||
|
|
||
| ```ts | ||
| router.addRoute('route-grpc-retry', { | ||
| routeSpec: appmesh.RouteSpec.grpc({ | ||
| weightedTargets: [{ virtualNode: node }], | ||
| match: { serviceName: 'servicename' }, | ||
| retryPolicy: { | ||
| grpcRetryEvents: [appmesh.GrpcRetryEvent.DEADLINE_EXCEEDED], | ||
| httpRetryEvents: [appmesh.HttpRetryEvent.CLIENT_ERROR], | ||
| tcpRetryEvents: [appmesh.TcpRetryEvent.CONNECTION_ERROR], | ||
|
||
| maxRetries: 5, | ||
| perRetryTimeout: cdk.Duration.seconds(1), | ||
| }, | ||
| }), | ||
| }); | ||
| ``` | ||
|
|
||
| The _RouteSpec_ class provides an easy interface for defining new protocol specific route specs. | ||
| The `tcp()`, `http()` and `http2()` methods provide the spec necessary to define a protocol specific spec. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import * as cdk from '@aws-cdk/core'; | ||
| import { CfnRoute } from './appmesh.generated'; | ||
| import { Protocol, HttpTimeout, GrpcTimeout, TcpTimeout } from './shared-interfaces'; | ||
| import { IVirtualNode } from './virtual-node'; | ||
|
|
@@ -68,6 +69,76 @@ export interface HttpRouteSpecOptions { | |
| * @default - None | ||
| */ | ||
| readonly timeout?: HttpTimeout; | ||
|
|
||
| /** | ||
| * The retry policy | ||
| * @default - no retry policy | ||
| */ | ||
| readonly retryPolicy?: HttpRetryPolicy; | ||
| } | ||
|
|
||
| /** | ||
| * HTTP retry policy | ||
| */ | ||
| export interface HttpRetryPolicy { | ||
| /** | ||
| * Specify HTTP events on which to retry | ||
| * @default - no retries for http events | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fact that either this, or |
||
| */ | ||
| readonly httpRetryEvents?: HttpRetryEvent[]; | ||
|
|
||
| /** | ||
| * The maximum number of retry attempts | ||
| */ | ||
| readonly maxRetries: number; | ||
|
||
|
|
||
| /** | ||
| * The timeout for each retry attempt | ||
| */ | ||
| readonly perRetryTimeout: cdk.Duration; | ||
|
||
|
|
||
| /** | ||
| * TCP events on which to retry. The event occurs before any processing of a | ||
| * request has started and is encountered when the upstream is temporarily or | ||
| * permanently unavailable. | ||
| * @default - no retries for tcp events | ||
| */ | ||
| readonly tcpRetryEvents?: TcpRetryEvent[]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fact that either this, or |
||
| } | ||
|
|
||
| /** | ||
| * HTTP events on which to retry. | ||
| */ | ||
| export enum HttpRetryEvent { | ||
| /** | ||
| * HTTP status codes 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, and 511 | ||
| */ | ||
| SERVER_ERROR = 'server-error', | ||
|
|
||
| /** | ||
| * HTTP status codes 502, 503, and 504 | ||
| */ | ||
| GATEWAY_ERROR = 'gateway-error', | ||
|
|
||
| /** | ||
| * HTTP status code 409 | ||
| */ | ||
| CLIENT_ERROR = 'client-error', | ||
|
|
||
| /** | ||
| * Retry on refused stream | ||
| */ | ||
| STREAM_ERROR = 'stream-error', | ||
| } | ||
|
|
||
| /** | ||
| * TCP events on which you may retry | ||
| */ | ||
| export enum TcpRetryEvent { | ||
| /** | ||
| * A connection error | ||
| */ | ||
| CONNECTION_ERROR = 'connection-error', | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -107,6 +178,51 @@ export interface GrpcRouteSpecOptions { | |
| * List of targets that traffic is routed to when a request matches the route | ||
| */ | ||
| readonly weightedTargets: WeightedTarget[]; | ||
|
|
||
| /** | ||
| * The retry policy | ||
| * @default - no retry policy | ||
| */ | ||
| readonly retryPolicy?: GrpcRetryPolicy; | ||
| } | ||
|
|
||
| /** gRPC retry policy */ | ||
| export interface GrpcRetryPolicy extends HttpRetryPolicy { | ||
| /** | ||
| * gRPC events on which to retry | ||
| * @default - no retries for gRPC events | ||
| */ | ||
| readonly grpcRetryEvents?: GrpcRetryEvent[]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fact that one of this, |
||
| } | ||
|
|
||
| /** | ||
| * gRPC events | ||
| */ | ||
| export enum GrpcRetryEvent { | ||
| /** | ||
| * Request was cancelled | ||
| */ | ||
| CANCELLED = 'cancelled', | ||
|
|
||
| /** | ||
| * The deadline was exceeded | ||
| */ | ||
| DEADLINE_EXCEEDED = 'deadline-exceeded', | ||
|
|
||
| /** | ||
| * Internal error | ||
| */ | ||
| INTERNAL = 'internal', | ||
|
||
|
|
||
| /** | ||
| * Resource was exhausted | ||
|
||
| */ | ||
| RESOURCE_EXHAUSTED = 'resource-exhausted', | ||
|
|
||
| /** | ||
| * Unavailable | ||
|
||
| */ | ||
| UNAVAILABILE = 'unavailable', | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -203,19 +319,43 @@ class HttpRouteSpec extends RouteSpec { | |
| */ | ||
| public readonly weightedTargets: WeightedTarget[]; | ||
|
|
||
| /** | ||
| * The retry policy | ||
| */ | ||
| public readonly retryPolicy?: HttpRetryPolicy; | ||
|
|
||
| constructor(props: HttpRouteSpecOptions, protocol: Protocol) { | ||
| super(); | ||
| this.protocol = protocol; | ||
| this.match = props.match; | ||
| this.weightedTargets = props.weightedTargets; | ||
| this.timeout = props.timeout; | ||
|
|
||
| if (props.retryPolicy) { | ||
| if (props.retryPolicy.httpRetryEvents && props.retryPolicy.httpRetryEvents.length === 0) { | ||
| throw new Error('Specify at least one value in `httpRetryEvents` or leave it undefined'); | ||
| } | ||
|
|
||
| if (props.retryPolicy.tcpRetryEvents && props.retryPolicy.tcpRetryEvents.length === 0) { | ||
| throw new Error('Specify at least one value in `tcpRetryEvents` or leave it undefined'); | ||
| } | ||
|
||
|
|
||
| const numHttpRetryEvents = (props.retryPolicy.httpRetryEvents ?? []).length; | ||
| const numTcpRetryEvents = (props.retryPolicy.tcpRetryEvents ?? []).length; | ||
| if (numHttpRetryEvents + numTcpRetryEvents === 0) { | ||
| throw new Error('You must specify one value for at least one of `httpRetryEvents` or `tcpRetryEvents`'); | ||
| } | ||
|
|
||
| this.retryPolicy = props.retryPolicy; | ||
| } | ||
| } | ||
|
|
||
| public bind(_scope: Construct): RouteSpecConfig { | ||
| const prefixPath = this.match ? this.match.prefixPath : '/'; | ||
| if (prefixPath[0] != '/') { | ||
| throw new Error(`Prefix Path must start with \'/\', got: ${prefixPath}`); | ||
| } | ||
|
|
||
| const httpConfig: CfnRoute.HttpRouteProperty = { | ||
| action: { | ||
| weightedTargets: renderWeightedTargets(this.weightedTargets), | ||
|
|
@@ -224,6 +364,7 @@ class HttpRouteSpec extends RouteSpec { | |
| prefix: prefixPath, | ||
| }, | ||
| timeout: renderTimeout(this.timeout), | ||
| retryPolicy: this.retryPolicy ? renderHttpRetryPolicy(this.retryPolicy) : undefined, | ||
| }; | ||
| return { | ||
| httpRouteSpec: this.protocol === Protocol.HTTP ? httpConfig : undefined, | ||
|
|
@@ -266,11 +407,40 @@ class GrpcRouteSpec extends RouteSpec { | |
| public readonly match: GrpcRouteMatch; | ||
| public readonly timeout?: GrpcTimeout; | ||
|
|
||
| /** | ||
| * The retry policy. | ||
| */ | ||
| public readonly retryPolicy?: GrpcRetryPolicy; | ||
|
|
||
| constructor(props: GrpcRouteSpecOptions) { | ||
| super(); | ||
| this.weightedTargets = props.weightedTargets; | ||
| this.match = props.match; | ||
| this.timeout = props.timeout; | ||
|
|
||
| if (props.retryPolicy) { | ||
| if (props.retryPolicy.grpcRetryEvents && props.retryPolicy.grpcRetryEvents.length === 0) { | ||
| throw new Error('Specify at least one value in `grpcRetryEvents` or leave it undefined'); | ||
| } | ||
|
|
||
| if (props.retryPolicy.httpRetryEvents && props.retryPolicy.httpRetryEvents.length === 0) { | ||
| throw new Error('Specify at least one value in `httpRetryEvents` or leave it undefined'); | ||
| } | ||
|
|
||
| if (props.retryPolicy.tcpRetryEvents && props.retryPolicy.tcpRetryEvents.length === 0) { | ||
| throw new Error('Specify at least one value in `tcpRetryEvents` or leave it undefined'); | ||
| } | ||
|
||
|
|
||
| const numGrpcRetryEvents = (props.retryPolicy.grpcRetryEvents ?? []).length; | ||
| const numHttpRetryEvents = (props.retryPolicy.httpRetryEvents ?? []).length; | ||
| const numTcpRetryEvents = (props.retryPolicy.tcpRetryEvents ?? []).length; | ||
|
|
||
| if (numGrpcRetryEvents + numHttpRetryEvents + numTcpRetryEvents === 0) { | ||
| throw new Error('You must specify one value for at least one of `grpcRetryEvents`, `httpRetryEvents`, `tcpRetryEvents`'); | ||
|
||
| } | ||
|
|
||
| this.retryPolicy = props.retryPolicy; | ||
| } | ||
| } | ||
|
|
||
| public bind(_scope: Construct): RouteSpecConfig { | ||
|
|
@@ -283,6 +453,7 @@ class GrpcRouteSpec extends RouteSpec { | |
| serviceName: this.match.serviceName, | ||
| }, | ||
| timeout: renderTimeout(this.timeout), | ||
| retryPolicy: this.retryPolicy ? renderGrpcRetryPolicy(this.retryPolicy) : undefined, | ||
| }, | ||
| }; | ||
| } | ||
|
|
@@ -323,3 +494,22 @@ function renderTimeout(timeout?: HttpTimeout): CfnRoute.HttpTimeoutProperty | un | |
| } | ||
| : undefined; | ||
| } | ||
|
|
||
| function renderHttpRetryPolicy(retryPolicy: HttpRetryPolicy): CfnRoute.HttpRetryPolicyProperty { | ||
| return { | ||
| maxRetries: retryPolicy.maxRetries, | ||
| perRetryTimeout: { | ||
| unit: 'ms', | ||
| value: retryPolicy.perRetryTimeout.toMilliseconds(), | ||
| }, | ||
| httpRetryEvents: retryPolicy.httpRetryEvents, | ||
| tcpRetryEvents: retryPolicy.tcpRetryEvents, | ||
| }; | ||
| } | ||
|
|
||
| function renderGrpcRetryPolicy(retryPolicy: GrpcRetryPolicy): CfnRoute.GrpcRetryPolicyProperty { | ||
| return { | ||
| ...renderHttpRetryPolicy(retryPolicy), | ||
| grpcRetryEvents: retryPolicy.grpcRetryEvents, | ||
| }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.