UNPKG

7.48 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 { Subchannel } from './subchannel';
20import { SubchannelAddress } from './subchannel-address';
21import { ConnectivityState } from './connectivity-state';
22import { Picker } from './picker';
23import { ChannelRef, SubchannelRef } from './channelz';
24import { SubchannelInterface } from './subchannel-interface';
25
26/**
27 * A collection of functions associated with a channel that a load balancer
28 * can call as necessary.
29 */
30export interface ChannelControlHelper {
31 /**
32 * Returns a subchannel connected to the specified address.
33 * @param subchannelAddress The address to connect to
34 * @param subchannelArgs Extra channel arguments specified by the load balancer
35 */
36 createSubchannel(
37 subchannelAddress: SubchannelAddress,
38 subchannelArgs: ChannelOptions
39 ): SubchannelInterface;
40 /**
41 * Passes a new subchannel picker up to the channel. This is called if either
42 * the connectivity state changes or if a different picker is needed for any
43 * other reason.
44 * @param connectivityState New connectivity state
45 * @param picker New picker
46 */
47 updateState(connectivityState: ConnectivityState, picker: Picker): void;
48 /**
49 * Request new data from the resolver.
50 */
51 requestReresolution(): void;
52 addChannelzChild(child: ChannelRef | SubchannelRef): void;
53 removeChannelzChild(child: ChannelRef | SubchannelRef): void;
54}
55
56/**
57 * Create a child ChannelControlHelper that overrides some methods of the
58 * parent while letting others pass through to the parent unmodified. This
59 * allows other code to create these children without needing to know about
60 * all of the methods to be passed through.
61 * @param parent
62 * @param overrides
63 */
64export function createChildChannelControlHelper(parent: ChannelControlHelper, overrides: Partial<ChannelControlHelper>): ChannelControlHelper {
65 return {
66 createSubchannel: overrides.createSubchannel?.bind(overrides) ?? parent.createSubchannel.bind(parent),
67 updateState: overrides.updateState?.bind(overrides) ?? parent.updateState.bind(parent),
68 requestReresolution: overrides.requestReresolution?.bind(overrides) ?? parent.requestReresolution.bind(parent),
69 addChannelzChild: overrides.addChannelzChild?.bind(overrides) ?? parent.addChannelzChild.bind(parent),
70 removeChannelzChild: overrides.removeChannelzChild?.bind(overrides) ?? parent.removeChannelzChild.bind(parent)
71 };
72}
73
74/**
75 * Tracks one or more connected subchannels and determines which subchannel
76 * each request should use.
77 */
78export interface LoadBalancer {
79 /**
80 * Gives the load balancer a new list of addresses to start connecting to.
81 * The load balancer will start establishing connections with the new list,
82 * but will continue using any existing connections until the new connections
83 * are established
84 * @param addressList The new list of addresses to connect to
85 * @param lbConfig The load balancing config object from the service config,
86 * if one was provided
87 */
88 updateAddressList(
89 addressList: SubchannelAddress[],
90 lbConfig: LoadBalancingConfig,
91 attributes: { [key: string]: unknown }
92 ): void;
93 /**
94 * If the load balancer is currently in the IDLE state, start connecting.
95 */
96 exitIdle(): void;
97 /**
98 * If the load balancer is currently in the CONNECTING or TRANSIENT_FAILURE
99 * state, reset the current connection backoff timeout to its base value and
100 * transition to CONNECTING if in TRANSIENT_FAILURE.
101 */
102 resetBackoff(): void;
103 /**
104 * The load balancer unrefs all of its subchannels and stops calling methods
105 * of its channel control helper.
106 */
107 destroy(): void;
108 /**
109 * Get the type name for this load balancer type. Must be constant across an
110 * entire load balancer implementation class and must match the name that the
111 * balancer implementation class was registered with.
112 */
113 getTypeName(): string;
114}
115
116export interface LoadBalancerConstructor {
117 new (channelControlHelper: ChannelControlHelper): LoadBalancer;
118}
119
120export interface LoadBalancingConfig {
121 getLoadBalancerName(): string;
122 toJsonObject(): object;
123}
124
125export interface LoadBalancingConfigConstructor {
126 // eslint-disable-next-line @typescript-eslint/no-explicit-any
127 new (...args: any): LoadBalancingConfig;
128 // eslint-disable-next-line @typescript-eslint/no-explicit-any
129 createFromJson(obj: any): LoadBalancingConfig;
130}
131
132const registeredLoadBalancerTypes: {
133 [name: string]: {
134 LoadBalancer: LoadBalancerConstructor;
135 LoadBalancingConfig: LoadBalancingConfigConstructor;
136 };
137} = {};
138
139let defaultLoadBalancerType: string | null = null;
140
141export function registerLoadBalancerType(
142 typeName: string,
143 loadBalancerType: LoadBalancerConstructor,
144 loadBalancingConfigType: LoadBalancingConfigConstructor
145) {
146 registeredLoadBalancerTypes[typeName] = {
147 LoadBalancer: loadBalancerType,
148 LoadBalancingConfig: loadBalancingConfigType,
149 };
150}
151
152export function registerDefaultLoadBalancerType(typeName: string) {
153 defaultLoadBalancerType = typeName;
154}
155
156export function createLoadBalancer(
157 config: LoadBalancingConfig,
158 channelControlHelper: ChannelControlHelper
159): LoadBalancer | null {
160 const typeName = config.getLoadBalancerName();
161 if (typeName in registeredLoadBalancerTypes) {
162 return new registeredLoadBalancerTypes[typeName].LoadBalancer(
163 channelControlHelper
164 );
165 } else {
166 return null;
167 }
168}
169
170export function isLoadBalancerNameRegistered(typeName: string): boolean {
171 return typeName in registeredLoadBalancerTypes;
172}
173
174export function getFirstUsableConfig(
175 configs: LoadBalancingConfig[],
176 fallbackTodefault?: true
177): LoadBalancingConfig;
178export function getFirstUsableConfig(
179 configs: LoadBalancingConfig[],
180 fallbackTodefault = false
181): LoadBalancingConfig | null {
182 for (const config of configs) {
183 if (config.getLoadBalancerName() in registeredLoadBalancerTypes) {
184 return config;
185 }
186 }
187 if (fallbackTodefault) {
188 if (defaultLoadBalancerType) {
189 return new registeredLoadBalancerTypes[
190 defaultLoadBalancerType
191 ]!.LoadBalancingConfig();
192 } else {
193 return null;
194 }
195 } else {
196 return null;
197 }
198}
199
200// eslint-disable-next-line @typescript-eslint/no-explicit-any
201export function validateLoadBalancingConfig(obj: any): LoadBalancingConfig {
202 if (!(obj !== null && typeof obj === 'object')) {
203 throw new Error('Load balancing config must be an object');
204 }
205 const keys = Object.keys(obj);
206 if (keys.length !== 1) {
207 throw new Error(
208 'Provided load balancing config has multiple conflicting entries'
209 );
210 }
211 const typeName = keys[0];
212 if (typeName in registeredLoadBalancerTypes) {
213 return registeredLoadBalancerTypes[
214 typeName
215 ].LoadBalancingConfig.createFromJson(obj[typeName]);
216 } else {
217 throw new Error(`Unrecognized load balancing config name ${typeName}`);
218 }
219}