UNPKG

@globalworldwide/grpc-resolvers

Version:
109 lines 4.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.K8SResolver = void 0; const tslib_1 = require("tslib"); const grpc = tslib_1.__importStar(require("@grpc/grpc-js")); const uri_parser_js_1 = require("@grpc/grpc-js/build/src/uri-parser.js"); const k8sWatch = tslib_1.__importStar(require("./internal/k8s-watch.js")); const logging_js_1 = require("./internal/logging.js"); const well_known_channel_options_js_1 = require("./well-known-channel-options.js"); const logger = (0, logging_js_1.makeLogger)('k8s-resolver'); class K8SResolver { static getDefaultAuthority(target) { return K8SResolver.parseHostPort(target)[0]; } static parseHostPort(target) { const [host, port] = target.path.split(':'); return [host, port]; } #target; #listener; #watch; #defaultResolutionError; #serviceName; #portName; // readonly #channelOptions: grpc.ChannelOptions #lastNotifyTimeMS = 0; #timeoutId = undefined; #hasEndpoints = false; #watchListenerRegistered = false; constructor(target, listener, channelOptions) { this.#target = target; this.#listener = listener; this.#watch = this.onWatchChange.bind(this); this.#defaultResolutionError = { code: grpc.status.UNAVAILABLE, details: `No endpoints available for target ${(0, uri_parser_js_1.uriToString)(this.#target)}`, metadata: new grpc.Metadata(), }; this.#serviceName = K8SResolver.getDefaultAuthority(this.#target); // MSED - I would like this to use the port from the target first and foremost, // but that doesn't work for us yet. Will switch once we drop the dns resolver. this.#portName = channelOptions[well_known_channel_options_js_1.WellKnownChannelOptions.portName] ?? 'grpc'; // this.#channelOptions = channelOptions } updateResolution() { if (!this.#watchListenerRegistered) { k8sWatch.addListener(this.#serviceName, this.#watch); this.#watchListenerRegistered = true; this.#hasEndpoints = this.hasEndpoints(); } // do not notify until we've seen an update from the k8s watch at least once if (!this.#hasEndpoints) { return; } // If the service is not actually running, then grpc-js starts calling updateResolution // as fast as we answer. In order to stop that from turning into a busy loop, we never // resolve more than once every 250ms if (!this.#timeoutId) { const nowMS = Date.now(); const delayMS = Math.max(0, 250 - (nowMS - this.#lastNotifyTimeMS)); this.#timeoutId = setTimeout(this.onTimeout.bind(this), delayMS); } } destroy() { clearTimeout(this.#timeoutId); this.#timeoutId = undefined; this.#lastNotifyTimeMS = 0; this.#watchListenerRegistered = false; k8sWatch.removeListener(this.#serviceName, this.#watch); } onWatchChange() { this.#hasEndpoints = true; if (this.#timeoutId) { clearTimeout(this.#timeoutId); this.#timeoutId = undefined; } this.notify(); } onTimeout() { this.#timeoutId = undefined; this.notify(); } notify() { this.#lastNotifyTimeMS = Date.now(); const endpoints = this.getEndpoints(); logger.debug?.(`mapped ${this.#serviceName} ${this.#portName}`, endpoints); if (endpoints.length > 0) { this.#listener.onSuccessfulResolution(endpoints, { loadBalancingConfig: [{ round_robin: {} }], methodConfig: [] }, null, null, {}); } else { this.#listener.onError(this.#defaultResolutionError); } } hasEndpoints() { return k8sWatch .getEndpoints(this.#serviceName) .some((e) => e.addresses.some((a) => a.portName === this.#portName)); } getEndpoints() { return k8sWatch .getEndpoints(this.#serviceName) .map((e) => ({ addresses: e.addresses.filter((a) => a.portName === this.#portName), })) .filter((e) => e.addresses.length > 0); } } exports.K8SResolver = K8SResolver; //# sourceMappingURL=k8s-resolver.js.map