|
"use strict"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.ResolvingLoadBalancer = void 0; |
|
const load_balancer_1 = require("./load-balancer"); |
|
const service_config_1 = require("./service-config"); |
|
const connectivity_state_1 = require("./connectivity-state"); |
|
const resolver_1 = require("./resolver"); |
|
const picker_1 = require("./picker"); |
|
const backoff_timeout_1 = require("./backoff-timeout"); |
|
const constants_1 = require("./constants"); |
|
const metadata_1 = require("./metadata"); |
|
const logging = require("./logging"); |
|
const constants_2 = require("./constants"); |
|
const uri_parser_1 = require("./uri-parser"); |
|
const load_balancer_child_handler_1 = require("./load-balancer-child-handler"); |
|
const TRACER_NAME = 'resolving_load_balancer'; |
|
function trace(text) { |
|
logging.trace(constants_2.LogVerbosity.DEBUG, TRACER_NAME, text); |
|
} |
|
|
|
|
|
|
|
|
|
const NAME_MATCH_LEVEL_ORDER = [ |
|
'SERVICE_AND_METHOD', |
|
'SERVICE', |
|
'EMPTY', |
|
]; |
|
function hasMatchingName(service, method, methodConfig, matchLevel) { |
|
for (const name of methodConfig.name) { |
|
switch (matchLevel) { |
|
case 'EMPTY': |
|
if (!name.service && !name.method) { |
|
return true; |
|
} |
|
break; |
|
case 'SERVICE': |
|
if (name.service === service && !name.method) { |
|
return true; |
|
} |
|
break; |
|
case 'SERVICE_AND_METHOD': |
|
if (name.service === service && name.method === method) { |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
function findMatchingConfig(service, method, methodConfigs, matchLevel) { |
|
for (const config of methodConfigs) { |
|
if (hasMatchingName(service, method, config, matchLevel)) { |
|
return config; |
|
} |
|
} |
|
return null; |
|
} |
|
function getDefaultConfigSelector(serviceConfig) { |
|
return function defaultConfigSelector(methodName, metadata) { |
|
var _a, _b; |
|
const splitName = methodName.split('/').filter(x => x.length > 0); |
|
const service = (_a = splitName[0]) !== null && _a !== void 0 ? _a : ''; |
|
const method = (_b = splitName[1]) !== null && _b !== void 0 ? _b : ''; |
|
if (serviceConfig && serviceConfig.methodConfig) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const matchLevel of NAME_MATCH_LEVEL_ORDER) { |
|
const matchingConfig = findMatchingConfig(service, method, serviceConfig.methodConfig, matchLevel); |
|
if (matchingConfig) { |
|
return { |
|
methodConfig: matchingConfig, |
|
pickInformation: {}, |
|
status: constants_1.Status.OK, |
|
dynamicFilterFactories: [], |
|
}; |
|
} |
|
} |
|
} |
|
return { |
|
methodConfig: { name: [] }, |
|
pickInformation: {}, |
|
status: constants_1.Status.OK, |
|
dynamicFilterFactories: [], |
|
}; |
|
}; |
|
} |
|
class ResolvingLoadBalancer { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(target, channelControlHelper, credentials, channelOptions, onSuccessfulResolution, onFailedResolution) { |
|
this.target = target; |
|
this.channelControlHelper = channelControlHelper; |
|
this.onSuccessfulResolution = onSuccessfulResolution; |
|
this.onFailedResolution = onFailedResolution; |
|
this.latestChildState = connectivity_state_1.ConnectivityState.IDLE; |
|
this.latestChildPicker = new picker_1.QueuePicker(this); |
|
|
|
|
|
|
|
this.currentState = connectivity_state_1.ConnectivityState.IDLE; |
|
|
|
|
|
|
|
|
|
|
|
this.previousServiceConfig = null; |
|
|
|
|
|
|
|
|
|
this.continueResolving = false; |
|
if (channelOptions['grpc.service_config']) { |
|
this.defaultServiceConfig = (0, service_config_1.validateServiceConfig)(JSON.parse(channelOptions['grpc.service_config'])); |
|
} |
|
else { |
|
this.defaultServiceConfig = { |
|
loadBalancingConfig: [], |
|
methodConfig: [], |
|
}; |
|
} |
|
this.updateState(connectivity_state_1.ConnectivityState.IDLE, new picker_1.QueuePicker(this)); |
|
this.childLoadBalancer = new load_balancer_child_handler_1.ChildLoadBalancerHandler({ |
|
createSubchannel: channelControlHelper.createSubchannel.bind(channelControlHelper), |
|
requestReresolution: () => { |
|
|
|
|
|
|
|
|
|
if (this.backoffTimeout.isRunning()) { |
|
trace('requestReresolution delayed by backoff timer until ' + |
|
this.backoffTimeout.getEndTime().toISOString()); |
|
this.continueResolving = true; |
|
} |
|
else { |
|
this.updateResolution(); |
|
} |
|
}, |
|
updateState: (newState, picker) => { |
|
this.latestChildState = newState; |
|
this.latestChildPicker = picker; |
|
this.updateState(newState, picker); |
|
}, |
|
addChannelzChild: channelControlHelper.addChannelzChild.bind(channelControlHelper), |
|
removeChannelzChild: channelControlHelper.removeChannelzChild.bind(channelControlHelper), |
|
}, credentials, channelOptions); |
|
this.innerResolver = (0, resolver_1.createResolver)(target, { |
|
onSuccessfulResolution: (endpointList, serviceConfig, serviceConfigError, configSelector, attributes) => { |
|
var _a; |
|
this.backoffTimeout.stop(); |
|
this.backoffTimeout.reset(); |
|
let workingServiceConfig = null; |
|
|
|
|
|
|
|
|
|
if (serviceConfig === null) { |
|
|
|
if (serviceConfigError === null) { |
|
|
|
this.previousServiceConfig = null; |
|
workingServiceConfig = this.defaultServiceConfig; |
|
} |
|
else { |
|
|
|
if (this.previousServiceConfig === null) { |
|
|
|
this.handleResolutionFailure(serviceConfigError); |
|
} |
|
else { |
|
|
|
workingServiceConfig = this.previousServiceConfig; |
|
} |
|
} |
|
} |
|
else { |
|
|
|
workingServiceConfig = serviceConfig; |
|
this.previousServiceConfig = serviceConfig; |
|
} |
|
const workingConfigList = (_a = workingServiceConfig === null || workingServiceConfig === void 0 ? void 0 : workingServiceConfig.loadBalancingConfig) !== null && _a !== void 0 ? _a : []; |
|
const loadBalancingConfig = (0, load_balancer_1.selectLbConfigFromList)(workingConfigList, true); |
|
if (loadBalancingConfig === null) { |
|
|
|
this.handleResolutionFailure({ |
|
code: constants_1.Status.UNAVAILABLE, |
|
details: 'All load balancer options in service config are not compatible', |
|
metadata: new metadata_1.Metadata(), |
|
}); |
|
return; |
|
} |
|
this.childLoadBalancer.updateAddressList(endpointList, loadBalancingConfig, attributes); |
|
const finalServiceConfig = workingServiceConfig !== null && workingServiceConfig !== void 0 ? workingServiceConfig : this.defaultServiceConfig; |
|
this.onSuccessfulResolution(finalServiceConfig, configSelector !== null && configSelector !== void 0 ? configSelector : getDefaultConfigSelector(finalServiceConfig)); |
|
}, |
|
onError: (error) => { |
|
this.handleResolutionFailure(error); |
|
}, |
|
}, channelOptions); |
|
const backoffOptions = { |
|
initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'], |
|
maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'], |
|
}; |
|
this.backoffTimeout = new backoff_timeout_1.BackoffTimeout(() => { |
|
if (this.continueResolving) { |
|
this.updateResolution(); |
|
this.continueResolving = false; |
|
} |
|
else { |
|
this.updateState(this.latestChildState, this.latestChildPicker); |
|
} |
|
}, backoffOptions); |
|
this.backoffTimeout.unref(); |
|
} |
|
updateResolution() { |
|
this.innerResolver.updateResolution(); |
|
if (this.currentState === connectivity_state_1.ConnectivityState.IDLE) { |
|
|
|
|
|
|
|
|
|
this.updateState(connectivity_state_1.ConnectivityState.CONNECTING, this.latestChildPicker); |
|
} |
|
this.backoffTimeout.runOnce(); |
|
} |
|
updateState(connectivityState, picker) { |
|
trace((0, uri_parser_1.uriToString)(this.target) + |
|
' ' + |
|
connectivity_state_1.ConnectivityState[this.currentState] + |
|
' -> ' + |
|
connectivity_state_1.ConnectivityState[connectivityState]); |
|
|
|
if (connectivityState === connectivity_state_1.ConnectivityState.IDLE) { |
|
picker = new picker_1.QueuePicker(this, picker); |
|
} |
|
this.currentState = connectivityState; |
|
this.channelControlHelper.updateState(connectivityState, picker); |
|
} |
|
handleResolutionFailure(error) { |
|
if (this.latestChildState === connectivity_state_1.ConnectivityState.IDLE) { |
|
this.updateState(connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, new picker_1.UnavailablePicker(error)); |
|
this.onFailedResolution(error); |
|
} |
|
} |
|
exitIdle() { |
|
if (this.currentState === connectivity_state_1.ConnectivityState.IDLE || |
|
this.currentState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) { |
|
if (this.backoffTimeout.isRunning()) { |
|
this.continueResolving = true; |
|
} |
|
else { |
|
this.updateResolution(); |
|
} |
|
} |
|
this.childLoadBalancer.exitIdle(); |
|
} |
|
updateAddressList(endpointList, lbConfig) { |
|
throw new Error('updateAddressList not supported on ResolvingLoadBalancer'); |
|
} |
|
resetBackoff() { |
|
this.backoffTimeout.reset(); |
|
this.childLoadBalancer.resetBackoff(); |
|
} |
|
destroy() { |
|
this.childLoadBalancer.destroy(); |
|
this.innerResolver.destroy(); |
|
this.backoffTimeout.reset(); |
|
this.backoffTimeout.stop(); |
|
this.latestChildState = connectivity_state_1.ConnectivityState.IDLE; |
|
this.latestChildPicker = new picker_1.QueuePicker(this); |
|
this.currentState = connectivity_state_1.ConnectivityState.IDLE; |
|
this.previousServiceConfig = null; |
|
this.continueResolving = false; |
|
} |
|
getTypeName() { |
|
return 'resolving_load_balancer'; |
|
} |
|
} |
|
exports.ResolvingLoadBalancer = ResolvingLoadBalancer; |
|
|