Skip to content

Commit

Permalink
Merge pull request #2580 from murgatroid99/grpc-js-xds_fix_idle_handling
Browse files Browse the repository at this point in the history
grpc-js-xds: Fix behavior when channel goes IDLE
  • Loading branch information
murgatroid99 committed Sep 21, 2023
2 parents c8cbffa + c8b9a45 commit 390a1a4
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 12 deletions.
2 changes: 1 addition & 1 deletion packages/grpc-js-xds/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@grpc/grpc-js-xds",
"version": "1.9.0",
"version": "1.9.1",
"description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.",
"main": "build/src/index.js",
"scripts": {
Expand Down
14 changes: 8 additions & 6 deletions packages/grpc-js-xds/src/resolver-xds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ function getPredicateForMatcher(routeMatch: RouteMatch__Output): Matcher {
* the ServiceConfig definition. The difference is that the protobuf message
* defines seconds as a long, which is represented as a string in JavaScript,
* and the one used in the service config defines it as a number.
* @param duration
* @param duration
*/
function protoDurationToDuration(duration: Duration__Output): Duration {
return {
Expand All @@ -235,7 +235,7 @@ function getDefaultRetryMaxInterval(baseInterval: string): string {
/**
* Encode a text string as a valid path of a URI, as specified in RFC-3986 section 3.3
* @param uriPath A value representing an unencoded URI path
* @returns
* @returns
*/
function encodeURIPath(uriPath: string): string {
return uriPath.replace(/[^A-Za-z0-9._~!$&^()*+,;=/-]/g, substring => encodeURIComponent(substring));
Expand Down Expand Up @@ -447,7 +447,7 @@ class XdsResolver implements Resolver {
}
}
}
let retryPolicy: RetryPolicy | undefined = undefined;
let retryPolicy: RetryPolicy | undefined = undefined;
if (EXPERIMENTAL_RETRY) {
const retryConfig = route.route!.retry_policy ?? virtualHost.retry_policy;
if (retryConfig) {
Expand All @@ -458,10 +458,10 @@ class XdsResolver implements Resolver {
}
}
if (retryableStatusCodes.length > 0) {
const baseInterval = retryConfig.retry_back_off?.base_interval ?
protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) :
const baseInterval = retryConfig.retry_back_off?.base_interval ?
protoDurationToSecondsString(retryConfig.retry_back_off.base_interval) :
DEFAULT_RETRY_BASE_INTERVAL;
const maxInterval = retryConfig.retry_back_off?.max_interval ?
const maxInterval = retryConfig.retry_back_off?.max_interval ?
protoDurationToSecondsString(retryConfig.retry_back_off.max_interval) :
getDefaultRetryMaxInterval(baseInterval);
retryPolicy = {
Expand Down Expand Up @@ -664,9 +664,11 @@ class XdsResolver implements Resolver {
destroy() {
if (this.listenerResourceName) {
ListenerResourceType.cancelWatch(this.xdsClient, this.listenerResourceName, this.ldsWatcher);
this.isLdsWatcherActive = false;
}
if (this.latestRouteConfigName) {
RouteConfigurationResourceType.cancelWatch(this.xdsClient, this.latestRouteConfigName, this.rdsWatcher);
this.latestRouteConfigName = null;
}
}

Expand Down
14 changes: 9 additions & 5 deletions packages/grpc-js-xds/test/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*
*/

import { credentials, loadPackageDefinition, ServiceError } from "@grpc/grpc-js";
import { ChannelOptions, credentials, loadPackageDefinition, ServiceError } from "@grpc/grpc-js";
import { loadSync } from "@grpc/proto-loader";
import { ProtoGrpcType } from "./generated/echo";
import { EchoTestServiceClient } from "./generated/grpc/testing/EchoTestService";
Expand Down Expand Up @@ -44,14 +44,14 @@ export class XdsTestClient {
private client: EchoTestServiceClient;
private callInterval: NodeJS.Timer;

constructor(target: string, bootstrapInfo: string) {
this.client = new loadedProtos.grpc.testing.EchoTestService(target, credentials.createInsecure(), {[BOOTSTRAP_CONFIG_KEY]: bootstrapInfo});
constructor(target: string, bootstrapInfo: string, options?: ChannelOptions) {
this.client = new loadedProtos.grpc.testing.EchoTestService(target, credentials.createInsecure(), {...options, [BOOTSTRAP_CONFIG_KEY]: bootstrapInfo});
this.callInterval = setInterval(() => {}, 0);
clearInterval(this.callInterval);
}

static createFromServer(targetName: string, xdsServer: XdsServer) {
return new XdsTestClient(`xds:///${targetName}`, xdsServer.getBootstrapInfoString());
static createFromServer(targetName: string, xdsServer: XdsServer, options?: ChannelOptions) {
return new XdsTestClient(`xds:///${targetName}`, xdsServer.getBootstrapInfoString(), options);
}

startCalls(interval: number) {
Expand Down Expand Up @@ -98,4 +98,8 @@ export class XdsTestClient {
}
sendInner(count, callback);
}

getConnectivityState() {
return this.client.getChannel().getConnectivityState(false);
}
}
31 changes: 31 additions & 0 deletions packages/grpc-js-xds/test/test-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { XdsServer } from "./xds-server";

import { register } from "../src";
import assert = require("assert");
import { connectivityState } from "@grpc/grpc-js";

register();

Expand Down Expand Up @@ -60,4 +61,34 @@ describe('core xDS functionality', () => {
}, reason => done(reason));
}, reason => done(reason));
});
it('should be able to enter and exit idle', function(done) {
this.timeout(5000);
const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [new Backend()], locality:{region: 'region1'}}]);
const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]);
routeGroup.startAllBackends().then(() => {
xdsServer.setEdsResource(cluster.getEndpointConfig());
xdsServer.setCdsResource(cluster.getClusterConfig());
xdsServer.setRdsResource(routeGroup.getRouteConfiguration());
xdsServer.setLdsResource(routeGroup.getListener());
xdsServer.addResponseListener((typeUrl, responseState) => {
if (responseState.state === 'NACKED') {
client.stopCalls();
assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`);
}
})
client = XdsTestClient.createFromServer('listener1', xdsServer, {
'grpc.client_idle_timeout_ms': 1000,
});
client.sendOneCall(error => {
assert.ifError(error);
assert.strictEqual(client.getConnectivityState(), connectivityState.READY);
setTimeout(() => {
assert.strictEqual(client.getConnectivityState(), connectivityState.IDLE);
client.sendOneCall(error => {
done(error);
})
}, 1100);
});
}, reason => done(reason));
});
});

0 comments on commit 390a1a4

Please sign in to comment.