Skip to content

Commit 5f01206

Browse files
authored
Merge branch 'grpc:master' into master
2 parents 6b4dd60 + a403ad7 commit 5f01206

File tree

6 files changed

+106
-35
lines changed

6 files changed

+106
-35
lines changed

packages/grpc-js-xds/src/environment.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
*/
1717

1818
export const EXPERIMENTAL_FAULT_INJECTION = (process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION ?? 'true') === 'true';
19-
export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true';
19+
export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true';
20+
export const EXPERIMENTAL_RETRY = process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY === 'true';

packages/grpc-js-xds/src/resolver-xds.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resourc
4444
import Duration = experimental.Duration;
4545
import { Duration__Output } from './generated/google/protobuf/Duration';
4646
import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter';
47-
import { EXPERIMENTAL_FAULT_INJECTION } from './environment';
47+
import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from './environment';
4848
import Filter = experimental.Filter;
4949
import FilterFactory = experimental.FilterFactory;
50+
import RetryPolicy = experimental.RetryPolicy;
5051

5152
const TRACER_NAME = 'xds_resolver';
5253

@@ -199,6 +200,24 @@ function protoDurationToDuration(duration: Duration__Output): Duration {
199200
}
200201
}
201202

203+
function protoDurationToSecondsString(duration: Duration__Output): string {
204+
return `${duration.seconds + duration.nanos / 1_000_000_000}s`;
205+
}
206+
207+
const DEFAULT_RETRY_BASE_INTERVAL = '0.025s'
208+
209+
function getDefaultRetryMaxInterval(baseInterval: string): string {
210+
return `${Number.parseFloat(baseInterval.substring(0, baseInterval.length - 1)) * 10}s`;
211+
}
212+
213+
const RETRY_CODES: {[key: string]: status} = {
214+
'cancelled': status.CANCELLED,
215+
'deadline-exceeded': status.DEADLINE_EXCEEDED,
216+
'internal': status.INTERNAL,
217+
'resource-exhausted': status.RESOURCE_EXHAUSTED,
218+
'unavailable': status.UNAVAILABLE
219+
};
220+
202221
class XdsResolver implements Resolver {
203222
private hasReportedSuccess = false;
204223

@@ -363,6 +382,33 @@ class XdsResolver implements Resolver {
363382
}
364383
}
365384
}
385+
let retryPolicy: RetryPolicy | undefined = undefined;
386+
if (EXPERIMENTAL_RETRY) {
387+
const retryConfig = route.route!.retry_policy ?? virtualHost.retry_policy;
388+
if (retryConfig) {
389+
const retryableStatusCodes = [];
390+
for (const code of retryConfig.retry_on.split(',')) {
391+
if (RETRY_CODES[code]) {
392+
retryableStatusCodes.push(RETRY_CODES[code]);
393+
}
394+
}
395+
if (retryableStatusCodes.length > 0) {
396+
const baseInterval = retryConfig.retry_back_off?.base_interval ?
397+
protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) :
398+
DEFAULT_RETRY_BASE_INTERVAL;
399+
const maxInterval = retryConfig.retry_back_off?.max_interval ?
400+
protoDurationToSecondsString(retryConfig.retry_back_off.max_interval) :
401+
getDefaultRetryMaxInterval(baseInterval);
402+
retryPolicy = {
403+
backoffMultiplier: 2,
404+
initialBackoff: baseInterval,
405+
maxBackoff: maxInterval,
406+
maxAttempts: (retryConfig.num_retries?.value ?? 1) + 1,
407+
retryableStatusCodes: retryableStatusCodes
408+
};
409+
}
410+
}
411+
}
366412
switch (route.route!.cluster_specifier) {
367413
case 'cluster_header':
368414
continue;
@@ -390,7 +436,7 @@ class XdsResolver implements Resolver {
390436
}
391437
}
392438
}
393-
routeAction = new SingleClusterRouteAction(cluster, timeout, extraFilterFactories);
439+
routeAction = new SingleClusterRouteAction(cluster, {name: [], timeout: timeout, retryPolicy: retryPolicy}, extraFilterFactories);
394440
break;
395441
}
396442
case 'weighted_clusters': {
@@ -432,7 +478,7 @@ class XdsResolver implements Resolver {
432478
}
433479
weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, dynamicFilterFactories: extraFilterFactories});
434480
}
435-
routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, timeout);
481+
routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, {name: [], timeout: timeout, retryPolicy: retryPolicy});
436482
break;
437483
}
438484
default:
@@ -470,7 +516,7 @@ class XdsResolver implements Resolver {
470516
this.unrefCluster(clusterResult.name);
471517
}
472518
return {
473-
methodConfig: {name: [], timeout: action.getTimeout()},
519+
methodConfig: clusterResult.methodConfig,
474520
onCommitted: onCommitted,
475521
pickInformation: {cluster: clusterResult.name},
476522
status: status.OK,

packages/grpc-js-xds/src/route-action.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@ import { experimental } from '@grpc/grpc-js';
1818
import Duration = experimental.Duration;
1919
import Filter = experimental.Filter;
2020
import FilterFactory = experimental.FilterFactory;
21+
import MethodConfig = experimental.MethodConfig;
2122

2223
export interface ClusterResult {
2324
name: string;
25+
methodConfig: MethodConfig;
2426
dynamicFilterFactories: FilterFactory<Filter>[];
2527
}
2628

2729
export interface RouteAction {
2830
toString(): string;
2931
getCluster(): ClusterResult;
30-
getTimeout(): Duration | undefined;
3132
}
3233

3334
function durationToLogString(duration: Duration) {
@@ -40,25 +41,18 @@ function durationToLogString(duration: Duration) {
4041
}
4142

4243
export class SingleClusterRouteAction implements RouteAction {
43-
constructor(private cluster: string, private timeout: Duration | undefined, private extraFilterFactories: FilterFactory<Filter>[]) {}
44+
constructor(private cluster: string, private methodConfig: MethodConfig, private extraFilterFactories: FilterFactory<Filter>[]) {}
4445

4546
getCluster() {
4647
return {
4748
name: this.cluster,
49+
methodConfig: this.methodConfig,
4850
dynamicFilterFactories: this.extraFilterFactories
4951
};
5052
}
5153

5254
toString() {
53-
if (this.timeout) {
54-
return 'SingleCluster(' + this.cluster + ', ' + 'timeout=' + durationToLogString(this.timeout) + 's)';
55-
} else {
56-
return 'SingleCluster(' + this.cluster + ')';
57-
}
58-
}
59-
60-
getTimeout() {
61-
return this.timeout;
55+
return 'SingleCluster(' + this.cluster + ', ' + JSON.stringify(this.methodConfig) + ')';
6256
}
6357
}
6458

@@ -79,7 +73,7 @@ export class WeightedClusterRouteAction implements RouteAction {
7973
* The weighted cluster choices represented as a CDF
8074
*/
8175
private clusterChoices: ClusterChoice[];
82-
constructor(private clusters: WeightedCluster[], private totalWeight: number, private timeout: Duration | undefined) {
76+
constructor(private clusters: WeightedCluster[], private totalWeight: number, private methodConfig: MethodConfig) {
8377
this.clusterChoices = [];
8478
let lastNumerator = 0;
8579
for (const clusterWeight of clusters) {
@@ -94,24 +88,17 @@ export class WeightedClusterRouteAction implements RouteAction {
9488
if (randomNumber < choice.numerator) {
9589
return {
9690
name: choice.name,
91+
methodConfig: this.methodConfig,
9792
dynamicFilterFactories: choice.dynamicFilterFactories
9893
};
9994
}
10095
}
10196
// This should be prevented by the validation rules
102-
return {name: '', dynamicFilterFactories: []};
97+
return {name: '', methodConfig: this.methodConfig, dynamicFilterFactories: []};
10398
}
10499

105100
toString() {
106101
const clusterListString = this.clusters.map(({name, weight}) => '(' + name + ':' + weight + ')').join(', ')
107-
if (this.timeout) {
108-
return 'WeightedCluster(' + clusterListString + ', ' + 'timeout=' + durationToLogString(this.timeout) + 's)';
109-
} else {
110-
return 'WeightedCluster(' + clusterListString + ')';
111-
}
112-
}
113-
114-
getTimeout() {
115-
return this.timeout;
102+
return 'WeightedCluster(' + clusterListString + ', ' + JSON.stringify(this.methodConfig) + ')';
116103
}
117104
}

packages/grpc-js-xds/src/xds-stream-state/rds-state.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
*
1616
*/
1717

18-
import { EXPERIMENTAL_FAULT_INJECTION } from "../environment";
18+
import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_RETRY } from "../environment";
19+
import { RetryPolicy__Output } from "../generated/envoy/config/route/v3/RetryPolicy";
1920
import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration";
21+
import { Duration__Output } from "../generated/google/protobuf/Duration";
2022
import { validateOverrideFilter } from "../http-filter";
2123
import { BaseXdsStreamState, XdsStreamState } from "./xds-stream-state";
2224

@@ -30,6 +32,13 @@ const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [
3032
'suffix_match'];
3133
const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header'];
3234

35+
function durationToMs(duration: Duration__Output | null): number | null {
36+
if (duration === null) {
37+
return null;
38+
}
39+
return (Number.parseInt(duration.seconds) * 1000 + duration.nanos / 1_000_000) | 0;
40+
}
41+
3342
export class RdsState extends BaseXdsStreamState<RouteConfiguration__Output> implements XdsStreamState<RouteConfiguration__Output> {
3443
protected isStateOfTheWorld(): boolean {
3544
return false;
@@ -40,6 +49,28 @@ export class RdsState extends BaseXdsStreamState<RouteConfiguration__Output> imp
4049
protected getProtocolName(): string {
4150
return 'RDS';
4251
}
52+
53+
private validateRetryPolicy(policy: RetryPolicy__Output | null): boolean {
54+
if (policy === null) {
55+
return true;
56+
}
57+
const numRetries = policy.num_retries?.value ?? 1
58+
if (numRetries < 1) {
59+
return false;
60+
}
61+
if (policy.retry_back_off) {
62+
if (!policy.retry_back_off.base_interval) {
63+
return false;
64+
}
65+
const baseInterval = durationToMs(policy.retry_back_off.base_interval)!;
66+
const maxInterval = durationToMs(policy.retry_back_off.max_interval) ?? (10 * baseInterval);
67+
if (!(maxInterval >= baseInterval) && (baseInterval > 0)) {
68+
return false;
69+
}
70+
}
71+
return true;
72+
}
73+
4374
validateResponse(message: RouteConfiguration__Output): boolean {
4475
// https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation
4576
for (const virtualHost of message.virtual_hosts) {
@@ -62,6 +93,11 @@ export class RdsState extends BaseXdsStreamState<RouteConfiguration__Output> imp
6293
}
6394
}
6495
}
96+
if (EXPERIMENTAL_RETRY) {
97+
if (!this.validateRetryPolicy(virtualHost.retry_policy)) {
98+
return false;
99+
}
100+
}
65101
for (const route of virtualHost.routes) {
66102
const match = route.match;
67103
if (!match) {
@@ -88,6 +124,11 @@ export class RdsState extends BaseXdsStreamState<RouteConfiguration__Output> imp
88124
}
89125
}
90126
}
127+
if (EXPERIMENTAL_RETRY) {
128+
if (!this.validateRetryPolicy(route.route.retry_policy)) {
129+
return false;
130+
}
131+
}
91132
if (route.route!.cluster_specifier === 'weighted_clusters') {
92133
if (route.route.weighted_clusters!.total_weight?.value === 0) {
93134
return false;

packages/grpc-js/src/experimental.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export {
77
} from './resolver';
88
export { GrpcUri, uriToString } from './uri-parser';
99
export { Duration, durationToMs } from './duration';
10-
export { ServiceConfig } from './service-config';
10+
export { ServiceConfig, MethodConfig, RetryPolicy } from './service-config';
1111
export { BackoffTimeout } from './backoff-timeout';
1212
export {
1313
LoadBalancer,

tools/release/native/Dockerfile

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
FROM debian:jessie
1+
FROM debian:stretch
22

3-
RUN echo "deb http://archive.debian.org/debian jessie-backports main" > /etc/apt/sources.list.d/backports.list
4-
RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf
5-
RUN sed -i '/deb http:\/\/deb.debian.org\/debian jessie-updates main/d' /etc/apt/sources.list
63
RUN apt-get update
7-
RUN apt-get -t jessie-backports install -y cmake
8-
RUN apt-get install -y curl build-essential python libc6-dev-i386 lib32stdc++-4.9-dev jq
4+
RUN apt-get install -y cmake curl build-essential python libc6-dev-i386 lib32stdc++-6-dev jq
95

106
RUN mkdir /usr/local/nvm
117
ENV NVM_DIR /usr/local/nvm

0 commit comments

Comments
 (0)