UNPKG

turnilo

Version:

Business intelligence, data exploration and visualization web application for Druid, formerly known as Swiv and Pivot

331 lines 16.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const plywood_1 = require("plywood"); const cluster_1 = require("../../../common/models/cluster/cluster"); const functional_1 = require("../../../common/utils/functional/functional"); const general_1 = require("../../../common/utils/general/general"); const module_loader_1 = require("../module-loader/module-loader"); const requester_1 = require("../requester/requester"); const CONNECTION_RETRY_TIMEOUT = 20000; const DRUID_REQUEST_DECORATOR_MODULE_VERSION = 1; function emptyResolve() { return Promise.resolve(null); } function getSourceFromExternal(external) { return String(external.source); } function externalContainsSource(external, source) { return Array.isArray(external.source) ? external.source.indexOf(source) > -1 : String(external.source) === source; } class ClusterManager { constructor(cluster, options) { this.managedExternals = []; this.sourceListRefreshInterval = 0; this.sourceListRefreshTimer = null; this.sourceReintrospectInterval = 0; this.sourceReintrospectTimer = null; this.initialConnectionTimer = null; this.scanSourceList = () => { const { logger, cluster, verbose } = this; if (!cluster_1.shouldScanSources(cluster)) return Promise.resolve(null); logger.log(`Scanning cluster '${cluster.name}' for new sources`); return plywood_1.External.getConstructorFor(cluster.type).getSourceList(this.requester) .then((sources) => { if (verbose) logger.log(`For cluster '${cluster.name}' got sources: [${sources.join(", ")}]`); const introspectionTasks = []; this.managedExternals.forEach(ex => { if (sources.find(src => src === String(ex.external.source)) == null) { logger.log(`Missing source '${String(ex.external.source)}' + " for cluster '${cluster.name}', removing...`); introspectionTasks.push(this.removeManagedExternal(ex)); } }); sources.forEach(source => { const existingExternalsForSource = this.managedExternals.filter(managedExternal => externalContainsSource(managedExternal.external, source)); if (existingExternalsForSource.length) { if (verbose) logger.log(`Cluster '${cluster.name}' already has an external for '${source}' ('${existingExternalsForSource[0].name}')`); if (!this.introspectedSources[source]) { logger.log(`Cluster '${cluster.name}' has never seen '${source}' and will introspect '${existingExternalsForSource[0].name}'`); existingExternalsForSource.forEach(existingExternalForSource => { introspectionTasks.push(this.introspectManagedExternal(existingExternalForSource)); }); } } else { logger.log(`Cluster '${cluster.name}' making external for '${source}'`); const external = cluster_1.makeExternalFromSourceName(source, this.version).attachRequester(this.requester); const newManagedExternal = { name: this.generateExternalName(external), external, autoDiscovered: true }; introspectionTasks.push(this.addManagedExternal(newManagedExternal) .then(() => this.introspectManagedExternal(newManagedExternal))); } }); return Promise.all(introspectionTasks); }, (e) => { logger.error(`Failed to get source list from cluster '${cluster.name}' because: ${e.message}`); }); }; this.introspectSources = () => { const { logger, cluster } = this; logger.log(`Introspecting all sources in cluster '${cluster.name}'`); return plywood_1.External.getConstructorFor(cluster.type).getSourceList(this.requester) .then((sources) => { const introspectionTasks = []; sources.forEach(source => { const existingExternalsForSource = this.managedExternals.filter(managedExternal => externalContainsSource(managedExternal.external, source)); if (existingExternalsForSource.length) { existingExternalsForSource.forEach(existingExternalForSource => { introspectionTasks.push(this.introspectManagedExternal(existingExternalForSource)); }); } }); return Promise.all(introspectionTasks); }, (e) => { logger.error(`Failed to get source list from cluster '${cluster.name}' because: ${e.message}`); }); }; if (!cluster) throw new Error("must have cluster"); this.logger = options.logger; this.verbose = Boolean(options.verbose); this.anchorPath = options.anchorPath; this.cluster = cluster; this.initialConnectionEstablished = false; this.introspectedSources = {}; this.version = cluster.version; this.managedExternals = options.initialExternals || []; this.onExternalChange = options.onExternalChange || emptyResolve; this.onExternalRemoved = options.onExternalRemoved || emptyResolve; this.generateExternalName = options.generateExternalName || getSourceFromExternal; this.requester = this.initRequester(); this.managedExternals.forEach(managedExternal => { managedExternal.external = managedExternal.external.attachRequester(this.requester); }); } init() { const { cluster, logger } = this; if (cluster.sourceListRefreshOnLoad) { logger.log(`Cluster '${cluster.name}' will refresh source list on load`); } if (cluster.sourceReintrospectOnLoad) { logger.log(`Cluster '${cluster.name}' will reintrospect sources on load`); } return this.establishInitialConnection() .then(() => this.introspectSources()) .then(() => this.scanSourceList()); } destroy() { if (this.sourceListRefreshTimer) { clearInterval(this.sourceListRefreshTimer); this.sourceListRefreshTimer = null; } if (this.sourceReintrospectTimer) { clearInterval(this.sourceReintrospectTimer); this.sourceReintrospectTimer = null; } if (this.initialConnectionTimer) { clearTimeout(this.initialConnectionTimer); this.initialConnectionTimer = null; } } addManagedExternal(managedExternal) { this.managedExternals.push(managedExternal); return this.onExternalChange(managedExternal.name, managedExternal.external); } updateManagedExternal(managedExternal, newExternal) { if (managedExternal.external.equals(newExternal)) return null; managedExternal.external = newExternal; return this.onExternalChange(managedExternal.name, managedExternal.external); } removeManagedExternal(managedExternal) { this.managedExternals = this.managedExternals.filter(ext => ext.external !== managedExternal.external); return this.onExternalRemoved(managedExternal.name, managedExternal.external); } initRequester() { const { cluster } = this; const druidRequestDecorator = this.createDruidRequestDecorator(); return requester_1.properRequesterFactory({ cluster, verbose: this.verbose, concurrentLimit: cluster.concurrentLimit || 5, druidRequestDecorator }); } clusterAuthHeaders() { const { auth } = this.cluster; if (general_1.isNil(auth)) return undefined; switch (auth.type) { case "http-basic": { const credentials = `${auth.username}:${auth.password}`; const Authorization = `Basic ${Buffer.from(credentials).toString("base64")}`; return { Authorization }; } } } createDruidRequestDecorator() { const requestDecorator = this.loadRequestDecoratorModule(); const authHeaders = this.clusterAuthHeaders(); if (general_1.isNil(requestDecorator)) { if (general_1.isNil(authHeaders)) { return undefined; } else { return functional_1.constant({ headers: authHeaders }); } } if (general_1.isNil(authHeaders)) return requestDecorator; return (request, context) => { const decoration = requestDecorator(request, context); return Promise.resolve(decoration).then(d => Object.assign(d, authHeaders)); }; } loadRequestDecoratorModule() { const { cluster, logger, anchorPath } = this; if (!cluster.requestDecorator) return undefined; try { logger.log(`Cluster ${cluster.name}: Loading requestDecorator`); const module = module_loader_1.loadModule(cluster.requestDecorator.path, anchorPath); if (module.version !== DRUID_REQUEST_DECORATOR_MODULE_VERSION) { logger.error(`Cluster ${cluster.name}: druidRequestDecorator module has incorrect version`); return undefined; } logger.log(`Cluster ${cluster.name} creating requestDecorator`); return module.druidRequestDecoratorFactory(logger.setLoggerId("DruidRequestDecoratorFactory"), { options: cluster.requestDecorator.options, cluster }); } catch (e) { logger.error(`Cluster ${cluster.name}: Couldn't load druidRequestDecorator module: ${e.message}`); return undefined; } } updateSourceListRefreshTimer() { const { logger, cluster } = this; if (this.sourceListRefreshInterval !== cluster.sourceListRefreshInterval) { this.sourceListRefreshInterval = cluster.sourceListRefreshInterval; if (this.sourceListRefreshTimer) { logger.log(`Clearing sourceListRefresh timer in cluster '${cluster.name}'`); clearInterval(this.sourceListRefreshTimer); this.sourceListRefreshTimer = null; } if (this.sourceListRefreshInterval && cluster_1.shouldScanSources(cluster)) { logger.log(`Setting up sourceListRefresh timer in cluster '${cluster.name}' (every ${this.sourceListRefreshInterval}ms)`); this.sourceListRefreshTimer = setInterval(() => { this.scanSourceList().catch(e => { logger.error(`Cluster '${cluster.name}' encountered and error during SourceListRefresh: ${e.message}`); }); }, this.sourceListRefreshInterval); this.sourceListRefreshTimer.unref(); } } } updateSourceReintrospectTimer() { const { logger, cluster } = this; if (this.sourceReintrospectInterval !== cluster.sourceReintrospectInterval) { this.sourceReintrospectInterval = cluster.sourceReintrospectInterval; if (this.sourceReintrospectTimer) { logger.log(`Clearing sourceReintrospect timer in cluster '${cluster.name}'`); clearInterval(this.sourceReintrospectTimer); this.sourceReintrospectTimer = null; } if (this.sourceReintrospectInterval) { logger.log(`Setting up sourceReintrospect timer in cluster '${cluster.name}' (every ${this.sourceReintrospectInterval}ms)`); this.sourceReintrospectTimer = setInterval(() => { this.introspectSources().catch(e => { logger.error(`Cluster '${cluster.name}' encountered and error during SourceReintrospect: ${e.message}`); }); }, this.sourceReintrospectInterval); this.sourceReintrospectTimer.unref(); } } } establishInitialConnection() { const { logger, verbose, cluster } = this; return new Promise(resolve => { let retryNumber = -1; let lastTryAt; const attemptConnection = () => { retryNumber++; if (retryNumber === 0) { if (verbose) logger.log(`Attempting to connect to cluster '${cluster.name}'`); } else { logger.log(`Re-attempting to connect to cluster '${cluster.name}' (retry ${retryNumber})`); } lastTryAt = Date.now(); plywood_1.External.getConstructorFor(cluster.type) .getVersion(this.requester) .then((version) => { this.onConnectionEstablished(); this.internalizeVersion(version).then(() => resolve(null)); }, (e) => { const msSinceLastTry = Date.now() - lastTryAt; const msToWait = Math.max(1, CONNECTION_RETRY_TIMEOUT - msSinceLastTry); logger.error(`Failed to connect to cluster '${cluster.name}' because: ${e.message} (will retry in ${msToWait}ms)`); this.initialConnectionTimer = setTimeout(attemptConnection, msToWait); }); }; attemptConnection(); }); } onConnectionEstablished() { const { logger, cluster } = this; logger.log(`Connected to cluster '${cluster.name}'`); this.initialConnectionEstablished = true; this.updateSourceListRefreshTimer(); this.updateSourceReintrospectTimer(); } internalizeVersion(version) { if (this.version) return Promise.resolve(null); const { logger, cluster } = this; logger.log(`Cluster '${cluster.name}' is running druid@${version}`); this.version = version; const tasks = this.managedExternals.map(managedExternal => { if (managedExternal.external.version) return Promise.resolve(null); return this.updateManagedExternal(managedExternal, managedExternal.external.changeVersion(version)); }); return Promise.all(tasks).then(functional_1.noop); } introspectManagedExternal(managedExternal) { const { logger, verbose, cluster } = this; if (managedExternal.suppressIntrospection) return Promise.resolve(null); if (verbose) logger.log(`Cluster '${cluster.name}' introspecting '${managedExternal.name}'`); return managedExternal.external.introspect() .then(introspectedExternal => { this.introspectedSources[String(introspectedExternal.source)] = true; return this.updateManagedExternal(managedExternal, introspectedExternal); }, (e) => { logger.error(`Cluster '${cluster.name}' could not introspect '${managedExternal.name}' because: ${e.message}`); }); } refresh() { const { cluster, initialConnectionEstablished } = this; let process = Promise.resolve(null); if (!initialConnectionEstablished) return process; if (cluster.sourceReintrospectOnLoad) { process = process.then(() => this.introspectSources()); } if (cluster.sourceListRefreshOnLoad) { process = process.then(() => this.scanSourceList()); } return process; } } exports.ClusterManager = ClusterManager; //# sourceMappingURL=cluster-manager.js.map