UNPKG

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