Skip to content

Commit fd76558

Browse files
authored
Merge pull request #2241 from grpc/@grpc/[email protected]
Upmerge v1.7.x branch into master
2 parents ea2e3ba + 7282d06 commit fd76558

File tree

9 files changed

+395
-23
lines changed

9 files changed

+395
-23
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
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';
19+
export const EXPERIMENTAL_OUTLIER_DETECTION = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true';

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js";
1919
import { isIPv4, isIPv6 } from "net";
2020
import { Locality__Output } from "../generated/envoy/config/core/v3/Locality";
21+
import { SocketAddress__Output } from "../generated/envoy/config/core/v3/SocketAddress";
2122
import { ClusterLoadAssignment__Output } from "../generated/envoy/config/endpoint/v3/ClusterLoadAssignment";
2223
import { Any__Output } from "../generated/google/protobuf/Any";
2324
import { BaseXdsStreamState, HandleResponseResult, RejectedResourceEntry, ResourcePair, Watcher, XdsStreamState } from "./xds-stream-state";
@@ -32,6 +33,10 @@ function localitiesEqual(a: Locality__Output, b: Locality__Output) {
3233
return a.region === b.region && a.sub_zone === b.sub_zone && a.zone === b.zone;
3334
}
3435

36+
function addressesEqual(a: SocketAddress__Output, b: SocketAddress__Output) {
37+
return a.address === b.address && a.port_value === b.port_value;
38+
}
39+
3540
export class EdsState extends BaseXdsStreamState<ClusterLoadAssignment__Output> implements XdsStreamState<ClusterLoadAssignment__Output> {
3641
protected getResourceName(resource: ClusterLoadAssignment__Output): string {
3742
return resource.cluster_name;
@@ -50,6 +55,8 @@ export class EdsState extends BaseXdsStreamState<ClusterLoadAssignment__Output>
5055
*/
5156
public validateResponse(message: ClusterLoadAssignment__Output) {
5257
const seenLocalities: {locality: Locality__Output, priority: number}[] = [];
58+
const seenAddresses: SocketAddress__Output[] = [];
59+
const priorityTotalWeights: Map<number, number> = new Map();
5360
for (const endpoint of message.endpoints) {
5461
if (!endpoint.locality) {
5562
return false;
@@ -71,6 +78,23 @@ export class EdsState extends BaseXdsStreamState<ClusterLoadAssignment__Output>
7178
if (!(isIPv4(socketAddress.address) || isIPv6(socketAddress.address))) {
7279
return false;
7380
}
81+
for (const address of seenAddresses) {
82+
if (addressesEqual(socketAddress, address)) {
83+
return false;
84+
}
85+
}
86+
seenAddresses.push(socketAddress);
87+
}
88+
priorityTotalWeights.set(endpoint.priority, (priorityTotalWeights.get(endpoint.priority) ?? 0) + (endpoint.load_balancing_weight?.value ?? 0));
89+
}
90+
for (const totalWeight of priorityTotalWeights.values()) {
91+
if (totalWeight >= 1<<32) {
92+
return false;
93+
}
94+
}
95+
for (const priority of priorityTotalWeights.keys()) {
96+
if (priority > 0 && !priorityTotalWeights.has(priority - 1)) {
97+
return false;
7498
}
7599
}
76100
return true;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ export class RdsState extends BaseXdsStreamState<RouteConfiguration__Output> imp
8989
}
9090
}
9191
if (route.route!.cluster_specifier === 'weighted_clusters') {
92+
if (route.route.weighted_clusters!.total_weight?.value === 0) {
93+
return false;
94+
}
9295
let weightSum = 0;
9396
for (const clusterWeight of route.route.weighted_clusters!.clusters) {
9497
weightSum += clusterWeight.weight?.value ?? 0;

packages/grpc-js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@grpc/grpc-js",
3-
"version": "1.7.0",
3+
"version": "1.7.1",
44
"description": "gRPC Library for Node - pure JS implementation",
55
"homepage": "https://grpc.io/",
66
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",

packages/grpc-js/src/channel.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,28 @@ function getNewCallNumber(): number {
6868
return callNumber;
6969
}
7070

71+
const INAPPROPRIATE_CONTROL_PLANE_CODES: Status[] = [
72+
Status.OK,
73+
Status.INVALID_ARGUMENT,
74+
Status.NOT_FOUND,
75+
Status.ALREADY_EXISTS,
76+
Status.FAILED_PRECONDITION,
77+
Status.ABORTED,
78+
Status.OUT_OF_RANGE,
79+
Status.DATA_LOSS
80+
]
81+
82+
function restrictControlPlaneStatusCode(code: Status, details: string): {code: Status, details: string} {
83+
if (INAPPROPRIATE_CONTROL_PLANE_CODES.includes(code)) {
84+
return {
85+
code: Status.INTERNAL,
86+
details: `Invalid status from control plane: ${code} ${Status[code]} ${details}`
87+
}
88+
} else {
89+
return {code, details};
90+
}
91+
}
92+
7193
/**
7294
* An interface that represents a communication channel to a server specified
7395
* by a given address.
@@ -320,7 +342,7 @@ export class ChannelImplementation implements Channel {
320342
this.trace('Name resolution failed with calls queued for config selection');
321343
}
322344
if (this.configSelector === null) {
323-
this.currentResolutionError = status;
345+
this.currentResolutionError = {...restrictControlPlaneStatusCode(status.code, status.details), metadata: status.metadata};
324346
}
325347
const localQueue = this.configSelectionQueue;
326348
this.configSelectionQueue = [];
@@ -534,10 +556,11 @@ export class ChannelImplementation implements Channel {
534556
},
535557
(error: Error & { code: number }) => {
536558
// We assume the error code isn't 0 (Status.OK)
537-
callStream.cancelWithStatus(
559+
const {code, details} = restrictControlPlaneStatusCode(
538560
typeof error.code === 'number' ? error.code : Status.UNKNOWN,
539561
`Getting metadata from plugin failed with error: ${error.message}`
540-
);
562+
)
563+
callStream.cancelWithStatus(code, details);
541564
}
542565
);
543566
}
@@ -549,17 +572,13 @@ export class ChannelImplementation implements Channel {
549572
if (callMetadata.getOptions().waitForReady) {
550573
this.pushPick(callStream, callMetadata, callConfig, dynamicFilters);
551574
} else {
552-
callStream.cancelWithStatus(
553-
pickResult.status!.code,
554-
pickResult.status!.details
555-
);
575+
const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details);
576+
callStream.cancelWithStatus(code, details);
556577
}
557578
break;
558579
case PickResultType.DROP:
559-
callStream.cancelWithStatus(
560-
pickResult.status!.code,
561-
pickResult.status!.details
562-
);
580+
const {code, details} = restrictControlPlaneStatusCode(pickResult.status!.code, pickResult.status!.details);
581+
callStream.cancelWithStatus(code, details);
563582
break;
564583
default:
565584
throw new Error(
@@ -668,10 +687,8 @@ export class ChannelImplementation implements Channel {
668687
this.tryPick(stream, metadata, callConfig, []);
669688
}
670689
} else {
671-
stream.cancelWithStatus(
672-
callConfig.status,
673-
'Failed to route call to method ' + stream.getMethod()
674-
);
690+
const {code, details} = restrictControlPlaneStatusCode(callConfig.status, 'Failed to route call to method ' + stream.getMethod());
691+
stream.cancelWithStatus(code, details);
675692
}
676693
}
677694
}

packages/grpc-js/src/client.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ export type ClientOptions = Partial<ChannelOptions> & {
108108
callInvocationTransformer?: CallInvocationTransformer;
109109
};
110110

111+
function getErrorStackString(error: Error): string {
112+
return error.stack!.split('\n').slice(1).join('\n');
113+
}
114+
111115
/**
112116
* A generic gRPC client. Primarily useful as a base class for all generated
113117
* clients.
@@ -321,7 +325,7 @@ export class Client {
321325
}
322326
let responseMessage: ResponseType | null = null;
323327
let receivedStatus = false;
324-
const callerStack = (new Error().stack!).split('\n').slice(1).join('\n');
328+
const callerStackError = new Error();
325329
call.start(callProperties.metadata, {
326330
onReceiveMetadata: (metadata) => {
327331
emitter.emit('metadata', metadata);
@@ -340,6 +344,7 @@ export class Client {
340344
receivedStatus = true;
341345
if (status.code === Status.OK) {
342346
if (responseMessage === null) {
347+
const callerStack = getErrorStackString(callerStackError);
343348
callProperties.callback!(callErrorFromStatus({
344349
code: Status.INTERNAL,
345350
details: 'No message received',
@@ -349,6 +354,7 @@ export class Client {
349354
callProperties.callback!(null, responseMessage);
350355
}
351356
} else {
357+
const callerStack = getErrorStackString(callerStackError);
352358
callProperties.callback!(callErrorFromStatus(status, callerStack));
353359
}
354360
emitter.emit('status', status);
@@ -447,7 +453,7 @@ export class Client {
447453
}
448454
let responseMessage: ResponseType | null = null;
449455
let receivedStatus = false;
450-
const callerStack = (new Error().stack!).split('\n').slice(1).join('\n');
456+
const callerStackError = new Error();
451457
call.start(callProperties.metadata, {
452458
onReceiveMetadata: (metadata) => {
453459
emitter.emit('metadata', metadata);
@@ -466,6 +472,7 @@ export class Client {
466472
receivedStatus = true;
467473
if (status.code === Status.OK) {
468474
if (responseMessage === null) {
475+
const callerStack = getErrorStackString(callerStackError);
469476
callProperties.callback!(callErrorFromStatus({
470477
code: Status.INTERNAL,
471478
details: 'No message received',
@@ -475,6 +482,7 @@ export class Client {
475482
callProperties.callback!(null, responseMessage);
476483
}
477484
} else {
485+
const callerStack = getErrorStackString(callerStackError);
478486
callProperties.callback!(callErrorFromStatus(status, callerStack));
479487
}
480488
emitter.emit('status', status);
@@ -577,7 +585,7 @@ export class Client {
577585
call.setCredentials(callProperties.callOptions.credentials);
578586
}
579587
let receivedStatus = false;
580-
const callerStack = (new Error().stack!).split('\n').slice(1).join('\n');
588+
const callerStackError = new Error();
581589
call.start(callProperties.metadata, {
582590
onReceiveMetadata(metadata: Metadata) {
583591
stream.emit('metadata', metadata);
@@ -593,6 +601,7 @@ export class Client {
593601
receivedStatus = true;
594602
stream.push(null);
595603
if (status.code !== Status.OK) {
604+
const callerStack = getErrorStackString(callerStackError);
596605
stream.emit('error', callErrorFromStatus(status, callerStack));
597606
}
598607
stream.emit('status', status);
@@ -675,7 +684,7 @@ export class Client {
675684
call.setCredentials(callProperties.callOptions.credentials);
676685
}
677686
let receivedStatus = false;
678-
const callerStack = (new Error().stack!).split('\n').slice(1).join('\n');
687+
const callerStackError = new Error();
679688
call.start(callProperties.metadata, {
680689
onReceiveMetadata(metadata: Metadata) {
681690
stream.emit('metadata', metadata);
@@ -690,6 +699,7 @@ export class Client {
690699
receivedStatus = true;
691700
stream.push(null);
692701
if (status.code !== Status.OK) {
702+
const callerStack = getErrorStackString(callerStackError);
693703
stream.emit('error', callErrorFromStatus(status, callerStack));
694704
}
695705
stream.emit('status', status);

packages/grpc-js/src/load-balancer-outlier-detection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function trace(text: string): void {
3838

3939
const TYPE_NAME = 'outlier_detection';
4040

41-
const OUTLIER_DETECTION_ENABLED = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION !== 'false';
41+
const OUTLIER_DETECTION_ENABLED = (process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION ?? 'true') === 'true';
4242

4343
export interface SuccessRateEjectionConfig {
4444
readonly stdev_factor: number;

packages/grpc-js/test/test-channel-credentials.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@
1717

1818
import * as assert from 'assert';
1919
import * as fs from 'fs';
20+
import * as path from 'path';
2021
import { promisify } from 'util';
22+
import * as protoLoader from '@grpc/proto-loader';
2123

2224
import { CallCredentials } from '../src/call-credentials';
2325
import { ChannelCredentials } from '../src/channel-credentials';
26+
import * as grpc from '../src';
27+
import { ServiceClient, ServiceClientConstructor } from '../src/make-client';
28+
import { TestServiceClient, TestServiceHandlers } from './generated/TestService';
29+
import { ProtoGrpcType as TestServiceGrpcType } from './generated/test_service';
2430

25-
import { assert2, mockFunction } from './common';
31+
import { assert2, loadProtoFile, mockFunction } from './common';
32+
import { sendUnaryData, ServerUnaryCall, ServiceError } from '../src';
33+
34+
const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto');
35+
const echoService = loadProtoFile(protoFile).EchoService as ServiceClientConstructor;
2636

2737
class CallCredentialsMock implements CallCredentials {
2838
child: CallCredentialsMock | null = null;
@@ -138,3 +148,65 @@ describe('ChannelCredentials Implementation', () => {
138148
});
139149
});
140150
});
151+
152+
describe('ChannelCredentials usage', () => {
153+
let client: ServiceClient;
154+
let server: grpc.Server;
155+
before(async () => {
156+
const {ca, key, cert} = await pFixtures;
157+
const serverCreds = grpc.ServerCredentials.createSsl(null, [{private_key: key, cert_chain: cert}]);
158+
const channelCreds = ChannelCredentials.createSsl(ca);
159+
const callCreds = CallCredentials.createFromMetadataGenerator((options, cb) => {
160+
const metadata = new grpc.Metadata();
161+
metadata.set('test-key', 'test-value');
162+
cb(null, metadata);
163+
});
164+
const combinedCreds = channelCreds.compose(callCreds);
165+
return new Promise<void>((resolve, reject) => {
166+
167+
server = new grpc.Server();
168+
server.addService(echoService.service, {
169+
echo(call: ServerUnaryCall<any, any>, callback: sendUnaryData<any>) {
170+
call.sendMetadata(call.metadata);
171+
callback(null, call.request);
172+
},
173+
});
174+
175+
server.bindAsync(
176+
'localhost:0',
177+
serverCreds,
178+
(err, port) => {
179+
if (err) {
180+
reject(err);
181+
return;
182+
}
183+
client = new echoService(
184+
`localhost:${port}`,
185+
combinedCreds,
186+
{'grpc.ssl_target_name_override': 'foo.test.google.fr', 'grpc.default_authority': 'foo.test.google.fr'}
187+
);
188+
server.start();
189+
resolve();
190+
}
191+
);
192+
});
193+
});
194+
after(() => {
195+
server.forceShutdown();
196+
});
197+
198+
it('Should send the metadata from call credentials attached to channel credentials', (done) => {
199+
const call = client.echo(
200+
{ value: 'test value', value2: 3 },
201+
assert2.mustCall((error: ServiceError, response: any) => {
202+
assert.ifError(error);
203+
assert.deepStrictEqual(response, { value: 'test value', value2: 3 });
204+
})
205+
);
206+
call.on('metadata', assert2.mustCall((metadata: grpc.Metadata) => {
207+
assert.deepStrictEqual(metadata.get('test-key'), ['test-value']);
208+
209+
}));
210+
assert2.afterMustCallsSatisfied(done);
211+
});
212+
});

0 commit comments

Comments
 (0)