resolving-load-balancer.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. exports.ResolvingLoadBalancer = void 0;
  20. const load_balancer_1 = require("./load-balancer");
  21. const service_config_1 = require("./service-config");
  22. const connectivity_state_1 = require("./connectivity-state");
  23. const resolver_1 = require("./resolver");
  24. const picker_1 = require("./picker");
  25. const backoff_timeout_1 = require("./backoff-timeout");
  26. const constants_1 = require("./constants");
  27. const metadata_1 = require("./metadata");
  28. const logging = require("./logging");
  29. const constants_2 = require("./constants");
  30. const uri_parser_1 = require("./uri-parser");
  31. const load_balancer_child_handler_1 = require("./load-balancer-child-handler");
  32. const TRACER_NAME = 'resolving_load_balancer';
  33. function trace(text) {
  34. logging.trace(constants_2.LogVerbosity.DEBUG, TRACER_NAME, text);
  35. }
  36. /**
  37. * Name match levels in order from most to least specific. This is the order in
  38. * which searches will be performed.
  39. */
  40. const NAME_MATCH_LEVEL_ORDER = [
  41. 'SERVICE_AND_METHOD',
  42. 'SERVICE',
  43. 'EMPTY',
  44. ];
  45. function hasMatchingName(service, method, methodConfig, matchLevel) {
  46. for (const name of methodConfig.name) {
  47. switch (matchLevel) {
  48. case 'EMPTY':
  49. if (!name.service && !name.method) {
  50. return true;
  51. }
  52. break;
  53. case 'SERVICE':
  54. if (name.service === service && !name.method) {
  55. return true;
  56. }
  57. break;
  58. case 'SERVICE_AND_METHOD':
  59. if (name.service === service && name.method === method) {
  60. return true;
  61. }
  62. }
  63. }
  64. return false;
  65. }
  66. function findMatchingConfig(service, method, methodConfigs, matchLevel) {
  67. for (const config of methodConfigs) {
  68. if (hasMatchingName(service, method, config, matchLevel)) {
  69. return config;
  70. }
  71. }
  72. return null;
  73. }
  74. function getDefaultConfigSelector(serviceConfig) {
  75. return function defaultConfigSelector(methodName, metadata) {
  76. var _a, _b;
  77. const splitName = methodName.split('/').filter(x => x.length > 0);
  78. const service = (_a = splitName[0]) !== null && _a !== void 0 ? _a : '';
  79. const method = (_b = splitName[1]) !== null && _b !== void 0 ? _b : '';
  80. if (serviceConfig && serviceConfig.methodConfig) {
  81. /* Check for the following in order, and return the first method
  82. * config that matches:
  83. * 1. A name that exactly matches the service and method
  84. * 2. A name with no method set that matches the service
  85. * 3. An empty name
  86. */
  87. for (const matchLevel of NAME_MATCH_LEVEL_ORDER) {
  88. const matchingConfig = findMatchingConfig(service, method, serviceConfig.methodConfig, matchLevel);
  89. if (matchingConfig) {
  90. return {
  91. methodConfig: matchingConfig,
  92. pickInformation: {},
  93. status: constants_1.Status.OK,
  94. dynamicFilterFactories: [],
  95. };
  96. }
  97. }
  98. }
  99. return {
  100. methodConfig: { name: [] },
  101. pickInformation: {},
  102. status: constants_1.Status.OK,
  103. dynamicFilterFactories: [],
  104. };
  105. };
  106. }
  107. class ResolvingLoadBalancer {
  108. /**
  109. * Wrapper class that behaves like a `LoadBalancer` and also handles name
  110. * resolution internally.
  111. * @param target The address of the backend to connect to.
  112. * @param channelControlHelper `ChannelControlHelper` instance provided by
  113. * this load balancer's owner.
  114. * @param defaultServiceConfig The default service configuration to be used
  115. * if none is provided by the name resolver. A `null` value indicates
  116. * that the default behavior should be the default unconfigured behavior.
  117. * In practice, that means using the "pick first" load balancer
  118. * implmentation
  119. */
  120. constructor(target, channelControlHelper, credentials, channelOptions, onSuccessfulResolution, onFailedResolution) {
  121. this.target = target;
  122. this.channelControlHelper = channelControlHelper;
  123. this.onSuccessfulResolution = onSuccessfulResolution;
  124. this.onFailedResolution = onFailedResolution;
  125. this.latestChildState = connectivity_state_1.ConnectivityState.IDLE;
  126. this.latestChildPicker = new picker_1.QueuePicker(this);
  127. /**
  128. * This resolving load balancer's current connectivity state.
  129. */
  130. this.currentState = connectivity_state_1.ConnectivityState.IDLE;
  131. /**
  132. * The service config object from the last successful resolution, if
  133. * available. A value of null indicates that we have not yet received a valid
  134. * service config from the resolver.
  135. */
  136. this.previousServiceConfig = null;
  137. /**
  138. * Indicates whether we should attempt to resolve again after the backoff
  139. * timer runs out.
  140. */
  141. this.continueResolving = false;
  142. if (channelOptions['grpc.service_config']) {
  143. this.defaultServiceConfig = (0, service_config_1.validateServiceConfig)(JSON.parse(channelOptions['grpc.service_config']));
  144. }
  145. else {
  146. this.defaultServiceConfig = {
  147. loadBalancingConfig: [],
  148. methodConfig: [],
  149. };
  150. }
  151. this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this));
  152. this.childLoadBalancer = new load_balancer_child_handler_1.ChildLoadBalancerHandler({
  153. createSubchannel: channelControlHelper.createSubchannel.bind(channelControlHelper),
  154. requestReresolution: () => {
  155. /* If the backoffTimeout is running, we're still backing off from
  156. * making resolve requests, so we shouldn't make another one here.
  157. * In that case, the backoff timer callback will call
  158. * updateResolution */
  159. if (this.backoffTimeout.isRunning()) {
  160. trace('requestReresolution delayed by backoff timer until ' +
  161. this.backoffTimeout.getEndTime().toISOString());
  162. this.continueResolving = true;
  163. }
  164. else {
  165. this.updateResolution();
  166. }
  167. },
  168. updateState: (newState, picker) => {
  169. this.latestChildState = newState;
  170. this.latestChildPicker = picker;
  171. this.updateState(newState, picker);
  172. },
  173. addChannelzChild: channelControlHelper.addChannelzChild.bind(channelControlHelper),
  174. removeChannelzChild: channelControlHelper.removeChannelzChild.bind(channelControlHelper),
  175. }, credentials, channelOptions);
  176. this.innerResolver = (0, resolver_1.createResolver)(target, {
  177. onSuccessfulResolution: (endpointList, serviceConfig, serviceConfigError, configSelector, attributes) => {
  178. var _a;
  179. this.backoffTimeout.stop();
  180. this.backoffTimeout.reset();
  181. let workingServiceConfig = null;
  182. /* This first group of conditionals implements the algorithm described
  183. * in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md
  184. * in the section called "Behavior on receiving a new gRPC Config".
  185. */
  186. if (serviceConfig === null) {
  187. // Step 4 and 5
  188. if (serviceConfigError === null) {
  189. // Step 5
  190. this.previousServiceConfig = null;
  191. workingServiceConfig = this.defaultServiceConfig;
  192. }
  193. else {
  194. // Step 4
  195. if (this.previousServiceConfig === null) {
  196. // Step 4.ii
  197. this.handleResolutionFailure(serviceConfigError);
  198. }
  199. else {
  200. // Step 4.i
  201. workingServiceConfig = this.previousServiceConfig;
  202. }
  203. }
  204. }
  205. else {
  206. // Step 3
  207. workingServiceConfig = serviceConfig;
  208. this.previousServiceConfig = serviceConfig;
  209. }
  210. const workingConfigList = (_a = workingServiceConfig === null || workingServiceConfig === void 0 ? void 0 : workingServiceConfig.loadBalancingConfig) !== null && _a !== void 0 ? _a : [];
  211. const loadBalancingConfig = (0, load_balancer_1.selectLbConfigFromList)(workingConfigList, true);
  212. if (loadBalancingConfig === null) {
  213. // There were load balancing configs but none are supported. This counts as a resolution failure
  214. this.handleResolutionFailure({
  215. code: constants_1.Status.UNAVAILABLE,
  216. details: 'All load balancer options in service config are not compatible',
  217. metadata: new metadata_1.Metadata(),
  218. });
  219. return;
  220. }
  221. this.childLoadBalancer.updateAddressList(endpointList, loadBalancingConfig, attributes);
  222. const finalServiceConfig = workingServiceConfig !== null && workingServiceConfig !== void 0 ? workingServiceConfig : this.defaultServiceConfig;
  223. this.onSuccessfulResolution(finalServiceConfig, configSelector !== null && configSelector !== void 0 ? configSelector : getDefaultConfigSelector(finalServiceConfig));
  224. },
  225. onError: (error) => {
  226. this.handleResolutionFailure(error);
  227. },
  228. }, channelOptions);
  229. const backoffOptions = {
  230. initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'],
  231. maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'],
  232. };
  233. this.backoffTimeout = new backoff_timeout_1.BackoffTimeout(() => {
  234. if (this.continueResolving) {
  235. this.updateResolution();
  236. this.continueResolving = false;
  237. }
  238. else {
  239. this.updateState(this.latestChildState, this.latestChildPicker);
  240. }
  241. }, backoffOptions);
  242. this.backoffTimeout.unref();
  243. }
  244. updateResolution() {
  245. this.innerResolver.updateResolution();
  246. if (this.currentState === connectivity_state_1.ConnectivityState.IDLE) {
  247. /* this.latestChildPicker is initialized as new QueuePicker(this), which
  248. * is an appropriate value here if the child LB policy is unset.
  249. * Otherwise, we want to delegate to the child here, in case that
  250. * triggers something. */
  251. this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, this.latestChildPicker);
  252. }
  253. this.backoffTimeout.runOnce();
  254. }
  255. updateState(connectivityState, picker) {
  256. trace((0, uri_parser_1.uriToString)(this.target) +
  257. ' ' +
  258. connectivity_state_1.ConnectivityState[this.currentState] +
  259. ' -> ' +
  260. connectivity_state_1.ConnectivityState[connectivityState]);
  261. // Ensure that this.exitIdle() is called by the picker
  262. if (connectivityState === connectivity_state_1.ConnectivityState.IDLE) {
  263. picker = new picker_1.QueuePicker(this, picker);
  264. }
  265. this.currentState = connectivityState;
  266. this.channelControlHelper.updateState(connectivityState, picker);
  267. }
  268. handleResolutionFailure(error) {
  269. if (this.latestChildState === connectivity_state_1.ConnectivityState.IDLE) {
  270. this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker(error));
  271. this.onFailedResolution(error);
  272. }
  273. }
  274. exitIdle() {
  275. if (this.currentState === connectivity_state_1.ConnectivityState.IDLE ||
  276. this.currentState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
  277. if (this.backoffTimeout.isRunning()) {
  278. this.continueResolving = true;
  279. }
  280. else {
  281. this.updateResolution();
  282. }
  283. }
  284. this.childLoadBalancer.exitIdle();
  285. }
  286. updateAddressList(endpointList, lbConfig) {
  287. throw new Error('updateAddressList not supported on ResolvingLoadBalancer');
  288. }
  289. resetBackoff() {
  290. this.backoffTimeout.reset();
  291. this.childLoadBalancer.resetBackoff();
  292. }
  293. destroy() {
  294. this.childLoadBalancer.destroy();
  295. this.innerResolver.destroy();
  296. this.backoffTimeout.reset();
  297. this.backoffTimeout.stop();
  298. this.latestChildState = connectivity_state_1.ConnectivityState.IDLE;
  299. this.latestChildPicker = new picker_1.QueuePicker(this);
  300. this.currentState = connectivity_state_1.ConnectivityState.IDLE;
  301. this.previousServiceConfig = null;
  302. this.continueResolving = false;
  303. }
  304. getTypeName() {
  305. return 'resolving_load_balancer';
  306. }
  307. }
  308. exports.ResolvingLoadBalancer = ResolvingLoadBalancer;
  309. //# sourceMappingURL=resolving-load-balancer.js.map