UNPKG

couchbase-index-manager

Version:
158 lines 5.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Plan = void 0; const tslib_1 = require("tslib"); const chalk_1 = tslib_1.__importDefault(require("chalk")); const lodash_1 = require("lodash"); const index_manager_1 = require("../index-manager"); /** * Adds mutations to a collection already grouped by phase */ function addMutationsByPhase(mutations, currentMutations) { mutations.reduce((accumulator, mutation) => { const phase = mutation.phase - 1; if (!accumulator[phase]) { accumulator[phase] = []; } accumulator[phase].push(mutation); return accumulator; }, currentMutations); return currentMutations; } const defaultOptions = { logger: console, buildDelay: 3000, }; /** * Represents a planned set of mutations for synchronization */ class Plan { constructor(manager, mutations, options) { this.manager = manager; this.options = { ...defaultOptions, ...options }; this.mutations = addMutationsByPhase(mutations, []); } /** * Returns true if the plan is empty */ isEmpty() { return this.mutations.length === 0; } /** * Prints the plan */ print() { if (this.isEmpty()) { this.options.logger.info(chalk_1.default.yellowBright('No mutations to be performed')); return; } this.options.logger.info(); this.options.logger.info('Index sync plan:'); this.options.logger.info(); (0, lodash_1.flatten)(this.mutations).forEach((mutation) => { mutation.print(this.options.logger); // Add blank line this.options.logger.info(); }); } /** * Executes the plan */ async execute() { let errorCount = 0; let skipCount = 0; for (const phase of this.mutations) { if (phase.length <= 0) { continue; } const phaseNum = phase[0].phase; if (errorCount > 0) { // Skip this phase if there are errors this.options.logger.info(chalk_1.default.yellowBright(`Skipping phase ${phaseNum} (${phase.length} tasks)`)); skipCount += phase.length; break; } else { this.options.logger.info(chalk_1.default.greenBright(`Executing phase ${phaseNum}...`)); } for (let i = 0; i < phase.length; i++) { try { await phase[i].execute(this.manager, this.options.logger); } catch (e) { this.options.logger.error(chalk_1.default.redBright(`${e}`)); errorCount++; } } await this.buildIndexes(phase); } if (errorCount === 0) { this.options.logger.info(chalk_1.default.greenBright('Plan completed')); this.options.logger.info(); } else if (skipCount > 0) { const msg = `Plan failed with ${errorCount} errors, ${skipCount} skipped`; throw new Error(msg); } else { const msg = `Plan completed with ${errorCount} errors`; throw new Error(msg); } } /** * Adds mutations to the plan */ addMutation(...mutations) { addMutationsByPhase(mutations, this.mutations); } async buildIndexes(phase) { // Wait 3 seconds for index nodes to synchronize before building // This helps to reduce race conditions // https://github.com/brantburnett/couchbase-index-manager/issues/35 if (this.options.buildDelay > 0) { await new Promise((resolve) => setTimeout(resolve, this.options.buildDelay)); } // Get a list of distinct scopes/collections const collections = [...new Set(phase.map(index => `${index.scope}~${index.collection}`))] .map(str => { const arr = str.split('~'); return { scope: arr[0], collection: arr[1] }; }); // Build each collection separately for (const collection of collections) { if (collection.scope === index_manager_1.DEFAULT_SCOPE && collection.collection === index_manager_1.DEFAULT_COLLECTION) { this.options.logger.info(chalk_1.default.greenBright('Building indexes...')); } else { this.options.logger.info(chalk_1.default.greenBright(`Building indexes on ${collection.scope}.${collection.collection}...`)); } await this.manager.buildDeferredIndexes(collection.scope, collection.collection); const waitOptions = { ...collection, timeoutMs: this.options.buildTimeout }; if (!await this.manager.waitForIndexBuild(waitOptions, this.indexBuildTickHandler.bind(this))) { this.options.logger.warn(chalk_1.default.yellowBright('Some indexes are not online')); } } } /** * When running in Kubernetes and attaching to logs, there is a five * minute timeout if there is no console output. This tick handler * ensures that output continues during that time. */ indexBuildTickHandler(milliseconds) { const secs = milliseconds / 1000; const secsPart = (0, lodash_1.padStart)(Math.floor(secs % 60).toString(10), 2, '0'); const minsPart = Math.floor(secs / 60); this.options.logger.log(chalk_1.default.greenBright(`Building ${minsPart}m${secsPart}s...`)); } } exports.Plan = Plan; //# sourceMappingURL=plan.js.map