mirror of https://github.com/grpc/grpc-node.git
grpc-js: Add ChildLoadBalancerHandler and use it in ResolvingLoadBalancer
This commit is contained in:
parent
440d985f1f
commit
90013c695d
|
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 gRPC authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
LoadBalancer,
|
||||||
|
ChannelControlHelper,
|
||||||
|
createLoadBalancer,
|
||||||
|
} from './load-balancer';
|
||||||
|
import {
|
||||||
|
SubchannelAddress,
|
||||||
|
Subchannel,
|
||||||
|
} from './subchannel';
|
||||||
|
import { LoadBalancingConfig } from './load-balancing-config';
|
||||||
|
import { ChannelOptions } from './channel-options';
|
||||||
|
import { ConnectivityState } from './channel';
|
||||||
|
import { Picker } from './picker';
|
||||||
|
|
||||||
|
const TYPE_NAME = 'child_load_balancer_helper';
|
||||||
|
|
||||||
|
export class ChildLoadBalancerHandler implements LoadBalancer {
|
||||||
|
private currentChild: LoadBalancer | null = null;
|
||||||
|
private pendingChild: LoadBalancer | null = null;
|
||||||
|
|
||||||
|
private ChildPolicyHelper = class {
|
||||||
|
private child: LoadBalancer | null = null;
|
||||||
|
constructor(private parent: ChildLoadBalancerHandler) {}
|
||||||
|
createSubchannel(subchannelAddress: SubchannelAddress, subchannelArgs: ChannelOptions): Subchannel {
|
||||||
|
return this.parent.channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
|
||||||
|
}
|
||||||
|
updateState(connectivityState: ConnectivityState, picker: Picker): void {
|
||||||
|
if (this.calledByPendingChild()) {
|
||||||
|
if (connectivityState !== ConnectivityState.READY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.parent.currentChild?.destroy();
|
||||||
|
this.parent.currentChild = this.parent.pendingChild;
|
||||||
|
this.parent.pendingChild = null;
|
||||||
|
} else if (!this.calledByCurrentChild()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.parent.channelControlHelper.updateState(connectivityState, picker);
|
||||||
|
}
|
||||||
|
requestReresolution(): void {
|
||||||
|
const latestChild = this.parent.pendingChild ?? this.parent.currentChild;
|
||||||
|
if (this.child === latestChild) {
|
||||||
|
this.parent.channelControlHelper.requestReresolution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setChild(newChild: LoadBalancer) {
|
||||||
|
this.child = newChild;
|
||||||
|
}
|
||||||
|
private calledByPendingChild(): boolean {
|
||||||
|
return this.child === this.parent.pendingChild;
|
||||||
|
}
|
||||||
|
private calledByCurrentChild(): boolean {
|
||||||
|
return this.child === this.parent.currentChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly channelControlHelper: ChannelControlHelper) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prerequisites: lbConfig !== null and lbConfig.name is registered
|
||||||
|
* @param addressList
|
||||||
|
* @param lbConfig
|
||||||
|
* @param attributes
|
||||||
|
*/
|
||||||
|
updateAddressList(addressList: SubchannelAddress[], lbConfig: LoadBalancingConfig | null, attributes: { [key: string]: unknown; }): void {
|
||||||
|
if (lbConfig === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let childToUpdate: LoadBalancer;
|
||||||
|
if (this.currentChild === null || this.currentChild.getTypeName() !== lbConfig.name) {
|
||||||
|
const newHelper = new this.ChildPolicyHelper(this);
|
||||||
|
const newChild = createLoadBalancer(lbConfig.name, newHelper)!;
|
||||||
|
newHelper.setChild(newChild);
|
||||||
|
if (this.currentChild === null) {
|
||||||
|
this.currentChild = newChild;
|
||||||
|
childToUpdate = this.currentChild;
|
||||||
|
} else {
|
||||||
|
if (this.pendingChild) {
|
||||||
|
this.pendingChild.destroy();
|
||||||
|
}
|
||||||
|
this.pendingChild = newChild;
|
||||||
|
childToUpdate = this.pendingChild;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.pendingChild === null) {
|
||||||
|
childToUpdate = this.currentChild;
|
||||||
|
} else {
|
||||||
|
childToUpdate = this.pendingChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
childToUpdate.updateAddressList(addressList, lbConfig, attributes);
|
||||||
|
}
|
||||||
|
exitIdle(): void {
|
||||||
|
if (this.currentChild) {
|
||||||
|
this.currentChild.resetBackoff();
|
||||||
|
if (this.pendingChild) {
|
||||||
|
this.pendingChild.resetBackoff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetBackoff(): void {
|
||||||
|
if (this.currentChild) {
|
||||||
|
this.currentChild.resetBackoff();
|
||||||
|
if (this.pendingChild) {
|
||||||
|
this.pendingChild.resetBackoff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destroy(): void {
|
||||||
|
if (this.currentChild) {
|
||||||
|
this.currentChild.destroy();
|
||||||
|
}
|
||||||
|
if (this.pendingChild) {
|
||||||
|
this.pendingChild.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getTypeName(): string {
|
||||||
|
return TYPE_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -129,7 +129,7 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
||||||
* @param channelControlHelper `ChannelControlHelper` instance provided by
|
* @param channelControlHelper `ChannelControlHelper` instance provided by
|
||||||
* this load balancer's owner.
|
* this load balancer's owner.
|
||||||
*/
|
*/
|
||||||
constructor(private channelControlHelper: ChannelControlHelper) {
|
constructor(private readonly channelControlHelper: ChannelControlHelper) {
|
||||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||||
this.subchannelStateCounts = {
|
this.subchannelStateCounts = {
|
||||||
[ConnectivityState.CONNECTING]: 0,
|
[ConnectivityState.CONNECTING]: 0,
|
||||||
|
|
@ -435,10 +435,6 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
||||||
getTypeName(): string {
|
getTypeName(): string {
|
||||||
return TYPE_NAME;
|
return TYPE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceChannelControlHelper(channelControlHelper: ChannelControlHelper) {
|
|
||||||
this.channelControlHelper = channelControlHelper;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setup(): void {
|
export function setup(): void {
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
|
||||||
|
|
||||||
private currentReadyPicker: RoundRobinPicker | null = null;
|
private currentReadyPicker: RoundRobinPicker | null = null;
|
||||||
|
|
||||||
constructor(private channelControlHelper: ChannelControlHelper) {
|
constructor(private readonly channelControlHelper: ChannelControlHelper) {
|
||||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||||
this.subchannelStateCounts = {
|
this.subchannelStateCounts = {
|
||||||
[ConnectivityState.CONNECTING]: 0,
|
[ConnectivityState.CONNECTING]: 0,
|
||||||
|
|
@ -229,11 +229,6 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
|
||||||
getTypeName(): string {
|
getTypeName(): string {
|
||||||
return TYPE_NAME;
|
return TYPE_NAME;
|
||||||
}
|
}
|
||||||
replaceChannelControlHelper(
|
|
||||||
channelControlHelper: ChannelControlHelper
|
|
||||||
): void {
|
|
||||||
this.channelControlHelper = channelControlHelper;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setup() {
|
export function setup() {
|
||||||
|
|
|
||||||
|
|
@ -91,11 +91,6 @@ export interface LoadBalancer {
|
||||||
* balancer implementation class was registered with.
|
* balancer implementation class was registered with.
|
||||||
*/
|
*/
|
||||||
getTypeName(): string;
|
getTypeName(): string;
|
||||||
/**
|
|
||||||
* Replace the existing ChannelControlHelper with a new one
|
|
||||||
* @param channelControlHelper The new ChannelControlHelper to use from now on
|
|
||||||
*/
|
|
||||||
replaceChannelControlHelper(channelControlHelper: ChannelControlHelper): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadBalancerConstructor {
|
export interface LoadBalancerConstructor {
|
||||||
|
|
@ -128,6 +123,15 @@ export function isLoadBalancerNameRegistered(typeName: string): boolean {
|
||||||
return typeName in registeredLoadBalancerTypes;
|
return typeName in registeredLoadBalancerTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFirstUsableConfig(configs: LoadBalancingConfig[]): LoadBalancingConfig | null {
|
||||||
|
for (const config of configs) {
|
||||||
|
if (config.name in registeredLoadBalancerTypes) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function registerAll() {
|
export function registerAll() {
|
||||||
load_balancer_pick_first.setup();
|
load_balancer_pick_first.setup();
|
||||||
load_balancer_round_robin.setup();
|
load_balancer_round_robin.setup();
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
* runtime */
|
* runtime */
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
export type PickFirstConfig = {};
|
||||||
|
|
||||||
export type RoundRobinConfig = {};
|
export type RoundRobinConfig = {};
|
||||||
|
|
||||||
export interface XdsConfig {
|
export interface XdsConfig {
|
||||||
|
|
@ -37,11 +39,56 @@ export interface GrpcLbConfig {
|
||||||
childPolicy: LoadBalancingConfig[];
|
childPolicy: LoadBalancingConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadBalancingConfig {
|
export interface PriorityChild {
|
||||||
/* Exactly one of these must be set for a config to be valid */
|
config: LoadBalancingConfig[];
|
||||||
round_robin?: RoundRobinConfig;
|
}
|
||||||
xds?: XdsConfig;
|
|
||||||
grpclb?: GrpcLbConfig;
|
export interface PriorityLbConfig {
|
||||||
|
children: Map<string, PriorityChild>;
|
||||||
|
priorities: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PickFirstLoadBalancingConfig {
|
||||||
|
name: 'pick_first';
|
||||||
|
pick_first: PickFirstConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoundRobinLoadBalancingConfig {
|
||||||
|
name: 'round_robin';
|
||||||
|
round_robin: RoundRobinConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface XdsLoadBalancingConfig {
|
||||||
|
name: 'xds';
|
||||||
|
xds: XdsConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrpcLbLoadBalancingConfig {
|
||||||
|
name: 'grpclb';
|
||||||
|
grpclb: GrpcLbConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PriorityLoadBalancingConfig {
|
||||||
|
name: 'priority';
|
||||||
|
priority: PriorityLbConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoadBalancingConfig = PickFirstLoadBalancingConfig | RoundRobinLoadBalancingConfig | XdsLoadBalancingConfig | GrpcLbLoadBalancingConfig | PriorityLoadBalancingConfig;
|
||||||
|
|
||||||
|
export function isRoundRobinLoadBalancingConfig(lbconfig: LoadBalancingConfig): lbconfig is RoundRobinLoadBalancingConfig {
|
||||||
|
return lbconfig.name === 'round_robin';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isXdsLoadBalancingConfig(lbconfig: LoadBalancingConfig): lbconfig is XdsLoadBalancingConfig {
|
||||||
|
return lbconfig.name === 'xds';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGrpcLbLoadBalancingConfig(lbconfig: LoadBalancingConfig): lbconfig is GrpcLbLoadBalancingConfig {
|
||||||
|
return lbconfig.name === 'grpclb';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPriorityLoadBalancingConfig(lbconfig: LoadBalancingConfig): lbconfig is PriorityLoadBalancingConfig {
|
||||||
|
return lbconfig.name === 'priority';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* In these functions we assume the input came from a JSON object. Therefore we
|
/* In these functions we assume the input came from a JSON object. Therefore we
|
||||||
|
|
@ -97,17 +144,26 @@ export function validateConfig(obj: any): LoadBalancingConfig {
|
||||||
throw new Error('Multiple load balancing policies configured');
|
throw new Error('Multiple load balancing policies configured');
|
||||||
}
|
}
|
||||||
if (obj['round_robin'] instanceof Object) {
|
if (obj['round_robin'] instanceof Object) {
|
||||||
return { round_robin: {} };
|
return {
|
||||||
|
name: 'round_robin',
|
||||||
|
round_robin: {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ('xds' in obj) {
|
if ('xds' in obj) {
|
||||||
if ('grpclb' in obj) {
|
if ('grpclb' in obj) {
|
||||||
throw new Error('Multiple load balancing policies configured');
|
throw new Error('Multiple load balancing policies configured');
|
||||||
}
|
}
|
||||||
return { xds: validateXdsConfig(obj.xds) };
|
return {
|
||||||
|
name: 'xds',
|
||||||
|
xds: validateXdsConfig(obj.xds)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if ('grpclb' in obj) {
|
if ('grpclb' in obj) {
|
||||||
return { grpclb: validateGrpcLbConfig(obj.grpclb) };
|
return {
|
||||||
|
name: 'grpclb',
|
||||||
|
grpclb: validateGrpcLbConfig(obj.grpclb)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
throw new Error('No recognized load balancing policy configured');
|
throw new Error('No recognized load balancing policy configured');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,12 @@
|
||||||
import {
|
import {
|
||||||
ChannelControlHelper,
|
ChannelControlHelper,
|
||||||
LoadBalancer,
|
LoadBalancer,
|
||||||
isLoadBalancerNameRegistered,
|
getFirstUsableConfig,
|
||||||
createLoadBalancer,
|
|
||||||
} from './load-balancer';
|
} from './load-balancer';
|
||||||
import { ServiceConfig } from './service-config';
|
import { ServiceConfig } from './service-config';
|
||||||
import { ConnectivityState } from './channel';
|
import { ConnectivityState } from './channel';
|
||||||
import { createResolver, Resolver } from './resolver';
|
import { createResolver, Resolver } from './resolver';
|
||||||
import { ServiceError } from './call';
|
import { ServiceError } from './call';
|
||||||
import { ChannelOptions } from './channel-options';
|
|
||||||
import { Picker, UnavailablePicker, QueuePicker } from './picker';
|
import { Picker, UnavailablePicker, QueuePicker } from './picker';
|
||||||
import { LoadBalancingConfig } from './load-balancing-config';
|
import { LoadBalancingConfig } from './load-balancing-config';
|
||||||
import { BackoffTimeout } from './backoff-timeout';
|
import { BackoffTimeout } from './backoff-timeout';
|
||||||
|
|
@ -36,6 +34,7 @@ import * as logging from './logging';
|
||||||
import { LogVerbosity } from './constants';
|
import { LogVerbosity } from './constants';
|
||||||
import { SubchannelAddress } from './subchannel';
|
import { SubchannelAddress } from './subchannel';
|
||||||
import { GrpcUri, uriToString } from './uri-parser';
|
import { GrpcUri, uriToString } from './uri-parser';
|
||||||
|
import { ChildLoadBalancerHandler } from './load-balancer-child-handler';
|
||||||
|
|
||||||
const TRACER_NAME = 'resolving_load_balancer';
|
const TRACER_NAME = 'resolving_load_balancer';
|
||||||
|
|
||||||
|
|
@ -50,19 +49,8 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
||||||
* The resolver class constructed for the target address.
|
* The resolver class constructed for the target address.
|
||||||
*/
|
*/
|
||||||
private innerResolver: Resolver;
|
private innerResolver: Resolver;
|
||||||
/**
|
|
||||||
* Current internal load balancer used for handling calls.
|
private childLoadBalancer: ChildLoadBalancerHandler;
|
||||||
* Invariant: innerLoadBalancer === null => pendingReplacementLoadBalancer === null.
|
|
||||||
*/
|
|
||||||
private innerLoadBalancer: LoadBalancer | null = null;
|
|
||||||
/**
|
|
||||||
* The load balancer instance that will be used in place of the current
|
|
||||||
* `innerLoadBalancer` once either that load balancer loses its connection
|
|
||||||
* or this one establishes a connection. For use when a new name resolution
|
|
||||||
* result comes in with a different load balancing configuration, and the
|
|
||||||
* current `innerLoadBalancer` is still connected.
|
|
||||||
*/
|
|
||||||
private pendingReplacementLoadBalancer: LoadBalancer | null = null;
|
|
||||||
/**
|
/**
|
||||||
* This resolving load balancer's current connectivity state.
|
* This resolving load balancer's current connectivity state.
|
||||||
*/
|
*/
|
||||||
|
|
@ -74,34 +62,6 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
||||||
* successful resolution explicitly provided a null service config.
|
* successful resolution explicitly provided a null service config.
|
||||||
*/
|
*/
|
||||||
private previousServiceConfig: ServiceConfig | null | undefined = undefined;
|
private previousServiceConfig: ServiceConfig | null | undefined = undefined;
|
||||||
/**
|
|
||||||
* The most recently reported connectivity state of the `innerLoadBalancer`.
|
|
||||||
*/
|
|
||||||
private innerBalancerState: ConnectivityState = ConnectivityState.IDLE;
|
|
||||||
|
|
||||||
private innerBalancerPicker: Picker = new UnavailablePicker();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The most recent reported state of the pendingReplacementLoadBalancer.
|
|
||||||
* Starts at IDLE for type simplicity. This should get updated as soon as the
|
|
||||||
* pendingReplacementLoadBalancer gets constructed.
|
|
||||||
*/
|
|
||||||
private replacementBalancerState: ConnectivityState = ConnectivityState.IDLE;
|
|
||||||
/**
|
|
||||||
* The picker associated with the replacementBalancerState. Starts as an
|
|
||||||
* UnavailablePicker for type simplicity. This should get updated as soon as
|
|
||||||
* the pendingReplacementLoadBalancer gets constructed.
|
|
||||||
*/
|
|
||||||
private replacementBalancerPicker: Picker = new UnavailablePicker();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ChannelControlHelper for the innerLoadBalancer.
|
|
||||||
*/
|
|
||||||
private readonly innerChannelControlHelper: ChannelControlHelper;
|
|
||||||
/**
|
|
||||||
* ChannelControlHelper for the pendingReplacementLoadBalancer.
|
|
||||||
*/
|
|
||||||
private readonly replacementChannelControlHelper: ChannelControlHelper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The backoff timer for handling name resolution failures.
|
* The backoff timer for handling name resolution failures.
|
||||||
|
|
@ -127,11 +87,28 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
||||||
* implmentation
|
* implmentation
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private target: GrpcUri,
|
private readonly target: GrpcUri,
|
||||||
private channelControlHelper: ChannelControlHelper,
|
private readonly channelControlHelper: ChannelControlHelper,
|
||||||
private defaultServiceConfig: ServiceConfig | null
|
private readonly defaultServiceConfig: ServiceConfig | null
|
||||||
) {
|
) {
|
||||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||||
|
this.childLoadBalancer = new ChildLoadBalancerHandler({
|
||||||
|
createSubchannel: channelControlHelper.createSubchannel.bind(channelControlHelper),
|
||||||
|
requestReresolution: () => {
|
||||||
|
/* If the backoffTimeout is running, we're still backing off from
|
||||||
|
* making resolve requests, so we shouldn't make another one here.
|
||||||
|
* In that case, the backoff timer callback will call
|
||||||
|
* updateResolution */
|
||||||
|
if (this.backoffTimeout.isRunning()) {
|
||||||
|
this.continueResolving = true;
|
||||||
|
} else {
|
||||||
|
this.updateResolution();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateState: (newState: ConnectivityState, picker: Picker) => {
|
||||||
|
this.updateState(newState, picker);
|
||||||
|
}
|
||||||
|
});
|
||||||
this.innerResolver = createResolver(target, {
|
this.innerResolver = createResolver(target, {
|
||||||
onSuccessfulResolution: (
|
onSuccessfulResolution: (
|
||||||
addressList: SubchannelAddress[],
|
addressList: SubchannelAddress[],
|
||||||
|
|
@ -171,30 +148,15 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
||||||
workingServiceConfig = serviceConfig;
|
workingServiceConfig = serviceConfig;
|
||||||
this.previousServiceConfig = serviceConfig;
|
this.previousServiceConfig = serviceConfig;
|
||||||
}
|
}
|
||||||
let loadBalancerName: string | null = null;
|
const workingConfigList = workingServiceConfig?.loadBalancingConfig ?? [];
|
||||||
let loadBalancingConfig: LoadBalancingConfig | null = null;
|
if (workingConfigList.length === 0) {
|
||||||
if (
|
workingConfigList.push({
|
||||||
workingServiceConfig === null ||
|
name: 'pick_first',
|
||||||
workingServiceConfig.loadBalancingConfig.length === 0
|
pick_first: {}
|
||||||
) {
|
});
|
||||||
loadBalancerName = DEFAULT_LOAD_BALANCER_NAME;
|
|
||||||
} else {
|
|
||||||
for (const lbConfig of workingServiceConfig.loadBalancingConfig) {
|
|
||||||
// Iterating through a oneof looking for whichever one is populated
|
|
||||||
for (const key in lbConfig) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(lbConfig, key)) {
|
|
||||||
if (isLoadBalancerNameRegistered(key)) {
|
|
||||||
loadBalancerName = key;
|
|
||||||
loadBalancingConfig = lbConfig;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
const loadBalancingConfig = getFirstUsableConfig(workingConfigList);
|
||||||
}
|
if (loadBalancingConfig === null) {
|
||||||
if (loadBalancerName !== null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (loadBalancerName === null) {
|
|
||||||
// There were load balancing configs but none are supported. This counts as a resolution failure
|
// There were load balancing configs but none are supported. This counts as a resolution failure
|
||||||
this.handleResolutionFailure({
|
this.handleResolutionFailure({
|
||||||
code: Status.UNAVAILABLE,
|
code: Status.UNAVAILABLE,
|
||||||
|
|
@ -204,194 +166,46 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
this.childLoadBalancer.updateAddressList(addressList, loadBalancingConfig, attributes);
|
||||||
if (this.innerLoadBalancer === null) {
|
|
||||||
this.innerLoadBalancer = createLoadBalancer(
|
|
||||||
loadBalancerName,
|
|
||||||
this.innerChannelControlHelper
|
|
||||||
)!;
|
|
||||||
this.innerLoadBalancer.updateAddressList(
|
|
||||||
addressList,
|
|
||||||
loadBalancingConfig,
|
|
||||||
attributes
|
|
||||||
);
|
|
||||||
} else if (this.innerLoadBalancer.getTypeName() === loadBalancerName) {
|
|
||||||
this.innerLoadBalancer.updateAddressList(
|
|
||||||
addressList,
|
|
||||||
loadBalancingConfig,
|
|
||||||
attributes
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
this.pendingReplacementLoadBalancer === null ||
|
|
||||||
this.pendingReplacementLoadBalancer.getTypeName() !==
|
|
||||||
loadBalancerName
|
|
||||||
) {
|
|
||||||
if (this.pendingReplacementLoadBalancer !== null) {
|
|
||||||
this.pendingReplacementLoadBalancer.destroy();
|
|
||||||
}
|
|
||||||
this.pendingReplacementLoadBalancer = createLoadBalancer(
|
|
||||||
loadBalancerName,
|
|
||||||
this.replacementChannelControlHelper
|
|
||||||
)!;
|
|
||||||
}
|
|
||||||
this.pendingReplacementLoadBalancer.updateAddressList(
|
|
||||||
addressList,
|
|
||||||
loadBalancingConfig,
|
|
||||||
attributes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError: (error: StatusObject) => {
|
onError: (error: StatusObject) => {
|
||||||
this.handleResolutionFailure(error);
|
this.handleResolutionFailure(error);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.innerChannelControlHelper = {
|
|
||||||
createSubchannel: (
|
|
||||||
subchannelAddress: SubchannelAddress,
|
|
||||||
subchannelArgs: ChannelOptions
|
|
||||||
) => {
|
|
||||||
return this.channelControlHelper.createSubchannel(
|
|
||||||
subchannelAddress,
|
|
||||||
subchannelArgs
|
|
||||||
);
|
|
||||||
},
|
|
||||||
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
|
|
||||||
this.innerBalancerState = connectivityState;
|
|
||||||
if (connectivityState === ConnectivityState.IDLE) {
|
|
||||||
picker = new QueuePicker(this);
|
|
||||||
}
|
|
||||||
this.innerBalancerPicker = picker;
|
|
||||||
if (
|
|
||||||
connectivityState !== ConnectivityState.READY &&
|
|
||||||
this.pendingReplacementLoadBalancer !== null
|
|
||||||
) {
|
|
||||||
this.switchOverReplacementBalancer();
|
|
||||||
} else {
|
|
||||||
if (connectivityState === ConnectivityState.IDLE) {
|
|
||||||
if (this.innerLoadBalancer) {
|
|
||||||
this.innerLoadBalancer.destroy();
|
|
||||||
this.innerLoadBalancer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updateState(connectivityState, picker);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
requestReresolution: () => {
|
|
||||||
if (this.pendingReplacementLoadBalancer === null) {
|
|
||||||
/* If the backoffTimeout is running, we're still backing off from
|
|
||||||
* making resolve requests, so we shouldn't make another one here.
|
|
||||||
* In that case, the backoff timer callback will call
|
|
||||||
* updateResolution */
|
|
||||||
if (this.backoffTimeout.isRunning()) {
|
|
||||||
this.continueResolving = true;
|
|
||||||
} else {
|
|
||||||
this.updateResolution();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.replacementChannelControlHelper = {
|
|
||||||
createSubchannel: (
|
|
||||||
subchannelAddress: SubchannelAddress,
|
|
||||||
subchannelArgs: ChannelOptions
|
|
||||||
) => {
|
|
||||||
return this.channelControlHelper.createSubchannel(
|
|
||||||
subchannelAddress,
|
|
||||||
subchannelArgs
|
|
||||||
);
|
|
||||||
},
|
|
||||||
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
|
|
||||||
if (connectivityState === ConnectivityState.IDLE) {
|
|
||||||
picker = new QueuePicker(this);
|
|
||||||
}
|
|
||||||
this.replacementBalancerState = connectivityState;
|
|
||||||
this.replacementBalancerPicker = picker;
|
|
||||||
if (connectivityState === ConnectivityState.READY) {
|
|
||||||
this.switchOverReplacementBalancer();
|
|
||||||
} else if (connectivityState === ConnectivityState.IDLE) {
|
|
||||||
if (this.pendingReplacementLoadBalancer) {
|
|
||||||
this.pendingReplacementLoadBalancer.destroy();
|
|
||||||
this.pendingReplacementLoadBalancer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
requestReresolution: () => {
|
|
||||||
/* If the backoffTimeout is running, we're still backing off from
|
|
||||||
* making resolve requests, so we shouldn't make another one here.
|
|
||||||
* In that case, the backoff timer callback will call
|
|
||||||
* updateResolution */
|
|
||||||
if (this.backoffTimeout.isRunning()) {
|
|
||||||
this.continueResolving = true;
|
|
||||||
} else {
|
|
||||||
this.updateResolution();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.backoffTimeout = new BackoffTimeout(() => {
|
this.backoffTimeout = new BackoffTimeout(() => {
|
||||||
if (this.continueResolving) {
|
if (this.continueResolving) {
|
||||||
this.updateResolution();
|
this.updateResolution();
|
||||||
this.continueResolving = false;
|
this.continueResolving = false;
|
||||||
} else {
|
|
||||||
if (this.innerLoadBalancer === null) {
|
|
||||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
|
||||||
} else {
|
|
||||||
this.updateState(this.innerBalancerState, this.innerBalancerPicker);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateResolution() {
|
private updateResolution() {
|
||||||
this.innerResolver.updateResolution();
|
this.innerResolver.updateResolution();
|
||||||
if (
|
if (this.currentState === ConnectivityState.IDLE) {
|
||||||
this.innerLoadBalancer === null ||
|
|
||||||
this.innerBalancerState === ConnectivityState.IDLE
|
|
||||||
) {
|
|
||||||
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateState(connectivitystate: ConnectivityState, picker: Picker) {
|
private updateState(connectivityState: ConnectivityState, picker: Picker) {
|
||||||
trace(
|
trace(
|
||||||
uriToString(this.target) +
|
uriToString(this.target) +
|
||||||
' ' +
|
' ' +
|
||||||
ConnectivityState[this.currentState] +
|
ConnectivityState[this.currentState] +
|
||||||
' -> ' +
|
' -> ' +
|
||||||
ConnectivityState[connectivitystate]
|
ConnectivityState[connectivityState]
|
||||||
);
|
);
|
||||||
this.currentState = connectivitystate;
|
// Ensure that this.exitIdle() is called by the picker
|
||||||
this.channelControlHelper.updateState(connectivitystate, picker);
|
if (connectivityState === ConnectivityState.IDLE) {
|
||||||
|
picker = new QueuePicker(this);
|
||||||
}
|
}
|
||||||
|
this.currentState = connectivityState;
|
||||||
/**
|
this.channelControlHelper.updateState(connectivityState, picker);
|
||||||
* Stop using the current innerLoadBalancer and replace it with the
|
|
||||||
* pendingReplacementLoadBalancer. Must only be called if both of
|
|
||||||
* those are currently not null.
|
|
||||||
*/
|
|
||||||
private switchOverReplacementBalancer() {
|
|
||||||
this.innerLoadBalancer!.destroy();
|
|
||||||
this.innerLoadBalancer = this.pendingReplacementLoadBalancer!;
|
|
||||||
this.innerLoadBalancer.replaceChannelControlHelper(
|
|
||||||
this.innerChannelControlHelper
|
|
||||||
);
|
|
||||||
this.pendingReplacementLoadBalancer = null;
|
|
||||||
this.innerBalancerState = this.replacementBalancerState;
|
|
||||||
this.innerBalancerPicker = this.replacementBalancerPicker;
|
|
||||||
this.updateState(
|
|
||||||
this.replacementBalancerState,
|
|
||||||
this.replacementBalancerPicker
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleResolutionFailure(error: StatusObject) {
|
private handleResolutionFailure(error: StatusObject) {
|
||||||
if (
|
if (this.currentState === ConnectivityState.IDLE) {
|
||||||
this.innerLoadBalancer === null ||
|
|
||||||
this.innerBalancerState === ConnectivityState.IDLE
|
|
||||||
) {
|
|
||||||
this.updateState(
|
this.updateState(
|
||||||
ConnectivityState.TRANSIENT_FAILURE,
|
ConnectivityState.TRANSIENT_FAILURE,
|
||||||
new UnavailablePicker(error)
|
new UnavailablePicker(error)
|
||||||
|
|
@ -401,9 +215,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIdle() {
|
exitIdle() {
|
||||||
if (this.innerLoadBalancer !== null) {
|
this.childLoadBalancer.exitIdle();
|
||||||
this.innerLoadBalancer.exitIdle();
|
|
||||||
}
|
|
||||||
if (this.currentState === ConnectivityState.IDLE) {
|
if (this.currentState === ConnectivityState.IDLE) {
|
||||||
if (this.backoffTimeout.isRunning()) {
|
if (this.backoffTimeout.isRunning()) {
|
||||||
this.continueResolving = true;
|
this.continueResolving = true;
|
||||||
|
|
@ -423,31 +235,15 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
||||||
|
|
||||||
resetBackoff() {
|
resetBackoff() {
|
||||||
this.backoffTimeout.reset();
|
this.backoffTimeout.reset();
|
||||||
if (this.innerLoadBalancer !== null) {
|
this.childLoadBalancer.resetBackoff();
|
||||||
this.innerLoadBalancer.resetBackoff();
|
|
||||||
}
|
|
||||||
if (this.pendingReplacementLoadBalancer !== null) {
|
|
||||||
this.pendingReplacementLoadBalancer.resetBackoff();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.innerLoadBalancer !== null) {
|
this.childLoadBalancer.destroy();
|
||||||
this.innerLoadBalancer.destroy();
|
|
||||||
this.innerLoadBalancer = null;
|
|
||||||
}
|
|
||||||
if (this.pendingReplacementLoadBalancer !== null) {
|
|
||||||
this.pendingReplacementLoadBalancer.destroy();
|
|
||||||
this.pendingReplacementLoadBalancer = null;
|
|
||||||
}
|
|
||||||
this.updateState(ConnectivityState.SHUTDOWN, new UnavailablePicker());
|
this.updateState(ConnectivityState.SHUTDOWN, new UnavailablePicker());
|
||||||
}
|
}
|
||||||
|
|
||||||
getTypeName() {
|
getTypeName() {
|
||||||
return 'resolving_load_balancer';
|
return 'resolving_load_balancer';
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceChannelControlHelper(channelControlHelper: ChannelControlHelper) {
|
|
||||||
this.channelControlHelper = channelControlHelper;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue