UNPKG

12 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright 2019 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18Object.defineProperty(exports, "__esModule", { value: true });
19exports.ResolvingLoadBalancer = void 0;
20const load_balancer_1 = require("./load-balancer");
21const service_config_1 = require("./service-config");
22const connectivity_state_1 = require("./connectivity-state");
23const resolver_1 = require("./resolver");
24const picker_1 = require("./picker");
25const backoff_timeout_1 = require("./backoff-timeout");
26const constants_1 = require("./constants");
27const metadata_1 = require("./metadata");
28const logging = require("./logging");
29const constants_2 = require("./constants");
30const uri_parser_1 = require("./uri-parser");
31const load_balancer_child_handler_1 = require("./load-balancer-child-handler");
32const TRACER_NAME = 'resolving_load_balancer';
33function trace(text) {
34 logging.trace(constants_2.LogVerbosity.DEBUG, TRACER_NAME, text);
35}
36const DEFAULT_LOAD_BALANCER_NAME = 'pick_first';
37function getDefaultConfigSelector(serviceConfig) {
38 return function defaultConfigSelector(methodName, metadata) {
39 var _a, _b;
40 const splitName = methodName.split('/').filter((x) => x.length > 0);
41 const service = (_a = splitName[0]) !== null && _a !== void 0 ? _a : '';
42 const method = (_b = splitName[1]) !== null && _b !== void 0 ? _b : '';
43 if (serviceConfig && serviceConfig.methodConfig) {
44 for (const methodConfig of serviceConfig.methodConfig) {
45 for (const name of methodConfig.name) {
46 if (name.service === service &&
47 (name.method === undefined || name.method === method)) {
48 return {
49 methodConfig: methodConfig,
50 pickInformation: {},
51 status: constants_1.Status.OK,
52 dynamicFilterFactories: []
53 };
54 }
55 }
56 }
57 }
58 return {
59 methodConfig: { name: [] },
60 pickInformation: {},
61 status: constants_1.Status.OK,
62 dynamicFilterFactories: []
63 };
64 };
65}
66class ResolvingLoadBalancer {
67 /**
68 * Wrapper class that behaves like a `LoadBalancer` and also handles name
69 * resolution internally.
70 * @param target The address of the backend to connect to.
71 * @param channelControlHelper `ChannelControlHelper` instance provided by
72 * this load balancer's owner.
73 * @param defaultServiceConfig The default service configuration to be used
74 * if none is provided by the name resolver. A `null` value indicates
75 * that the default behavior should be the default unconfigured behavior.
76 * In practice, that means using the "pick first" load balancer
77 * implmentation
78 */
79 constructor(target, channelControlHelper, channelOptions, onSuccessfulResolution, onFailedResolution) {
80 this.target = target;
81 this.channelControlHelper = channelControlHelper;
82 this.channelOptions = channelOptions;
83 this.onSuccessfulResolution = onSuccessfulResolution;
84 this.onFailedResolution = onFailedResolution;
85 this.latestChildState = connectivity_state_1.ConnectivityState.IDLE;
86 this.latestChildPicker = new picker_1.QueuePicker(this);
87 /**
88 * This resolving load balancer's current connectivity state.
89 */
90 this.currentState = connectivity_state_1.ConnectivityState.IDLE;
91 /**
92 * The service config object from the last successful resolution, if
93 * available. A value of null indicates that we have not yet received a valid
94 * service config from the resolver.
95 */
96 this.previousServiceConfig = null;
97 /**
98 * Indicates whether we should attempt to resolve again after the backoff
99 * timer runs out.
100 */
101 this.continueResolving = false;
102 if (channelOptions['grpc.service_config']) {
103 this.defaultServiceConfig = service_config_1.validateServiceConfig(JSON.parse(channelOptions['grpc.service_config']));
104 }
105 else {
106 this.defaultServiceConfig = {
107 loadBalancingConfig: [],
108 methodConfig: [],
109 };
110 }
111 this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this));
112 this.childLoadBalancer = new load_balancer_child_handler_1.ChildLoadBalancerHandler({
113 createSubchannel: channelControlHelper.createSubchannel.bind(channelControlHelper),
114 requestReresolution: () => {
115 /* If the backoffTimeout is running, we're still backing off from
116 * making resolve requests, so we shouldn't make another one here.
117 * In that case, the backoff timer callback will call
118 * updateResolution */
119 if (this.backoffTimeout.isRunning()) {
120 this.continueResolving = true;
121 }
122 else {
123 this.updateResolution();
124 }
125 },
126 updateState: (newState, picker) => {
127 this.latestChildState = newState;
128 this.latestChildPicker = picker;
129 this.updateState(newState, picker);
130 },
131 addChannelzChild: channelControlHelper.addChannelzChild.bind(channelControlHelper),
132 removeChannelzChild: channelControlHelper.removeChannelzChild.bind(channelControlHelper)
133 });
134 this.innerResolver = resolver_1.createResolver(target, {
135 onSuccessfulResolution: (addressList, serviceConfig, serviceConfigError, configSelector, attributes) => {
136 var _a;
137 let workingServiceConfig = null;
138 /* This first group of conditionals implements the algorithm described
139 * in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md
140 * in the section called "Behavior on receiving a new gRPC Config".
141 */
142 if (serviceConfig === null) {
143 // Step 4 and 5
144 if (serviceConfigError === null) {
145 // Step 5
146 this.previousServiceConfig = null;
147 workingServiceConfig = this.defaultServiceConfig;
148 }
149 else {
150 // Step 4
151 if (this.previousServiceConfig === null) {
152 // Step 4.ii
153 this.handleResolutionFailure(serviceConfigError);
154 }
155 else {
156 // Step 4.i
157 workingServiceConfig = this.previousServiceConfig;
158 }
159 }
160 }
161 else {
162 // Step 3
163 workingServiceConfig = serviceConfig;
164 this.previousServiceConfig = serviceConfig;
165 }
166 const workingConfigList = (_a = workingServiceConfig === null || workingServiceConfig === void 0 ? void 0 : workingServiceConfig.loadBalancingConfig) !== null && _a !== void 0 ? _a : [];
167 const loadBalancingConfig = load_balancer_1.getFirstUsableConfig(workingConfigList, true);
168 if (loadBalancingConfig === null) {
169 // There were load balancing configs but none are supported. This counts as a resolution failure
170 this.handleResolutionFailure({
171 code: constants_1.Status.UNAVAILABLE,
172 details: 'All load balancer options in service config are not compatible',
173 metadata: new metadata_1.Metadata(),
174 });
175 return;
176 }
177 this.childLoadBalancer.updateAddressList(addressList, loadBalancingConfig, attributes);
178 const finalServiceConfig = workingServiceConfig !== null && workingServiceConfig !== void 0 ? workingServiceConfig : this.defaultServiceConfig;
179 this.onSuccessfulResolution(configSelector !== null && configSelector !== void 0 ? configSelector : getDefaultConfigSelector(finalServiceConfig));
180 },
181 onError: (error) => {
182 this.handleResolutionFailure(error);
183 },
184 }, channelOptions);
185 const backoffOptions = {
186 initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'],
187 maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'],
188 };
189 this.backoffTimeout = new backoff_timeout_1.BackoffTimeout(() => {
190 if (this.continueResolving) {
191 this.updateResolution();
192 this.continueResolving = false;
193 }
194 else {
195 this.updateState(this.latestChildState, this.latestChildPicker);
196 }
197 }, backoffOptions);
198 this.backoffTimeout.unref();
199 }
200 updateResolution() {
201 this.innerResolver.updateResolution();
202 if (this.currentState === connectivity_state_1.ConnectivityState.IDLE) {
203 this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, new picker_1.QueuePicker(this));
204 }
205 this.backoffTimeout.runOnce();
206 }
207 updateState(connectivityState, picker) {
208 trace(uri_parser_1.uriToString(this.target) +
209 ' ' +
210 connectivity_state_1.ConnectivityState[this.currentState] +
211 ' -> ' +
212 connectivity_state_1.ConnectivityState[connectivityState]);
213 // Ensure that this.exitIdle() is called by the picker
214 if (connectivityState === connectivity_state_1.ConnectivityState.IDLE) {
215 picker = new picker_1.QueuePicker(this);
216 }
217 this.currentState = connectivityState;
218 this.channelControlHelper.updateState(connectivityState, picker);
219 }
220 handleResolutionFailure(error) {
221 if (this.latestChildState === connectivity_state_1.ConnectivityState.IDLE) {
222 this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker(error));
223 this.onFailedResolution(error);
224 }
225 }
226 exitIdle() {
227 if (this.currentState === connectivity_state_1.ConnectivityState.IDLE || this.currentState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
228 if (this.backoffTimeout.isRunning()) {
229 this.continueResolving = true;
230 }
231 else {
232 this.updateResolution();
233 }
234 }
235 this.childLoadBalancer.exitIdle();
236 }
237 updateAddressList(addressList, lbConfig) {
238 throw new Error('updateAddressList not supported on ResolvingLoadBalancer');
239 }
240 resetBackoff() {
241 this.backoffTimeout.reset();
242 this.childLoadBalancer.resetBackoff();
243 }
244 destroy() {
245 this.childLoadBalancer.destroy();
246 this.innerResolver.destroy();
247 this.updateState(connectivity_state_1.ConnectivityState.SHUTDOWN, new picker_1.UnavailablePicker());
248 }
249 getTypeName() {
250 return 'resolving_load_balancer';
251 }
252}
253exports.ResolvingLoadBalancer = ResolvingLoadBalancer;
254//# sourceMappingURL=resolving-load-balancer.js.map
\No newline at end of file