UNPKG

7.96 kBPlain TextView Raw
1/*
2 * Copyright 2019 gRPC authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18import { ChannelOptions } from './channel-options';
19import { Endpoint, SubchannelAddress } from './subchannel-address';
20import { ConnectivityState } from './connectivity-state';
21import { Picker } from './picker';
22import type { ChannelRef, SubchannelRef } from './channelz';
23import { SubchannelInterface } from './subchannel-interface';
24import { LoadBalancingConfig } from './service-config';
25import { log } from './logging';
26import { LogVerbosity } from './constants';
27
28/**
29 * A collection of functions associated with a channel that a load balancer
30 * can call as necessary.
31 */
32export interface ChannelControlHelper {
33 /**
34 * Returns a subchannel connected to the specified address.
35 * @param subchannelAddress The address to connect to
36 * @param subchannelArgs Extra channel arguments specified by the load balancer
37 */
38 createSubchannel(
39 subchannelAddress: SubchannelAddress,
40 subchannelArgs: ChannelOptions
41 ): SubchannelInterface;
42 /**
43 * Passes a new subchannel picker up to the channel. This is called if either
44 * the connectivity state changes or if a different picker is needed for any
45 * other reason.
46 * @param connectivityState New connectivity state
47 * @param picker New picker
48 */
49 updateState(connectivityState: ConnectivityState, picker: Picker): void;
50 /**
51 * Request new data from the resolver.
52 */
53 requestReresolution(): void;
54 addChannelzChild(child: ChannelRef | SubchannelRef): void;
55 removeChannelzChild(child: ChannelRef | SubchannelRef): void;
56}
57
58/**
59 * Create a child ChannelControlHelper that overrides some methods of the
60 * parent while letting others pass through to the parent unmodified. This
61 * allows other code to create these children without needing to know about
62 * all of the methods to be passed through.
63 * @param parent
64 * @param overrides
65 */
66export function createChildChannelControlHelper(
67 parent: ChannelControlHelper,
68 overrides: Partial<ChannelControlHelper>
69): ChannelControlHelper {
70 return {
71 createSubchannel:
72 overrides.createSubchannel?.bind(overrides) ??
73 parent.createSubchannel.bind(parent),
74 updateState:
75 overrides.updateState?.bind(overrides) ?? parent.updateState.bind(parent),
76 requestReresolution:
77 overrides.requestReresolution?.bind(overrides) ??
78 parent.requestReresolution.bind(parent),
79 addChannelzChild:
80 overrides.addChannelzChild?.bind(overrides) ??
81 parent.addChannelzChild.bind(parent),
82 removeChannelzChild:
83 overrides.removeChannelzChild?.bind(overrides) ??
84 parent.removeChannelzChild.bind(parent),
85 };
86}
87
88/**
89 * Tracks one or more connected subchannels and determines which subchannel
90 * each request should use.
91 */
92export interface LoadBalancer {
93 /**
94 * Gives the load balancer a new list of addresses to start connecting to.
95 * The load balancer will start establishing connections with the new list,
96 * but will continue using any existing connections until the new connections
97 * are established
98 * @param endpointList The new list of addresses to connect to
99 * @param lbConfig The load balancing config object from the service config,
100 * if one was provided
101 */
102 updateAddressList(
103 endpointList: Endpoint[],
104 lbConfig: TypedLoadBalancingConfig,
105 attributes: { [key: string]: unknown }
106 ): void;
107 /**
108 * If the load balancer is currently in the IDLE state, start connecting.
109 */
110 exitIdle(): void;
111 /**
112 * If the load balancer is currently in the CONNECTING or TRANSIENT_FAILURE
113 * state, reset the current connection backoff timeout to its base value and
114 * transition to CONNECTING if in TRANSIENT_FAILURE.
115 */
116 resetBackoff(): void;
117 /**
118 * The load balancer unrefs all of its subchannels and stops calling methods
119 * of its channel control helper.
120 */
121 destroy(): void;
122 /**
123 * Get the type name for this load balancer type. Must be constant across an
124 * entire load balancer implementation class and must match the name that the
125 * balancer implementation class was registered with.
126 */
127 getTypeName(): string;
128}
129
130export interface LoadBalancerConstructor {
131 new (
132 channelControlHelper: ChannelControlHelper,
133 options: ChannelOptions
134 ): LoadBalancer;
135}
136
137export interface TypedLoadBalancingConfig {
138 getLoadBalancerName(): string;
139 toJsonObject(): object;
140}
141
142export interface TypedLoadBalancingConfigConstructor {
143 // eslint-disable-next-line @typescript-eslint/no-explicit-any
144 new (...args: any): TypedLoadBalancingConfig;
145 // eslint-disable-next-line @typescript-eslint/no-explicit-any
146 createFromJson(obj: any): TypedLoadBalancingConfig;
147}
148
149const registeredLoadBalancerTypes: {
150 [name: string]: {
151 LoadBalancer: LoadBalancerConstructor;
152 LoadBalancingConfig: TypedLoadBalancingConfigConstructor;
153 };
154} = {};
155
156let defaultLoadBalancerType: string | null = null;
157
158export function registerLoadBalancerType(
159 typeName: string,
160 loadBalancerType: LoadBalancerConstructor,
161 loadBalancingConfigType: TypedLoadBalancingConfigConstructor
162) {
163 registeredLoadBalancerTypes[typeName] = {
164 LoadBalancer: loadBalancerType,
165 LoadBalancingConfig: loadBalancingConfigType,
166 };
167}
168
169export function registerDefaultLoadBalancerType(typeName: string) {
170 defaultLoadBalancerType = typeName;
171}
172
173export function createLoadBalancer(
174 config: TypedLoadBalancingConfig,
175 channelControlHelper: ChannelControlHelper,
176 options: ChannelOptions
177): LoadBalancer | null {
178 const typeName = config.getLoadBalancerName();
179 if (typeName in registeredLoadBalancerTypes) {
180 return new registeredLoadBalancerTypes[typeName].LoadBalancer(
181 channelControlHelper,
182 options
183 );
184 } else {
185 return null;
186 }
187}
188
189export function isLoadBalancerNameRegistered(typeName: string): boolean {
190 return typeName in registeredLoadBalancerTypes;
191}
192
193export function parseLoadBalancingConfig(
194 rawConfig: LoadBalancingConfig
195): TypedLoadBalancingConfig {
196 const keys = Object.keys(rawConfig);
197 if (keys.length !== 1) {
198 throw new Error(
199 'Provided load balancing config has multiple conflicting entries'
200 );
201 }
202 const typeName = keys[0];
203 if (typeName in registeredLoadBalancerTypes) {
204 try {
205 return registeredLoadBalancerTypes[
206 typeName
207 ].LoadBalancingConfig.createFromJson(rawConfig[typeName]);
208 } catch (e) {
209 throw new Error(`${typeName}: ${(e as Error).message}`);
210 }
211 } else {
212 throw new Error(`Unrecognized load balancing config name ${typeName}`);
213 }
214}
215
216export function getDefaultConfig() {
217 if (!defaultLoadBalancerType) {
218 throw new Error('No default load balancer type registered');
219 }
220 return new registeredLoadBalancerTypes[
221 defaultLoadBalancerType
222 ]!.LoadBalancingConfig();
223}
224
225export function selectLbConfigFromList(
226 configs: LoadBalancingConfig[],
227 fallbackTodefault = false
228): TypedLoadBalancingConfig | null {
229 for (const config of configs) {
230 try {
231 return parseLoadBalancingConfig(config);
232 } catch (e) {
233 log(
234 LogVerbosity.DEBUG,
235 'Config parsing failed with error',
236 (e as Error).message
237 );
238 continue;
239 }
240 }
241 if (fallbackTodefault) {
242 if (defaultLoadBalancerType) {
243 return new registeredLoadBalancerTypes[
244 defaultLoadBalancerType
245 ]!.LoadBalancingConfig();
246 } else {
247 return null;
248 }
249 } else {
250 return null;
251 }
252}