Skip to content

Commit 092d1e9

Browse files
authored
Merge pull request #2561 from murgatroid99/grpc-js_pick_first_leaf
grpc-js: Make pick_first the universal leaf policy, plus related changes
2 parents 8d532f9 + 266af4c commit 092d1e9

28 files changed

+816
-593
lines changed

packages/grpc-js-xds/interop/xds-interop-client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import TypedLoadBalancingConfig = grpc.experimental.TypedLoadBalancingConfig;
3434
import LoadBalancer = grpc.experimental.LoadBalancer;
3535
import ChannelControlHelper = grpc.experimental.ChannelControlHelper;
3636
import ChildLoadBalancerHandler = grpc.experimental.ChildLoadBalancerHandler;
37-
import SubchannelAddress = grpc.experimental.SubchannelAddress;
37+
import Endpoint = grpc.experimental.Endpoint;
3838
import Picker = grpc.experimental.Picker;
3939
import PickArgs = grpc.experimental.PickArgs;
4040
import PickResult = grpc.experimental.PickResult;
@@ -99,12 +99,12 @@ class RpcBehaviorLoadBalancer implements LoadBalancer {
9999
});
100100
this.child = new ChildLoadBalancerHandler(childChannelControlHelper);
101101
}
102-
updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void {
102+
updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void {
103103
if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) {
104104
return;
105105
}
106106
this.latestConfig = lbConfig;
107-
this.child.updateAddressList(addressList, RPC_BEHAVIOR_CHILD_CONFIG, attributes);
107+
this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, attributes);
108108
}
109109
exitIdle(): void {
110110
this.child.exitIdle();

packages/grpc-js-xds/src/load-balancer-cds.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,16 @@
1818
import { connectivityState, status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from '@grpc/grpc-js';
1919
import { getSingletonXdsClient, Watcher, XdsClient } from './xds-client';
2020
import { Cluster__Output } from './generated/envoy/config/cluster/v3/Cluster';
21-
import SubchannelAddress = experimental.SubchannelAddress;
21+
import Endpoint = experimental.Endpoint;
2222
import UnavailablePicker = experimental.UnavailablePicker;
2323
import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
2424
import LoadBalancer = experimental.LoadBalancer;
2525
import ChannelControlHelper = experimental.ChannelControlHelper;
2626
import registerLoadBalancerType = experimental.registerLoadBalancerType;
2727
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
28-
import SuccessRateEjectionConfig = experimental.SuccessRateEjectionConfig;
29-
import FailurePercentageEjectionConfig = experimental.FailurePercentageEjectionConfig;
3028
import QueuePicker = experimental.QueuePicker;
31-
import OutlierDetectionRawConfig = experimental.OutlierDetectionRawConfig;
3229
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
33-
import { OutlierDetection__Output } from './generated/envoy/config/cluster/v3/OutlierDetection';
34-
import { Duration__Output } from './generated/google/protobuf/Duration';
35-
import { EXPERIMENTAL_OUTLIER_DETECTION } from './environment';
3630
import { DiscoveryMechanism, XdsClusterResolverChildPolicyHandler } from './load-balancer-xds-cluster-resolver';
37-
import { CLUSTER_CONFIG_TYPE_URL, decodeSingleResource } from './resources';
3831
import { CdsUpdate, ClusterResourceType } from './xds-resource-type/cluster-resource-type';
3932

4033
const TRACER_NAME = 'cds_balancer';
@@ -258,7 +251,7 @@ export class CdsLoadBalancer implements LoadBalancer {
258251
}
259252

260253
updateAddressList(
261-
addressList: SubchannelAddress[],
254+
endpointList: Endpoint[],
262255
lbConfig: TypedLoadBalancingConfig,
263256
attributes: { [key: string]: unknown }
264257
): void {

packages/grpc-js-xds/src/load-balancer-priority.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import { connectivityState as ConnectivityState, status as Status, Metadata, log
1919
import LoadBalancer = experimental.LoadBalancer;
2020
import ChannelControlHelper = experimental.ChannelControlHelper;
2121
import registerLoadBalancerType = experimental.registerLoadBalancerType;
22-
import SubchannelAddress = experimental.SubchannelAddress;
23-
import subchannelAddressToString = experimental.subchannelAddressToString;
22+
import Endpoint = experimental.Endpoint;
23+
import endpointToString = experimental.endpointToString;
2424
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
2525
import Picker = experimental.Picker;
2626
import QueuePicker = experimental.QueuePicker;
@@ -40,16 +40,16 @@ const TYPE_NAME = 'priority';
4040
const DEFAULT_FAILOVER_TIME_MS = 10_000;
4141
const DEFAULT_RETENTION_INTERVAL_MS = 15 * 60 * 1000;
4242

43-
export type LocalitySubchannelAddress = SubchannelAddress & {
43+
export interface LocalityEndpoint extends Endpoint {
4444
localityPath: string[];
4545
locality: Locality__Output;
4646
weight: number;
4747
};
4848

49-
export function isLocalitySubchannelAddress(
50-
address: SubchannelAddress
51-
): address is LocalitySubchannelAddress {
52-
return Array.isArray((address as LocalitySubchannelAddress).localityPath);
49+
export function isLocalityEndpoint(
50+
address: Endpoint
51+
): address is LocalityEndpoint {
52+
return Array.isArray((address as LocalityEndpoint).localityPath);
5353
}
5454

5555
/**
@@ -138,7 +138,7 @@ class PriorityLoadBalancingConfig implements TypedLoadBalancingConfig {
138138

139139
interface PriorityChildBalancer {
140140
updateAddressList(
141-
addressList: SubchannelAddress[],
141+
endpointList: Endpoint[],
142142
lbConfig: TypedLoadBalancingConfig,
143143
attributes: { [key: string]: unknown }
144144
): void;
@@ -154,7 +154,7 @@ interface PriorityChildBalancer {
154154
}
155155

156156
interface UpdateArgs {
157-
subchannelAddress: SubchannelAddress[];
157+
subchannelAddress: Endpoint[];
158158
lbConfig: TypedLoadBalancingConfig;
159159
ignoreReresolutionRequests: boolean;
160160
}
@@ -218,11 +218,11 @@ export class PriorityLoadBalancer implements LoadBalancer {
218218
}
219219

220220
updateAddressList(
221-
addressList: SubchannelAddress[],
221+
endpointList: Endpoint[],
222222
lbConfig: TypedLoadBalancingConfig,
223223
attributes: { [key: string]: unknown }
224224
): void {
225-
this.childBalancer.updateAddressList(addressList, lbConfig, attributes);
225+
this.childBalancer.updateAddressList(endpointList, lbConfig, attributes);
226226
}
227227

228228
exitIdle() {
@@ -412,7 +412,7 @@ export class PriorityLoadBalancer implements LoadBalancer {
412412
}
413413

414414
updateAddressList(
415-
addressList: SubchannelAddress[],
415+
endpointList: Endpoint[],
416416
lbConfig: TypedLoadBalancingConfig,
417417
attributes: { [key: string]: unknown }
418418
): void {
@@ -425,23 +425,23 @@ export class PriorityLoadBalancer implements LoadBalancer {
425425
* which child it belongs to. So we bucket those addresses by that first
426426
* element, and pass along the rest of the localityPath for that child
427427
* to use. */
428-
const childAddressMap: Map<string, LocalitySubchannelAddress[]> = new Map<
428+
const childAddressMap: Map<string, LocalityEndpoint[]> = new Map<
429429
string,
430-
LocalitySubchannelAddress[]
430+
LocalityEndpoint[]
431431
>();
432-
for (const address of addressList) {
433-
if (!isLocalitySubchannelAddress(address)) {
432+
for (const endpoint of endpointList) {
433+
if (!isLocalityEndpoint(endpoint)) {
434434
// Reject address that cannot be prioritized
435435
return;
436436
}
437-
if (address.localityPath.length < 1) {
437+
if (endpoint.localityPath.length < 1) {
438438
// Reject address that cannot be prioritized
439439
return;
440440
}
441-
const childName = address.localityPath[0];
442-
const childAddress: LocalitySubchannelAddress = {
443-
...address,
444-
localityPath: address.localityPath.slice(1),
441+
const childName = endpoint.localityPath[0];
442+
const childAddress: LocalityEndpoint = {
443+
...endpoint,
444+
localityPath: endpoint.localityPath.slice(1),
445445
};
446446
let childAddressList = childAddressMap.get(childName);
447447
if (childAddressList === undefined) {
@@ -458,7 +458,7 @@ export class PriorityLoadBalancer implements LoadBalancer {
458458
* update all existing children with their new configs */
459459
for (const [childName, childConfig] of lbConfig.getChildren()) {
460460
const childAddresses = childAddressMap.get(childName) ?? [];
461-
trace('Assigning child ' + childName + ' address list ' + childAddresses.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')'))
461+
trace('Assigning child ' + childName + ' endpoint list ' + childAddresses.map(endpoint => '(' + endpointToString(endpoint) + ' path=' + endpoint.localityPath + ')'))
462462
this.latestUpdates.set(childName, {
463463
subchannelAddress: childAddresses,
464464
lbConfig: childConfig.config,

packages/grpc-js-xds/src/load-balancer-weighted-target.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental, LoadBalancingConfig } from "@grpc/grpc-js";
19-
import { isLocalitySubchannelAddress, LocalitySubchannelAddress } from "./load-balancer-priority";
19+
import { isLocalityEndpoint, LocalityEndpoint } from "./load-balancer-priority";
2020
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
2121
import LoadBalancer = experimental.LoadBalancer;
2222
import ChannelControlHelper = experimental.ChannelControlHelper;
@@ -27,8 +27,8 @@ import PickResult = experimental.PickResult;
2727
import PickArgs = experimental.PickArgs;
2828
import QueuePicker = experimental.QueuePicker;
2929
import UnavailablePicker = experimental.UnavailablePicker;
30-
import SubchannelAddress = experimental.SubchannelAddress;
31-
import subchannelAddressToString = experimental.subchannelAddressToString;
30+
import Endpoint = experimental.Endpoint;
31+
import endpointToString = experimental.endpointToString;
3232
import selectLbConfigFromList = experimental.selectLbConfigFromList;
3333

3434
const TRACER_NAME = 'weighted_target';
@@ -154,7 +154,7 @@ class WeightedTargetPicker implements Picker {
154154
}
155155

156156
interface WeightedChild {
157-
updateAddressList(addressList: SubchannelAddress[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void;
157+
updateAddressList(endpointList: Endpoint[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void;
158158
exitIdle(): void;
159159
resetBackoff(): void;
160160
destroy(): void;
@@ -190,9 +190,9 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
190190
this.parent.maybeUpdateState();
191191
}
192192

193-
updateAddressList(addressList: SubchannelAddress[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void {
193+
updateAddressList(endpointList: Endpoint[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void {
194194
this.weight = lbConfig.weight;
195-
this.childBalancer.updateAddressList(addressList, lbConfig.child_policy, attributes);
195+
this.childBalancer.updateAddressList(endpointList, lbConfig.child_policy, attributes);
196196
}
197197
exitIdle(): void {
198198
this.childBalancer.exitIdle();
@@ -319,7 +319,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
319319
this.channelControlHelper.updateState(connectivityState, picker);
320320
}
321321

322-
updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void {
322+
updateAddressList(addressList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void {
323323
if (!(lbConfig instanceof WeightedTargetLoadBalancingConfig)) {
324324
// Reject a config of the wrong type
325325
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
@@ -330,9 +330,9 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
330330
* which child it belongs to. So we bucket those addresses by that first
331331
* element, and pass along the rest of the localityPath for that child
332332
* to use. */
333-
const childAddressMap = new Map<string, LocalitySubchannelAddress[]>();
333+
const childEndpointMap = new Map<string, LocalityEndpoint[]>();
334334
for (const address of addressList) {
335-
if (!isLocalitySubchannelAddress(address)) {
335+
if (!isLocalityEndpoint(address)) {
336336
// Reject address that cannot be associated with targets
337337
return;
338338
}
@@ -341,14 +341,14 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
341341
return;
342342
}
343343
const childName = address.localityPath[0];
344-
const childAddress: LocalitySubchannelAddress = {
344+
const childAddress: LocalityEndpoint = {
345345
...address,
346346
localityPath: address.localityPath.slice(1),
347347
};
348-
let childAddressList = childAddressMap.get(childName);
348+
let childAddressList = childEndpointMap.get(childName);
349349
if (childAddressList === undefined) {
350350
childAddressList = [];
351-
childAddressMap.set(childName, childAddressList);
351+
childEndpointMap.set(childName, childAddressList);
352352
}
353353
childAddressList.push(childAddress);
354354
}
@@ -363,9 +363,9 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
363363
} else {
364364
target.maybeReactivate();
365365
}
366-
const targetAddresses = childAddressMap.get(targetName) ?? [];
367-
trace('Assigning target ' + targetName + ' address list ' + targetAddresses.map(address => '(' + subchannelAddressToString(address) + ' path=' + address.localityPath + ')'));
368-
target.updateAddressList(targetAddresses, targetConfig, attributes);
366+
const targetEndpoints = childEndpointMap.get(targetName) ?? [];
367+
trace('Assigning target ' + targetName + ' address list ' + targetEndpoints.map(endpoint => '(' + endpointToString(endpoint) + ' path=' + endpoint.localityPath + ')'));
368+
target.updateAddressList(targetEndpoints, targetConfig, attributes);
369369
}
370370

371371
// Deactivate targets that are not in the new config

packages/grpc-js-xds/src/load-balancer-xds-cluster-impl.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
import { experimental, logVerbosity, status as Status, Metadata, connectivityState } from "@grpc/grpc-js";
1919
import { validateXdsServerConfig, XdsServerConfig } from "./xds-bootstrap";
2020
import { getSingletonXdsClient, XdsClient, XdsClusterDropStats, XdsClusterLocalityStats } from "./xds-client";
21-
import { LocalitySubchannelAddress } from "./load-balancer-priority";
21+
import { LocalityEndpoint } from "./load-balancer-priority";
2222

2323
import LoadBalancer = experimental.LoadBalancer;
2424
import registerLoadBalancerType = experimental.registerLoadBalancerType;
25-
import SubchannelAddress = experimental.SubchannelAddress;
25+
import Endpoint = experimental.Endpoint;
26+
import endpointHasAddress = experimental.endpointHasAddress;
27+
import subchannelAddressToString = experimental.subchannelAddressToString;
2628
import Picker = experimental.Picker;
2729
import PickArgs = experimental.PickArgs;
2830
import PickResult = experimental.PickResult;
@@ -34,6 +36,7 @@ import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
3436
import selectLbConfigFromList = experimental.selectLbConfigFromList;
3537
import SubchannelInterface = experimental.SubchannelInterface;
3638
import BaseSubchannelWrapper = experimental.BaseSubchannelWrapper;
39+
import { Locality__Output } from "./generated/envoy/config/core/v3/Locality";
3740

3841
const TRACER_NAME = 'xds_cluster_impl';
3942

@@ -245,18 +248,28 @@ function getCallCounterMapKey(cluster: string, edsServiceName?: string): string
245248

246249
class XdsClusterImplBalancer implements LoadBalancer {
247250
private childBalancer: ChildLoadBalancerHandler;
251+
private lastestEndpointList: Endpoint[] | null = null;
248252
private latestConfig: XdsClusterImplLoadBalancingConfig | null = null;
249253
private clusterDropStats: XdsClusterDropStats | null = null;
250254
private xdsClient: XdsClient | null = null;
251255

252256
constructor(private readonly channelControlHelper: ChannelControlHelper) {
253257
this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, {
254258
createSubchannel: (subchannelAddress, subchannelArgs) => {
255-
if (!this.xdsClient || !this.latestConfig) {
259+
if (!this.xdsClient || !this.latestConfig || !this.lastestEndpointList) {
256260
throw new Error('xds_cluster_impl: invalid state: createSubchannel called with xdsClient or latestConfig not populated');
257261
}
258-
const locality = (subchannelAddress as LocalitySubchannelAddress).locality ?? '';
259262
const wrapperChild = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
263+
let locality: Locality__Output | null = null;
264+
for (const endpoint of this.lastestEndpointList) {
265+
if (endpointHasAddress(endpoint, subchannelAddress)) {
266+
locality = (endpoint as LocalityEndpoint).locality;
267+
}
268+
}
269+
if (locality === null) {
270+
trace('Not reporting load for address ' + subchannelAddressToString(subchannelAddress) + ' because it has unknown locality.');
271+
return wrapperChild;
272+
}
260273
const lrsServer = this.latestConfig.getLrsLoadReportingServer();
261274
let statsObj: XdsClusterLocalityStats | null = null;
262275
if (lrsServer) {
@@ -279,15 +292,15 @@ class XdsClusterImplBalancer implements LoadBalancer {
279292
}
280293
}));
281294
}
282-
updateAddressList(addressList: SubchannelAddress[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void {
295+
updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void {
283296
if (!(lbConfig instanceof XdsClusterImplLoadBalancingConfig)) {
284297
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
285298
return;
286299
}
287300
trace('Received update with config: ' + JSON.stringify(lbConfig, undefined, 2));
301+
this.lastestEndpointList = endpointList;
288302
this.latestConfig = lbConfig;
289303
this.xdsClient = attributes.xdsClient as XdsClient;
290-
291304
if (lbConfig.getLrsLoadReportingServer()) {
292305
this.clusterDropStats = this.xdsClient.addClusterDropStats(
293306
lbConfig.getLrsLoadReportingServer()!,
@@ -296,7 +309,7 @@ class XdsClusterImplBalancer implements LoadBalancer {
296309
);
297310
}
298311

299-
this.childBalancer.updateAddressList(addressList, lbConfig.getChildPolicy(), attributes);
312+
this.childBalancer.updateAddressList(endpointList, lbConfig.getChildPolicy(), attributes);
300313
}
301314
exitIdle(): void {
302315
this.childBalancer.exitIdle();

0 commit comments

Comments
 (0)