UNPKG

ydb-sdk

Version:

Node.js bindings for working with YDB API over gRPC

135 lines (134 loc) 6.55 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const ydb_sdk_proto_1 = require("ydb-sdk-proto"); var DiscoveryServiceAPI = ydb_sdk_proto_1.Ydb.Discovery.V1.DiscoveryService; const endpoint_1 = require("./endpoint"); const events_1 = __importDefault(require("events")); const lodash_1 = __importDefault(require("lodash")); const constants_1 = require("../constants"); const retries_obsoleted_1 = require("../retries_obsoleted"); const process_ydb_operation_result_1 = require("../utils/process-ydb-operation-result"); const utils_1 = require("../utils"); const internal_topic_client_1 = require("../topic/internal/internal-topic-client"); const noOp = () => { }; class DiscoveryService extends utils_1.AuthenticatedService { constructor(settings) { super(settings.endpoint, settings.database, 'Ydb.Discovery.V1.DiscoveryService', DiscoveryServiceAPI, settings.authService, settings.sslCredentials, settings.clientOptions); this.resolveEndpoints = noOp; this.rejectEndpoints = noOp; this.endpoints = []; this.currentEndpointIndex = 0; this.events = new events_1.default(); this.database = settings.database; this.discoveryPeriod = settings.discoveryPeriod; this.logger = settings.logger; this.endpointsPromise = new Promise((resolve, reject) => { this.resolveEndpoints = (endpoints) => { this.updateEndpoints(endpoints); resolve(); }; this.rejectEndpoints = reject; }); this.periodicDiscoveryId = this.init(); } destroy() { clearInterval(this.periodicDiscoveryId); } init() { this.discoverEndpoints(this.database) .then(this.resolveEndpoints) .catch(this.rejectEndpoints); return setInterval(async () => { await this.endpointsPromise; try { const endpoints = await this.discoverEndpoints(this.database); this.updateEndpoints(endpoints); } catch (error) { this.logger.error(error); } }, this.discoveryPeriod); } updateEndpoints(endpoints) { const getHost = (endpoint) => endpoint.toString(); const endpointsToAdd = lodash_1.default.differenceBy(endpoints, this.endpoints, getHost); const endpointsToRemove = lodash_1.default.differenceBy(this.endpoints, endpoints, getHost); const endpointsToUpdate = lodash_1.default.intersectionBy(this.endpoints, endpoints, getHost); this.logger.trace('Current endpoints %o', this.endpoints); this.logger.trace('Incoming endpoints %o', endpoints); this.logger.trace('Endpoints to add %o', endpointsToAdd); this.logger.trace('Endpoints to remove %o', endpointsToRemove); this.logger.trace('Endpoints to update %o', endpointsToUpdate); lodash_1.default.forEach(endpointsToRemove, (endpoint) => { endpoint.closeGrpcClient(); this.emit(constants_1.Events.ENDPOINT_REMOVED, endpoint); }); for (const current of endpointsToUpdate) { const newEndpoint = lodash_1.default.find(endpoints, (incoming) => incoming.toString() === current.toString()); current.update(newEndpoint); } // endpointsToUpdate ordering is the same as this.endpoints, according to _.intersectionBy docs this.endpoints = endpointsToUpdate.concat(endpointsToAdd); // reset round-robin index in case new endpoints have been discovered or existing ones have become stale if (endpointsToRemove.length + endpointsToAdd.length > 0) { this.endpoints = lodash_1.default.shuffle(this.endpoints); this.currentEndpointIndex = 0; } } async discoverEndpoints(database) { const response = await this.api.listEndpoints({ database }); const payload = (0, process_ydb_operation_result_1.getOperationPayload)(response); const endpointsResult = ydb_sdk_proto_1.Ydb.Discovery.ListEndpointsResult.decode(payload); // this.selfLocation = endpointsResult.selfLocation; return lodash_1.default.map(endpointsResult.endpoints, (endpointInfo) => new endpoint_1.Endpoint(endpointInfo, database)); } emit(eventName, ...args) { this.events.emit(eventName, ...args); } on(eventName, callback) { this.events.on(eventName, callback); } ready(timeout) { return (0, utils_1.withTimeout)(this.endpointsPromise, timeout); } // TODO: Add find by nodeid async getEndpointRR() { await this.endpointsPromise; // TODO: Consider taken a node with already opened grpc connection const endpoint = this.endpoints[this.currentEndpointIndex++ % this.endpoints.length]; return endpoint; } async getEndpoint() { let endpoint = await this.getEndpointRR(); let counter = 0; while (endpoint.pessimized && counter < this.endpoints.length) { endpoint = await this.getEndpointRR(); counter++; } if (counter === this.endpoints.length) { this.logger.debug('All endpoints are pessimized, returning original endpoint'); } return endpoint; } async getTopicNodeClient() { const endpoint = await this.getEndpoint(); if (!endpoint.topicNodeClient) { endpoint.topicNodeClient = new internal_topic_client_1.InternalTopicClient(endpoint, this.database, this.authService, this.logger, this.sslCredentials, this.clientOptions); } return endpoint.topicNodeClient; } } exports.default = DiscoveryService; __decorate([ (0, retries_obsoleted_1.retryable)() ], DiscoveryService.prototype, "discoverEndpoints", null);