UNPKG

@mdf.js/service-registry

Version:

MMS - API - Service Registry

347 lines 16 kB
"use strict"; /** * Copyright 2024 Mytra Control S.L. All rights reserved. * * Use of this source code is governed by an MIT-style license that can be found in the LICENSE file * or at https://opensource.org/licenses/MIT. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ServiceRegistry = void 0; const tslib_1 = require("tslib"); const crash_1 = require("@mdf.js/crash"); const logger_1 = require("@mdf.js/logger"); const utils_1 = require("@mdf.js/utils"); const events_1 = tslib_1.__importDefault(require("events")); const control_1 = require("./control"); const observability_1 = require("./observability"); const settings_1 = require("./settings"); const types_1 = require("./types"); class ServiceRegistry extends events_1.default { /** * Create a new instance of the Service Registry * @param bootstrapOptions - Bootstrap settings, define how the Custom and the Service Registry * settings should be loaded. * @param serviceRegistryOptions - Service Registry settings, used as a base for the Service * Registry configuration manager. * @param customSettings - Custom settings provided by the user, used as a base for the Custom * configuration manager. */ constructor(bootstrapOptions, serviceRegistryOptions, customSettings) { super(); /** Resources attached to the service registry observability */ this._resources = []; /** Flag to indicate if the service has performed the bootstrap */ this._booted = false; /** Flag to indicate if the service has started */ this._started = false; /** Return the health information from the observability instance */ this.onHealthCommand = async () => { return this._observability.health; }; /** Return the service stats from the observability instance */ this.onStatsCommand = async () => { return this._observability.metrics; }; /** Return the errors stored in the registry from the observability instance */ this.onErrorsCommand = async () => { return (0, utils_1.deCycle)(this._observability.errors); }; /** Return the custom settings from the configuration manager */ this.onConfigCommand = async () => { return this.settings; }; /** * Perform the finish of the service engine and exit the process * @param signal - The signal received */ this.onFinishCommand = async (signal) => { // Stryker disable next-line all this._logger.warn(`Received ${signal} signal, finishing application engine ...`); try { await this.shutdown(); await this.stop(); } catch (rawError) { const cause = crash_1.Crash.from(rawError); this._logger.crash(cause); } finally { // Stryker disable next-line all this._logger.info(`Application engine finished`); setTimeout(process.exit, types_1.SHUTDOWN_DELAY, signal === 'SIGINT' ? 0 : 1); } }; /** Handle the command event from the OpenC2 consumer */ this.onCommandEvent = (command) => { this.emit('command', command); }; /** * Wrap the start method of the resource to avoid errors * @param resource - the resource to be wrapped */ this.wrappedStart = async (resource) => { if ('start' in resource && typeof resource.start === 'function') { await (0, utils_1.retryBind)(resource.start, resource, [], this.retryOptions); } else { // Stryker disable next-line all this._logger.info(`${resource.name} has not a start method`); await Promise.resolve(); } }; /** * Wrap the stop method of the resource to avoid errors * @param resource - the resource to be wrapped * @returns */ this.wrappedStop = async (resource) => { if ('stop' in resource && typeof resource.stop === 'function') { await (0, utils_1.retryBind)(resource.stop, resource, [], this.retryOptions); } else { // Stryker disable next-line all this._logger.info(`${resource.name} has not a stop method`); await Promise.resolve(); } }; /** Perform the bootstrap of all the service registry resources */ this.bootstrap = async () => { try { if (this._booted) { return; } // Stryker disable next-line all this._logger.info(`Welcome to ${this.identification}`); // Stryker disable next-line all this._logger.info('Bootstrapping application engine ...'); await (0, utils_1.retryBind)(this._observability.start, this._observability, [], this.retryOptions); await (0, utils_1.retryBind)(this._settingsManager.start, this._settingsManager, [], this.retryOptions); const links = JSON.stringify(this._observability.links, null, 2); // Stryker disable next-line all this._logger.info(`Observability engine started, the health information is at: ${links}`); if (this._consumer.instance) { await (0, utils_1.retryBind)(this._consumer.start, this._consumer, [], this.retryOptions); // Stryker disable next-line all this._logger.info('OpenC2 Consumer engine started'); } this._booted = true; } catch (rawError) { const cause = crash_1.Crash.from(rawError); const error = new crash_1.Crash(`Error bootstrapping the application engine: ${cause.message}`, { cause, }); this._logger.crash(error); throw error; } }; /** Perform the shutdown of all the service registry resources */ this.shutdown = async () => { try { if (!this._booted) { return; } // Stryker disable next-line all this._logger.info('Shutting down application engine ...'); if (this._consumer.instance) { await (0, utils_1.retryBind)(this._consumer.stop, this._consumer, [], this.retryOptions); // Stryker disable next-line all this._logger.info('OpenC2 Consumer engine stopped'); } await (0, utils_1.retryBind)(this._observability.stop, this._observability, [], this.retryOptions); await (0, utils_1.retryBind)(this._settingsManager.stop, this._settingsManager, [], this.retryOptions); // Stryker disable next-line all this._logger.info('Observability engine stopped'); this._booted = false; } catch (rawError) { const cause = crash_1.Crash.from(rawError); const error = new crash_1.Crash(`Error shutting down the application engine: ${cause.message}`, { cause, }); // Stryker disable next-line all this._logger.crash(error); throw error; } }; /** Perform the initialization of all the service resources that has been attached */ this.start = async () => { const startedResources = []; try { if (this._started) { return; } if (!this._booted) { await this.bootstrap(); } if (this._settingsManager.isPrimary) { // Stryker disable next-line all this._logger.info('Application resources are not started in the primary cluster node'); this._started = true; return; } // Stryker disable next-line all this._logger.info('Starting application resources ...'); for (const resource of this._resources) { // Stryker disable next-line all this._logger.info(`Starting resource: ${resource.name} ...`); await this.wrappedStart(resource); // Stryker disable next-line all this._logger.info(`... ${resource.name} started`); startedResources.push(resource); } // Stryker disable next-line all this._logger.info('... application resources started'); this._started = true; } catch (rawError) { const cause = crash_1.Crash.from(rawError); const error = new crash_1.Crash(`Error starting the application resources: ${cause.message}`, { cause, }); this._logger.crash(error); // Stryker disable next-line all this._logger.info('Rolling back started resources ...'); for (const resource of startedResources) { // Stryker disable next-line all this._logger.info(`Stopping resource: ${resource.name} ...`); await this.wrappedStop(resource); // Stryker disable next-line all this._logger.info(`... ${resource.name} stopped`); } throw error; } }; /** Perform the stop of all the service resources that has been attached */ this.stop = async () => { try { if (!this._started) { return; } if (this._booted) { await this.shutdown(); } if (this._settingsManager.isPrimary) { // Stryker disable next-line all this._logger.info('Application resources are not stopped in the primary cluster node'); this._started = false; return; } // Stryker disable next-line all this._logger.info('Stopping application resources ...'); for (const resource of this._resources) { // Stryker disable next-line all this._logger.info(`Stopping resource: ${resource.name} ...`); await this.wrappedStop(resource); // Stryker disable next-line all this._logger.info(`... ${resource.name} stopped`); } // Stryker disable next-line all this._logger.info('... application resources stopped'); this._started = false; } catch (rawError) { const cause = crash_1.Crash.from(rawError); const error = new crash_1.Crash(`Error stopping the application resources: ${cause.message}`, { cause, }); // Stryker disable next-line all this._logger.crash(error); throw error; } }; this._settingsManager = new settings_1.SettingsManager(bootstrapOptions, serviceRegistryOptions, customSettings); this._logger = (0, logger_1.SetContext)(new logger_1.Logger(this._settingsManager.name, this._settingsManager.logger), this._settingsManager.name, this._settingsManager.instanceId); this._observability = new observability_1.Observability({ ...this._settingsManager.observability, logger: this._logger, }); this._consumer = new control_1.ControlManager(this._settingsManager.serviceRegistrySettings, this._logger, this.resolverMap); this._observability.attach(this._settingsManager); if (this._consumer.instance && !this._consumer.error && (bootstrapOptions === null || bootstrapOptions === void 0 ? void 0 : bootstrapOptions.consumer)) { this._consumer.on('command', this.onCommandEvent.bind(this)); this._observability.attach(this._consumer.instance); } else if (this._consumer.error) { // Stryker disable next-line all this._logger.warn('OpenC2 Consumer is not available, the service is not able to receive commands'); this._observability.push(this._consumer.error); } process.on('SIGINT', () => this.onFinishCommand('SIGINT')); process.on('SIGTERM', () => this.onFinishCommand('SIGTERM')); } /** @returns Default resolver map for the OpenC2 Consumer interface */ get resolverMap() { if (this._settingsManager.namespace) { return { [`query:${this._settingsManager.namespace}:health`]: this.onHealthCommand.bind(this), [`query:${this._settingsManager.namespace}:stats`]: this.onStatsCommand.bind(this), [`query:${this._settingsManager.namespace}:errors`]: this.onErrorsCommand.bind(this), [`query:${this._settingsManager.namespace}:config`]: this.onConfigCommand.bind(this), [`start:${this._settingsManager.namespace}:resources`]: this.start.bind(this), [`stop:${this._settingsManager.namespace}:resources`]: this.stop.bind(this), [`restart:${this._settingsManager.namespace}:all`]: this.onFinishCommand.bind(this, 'SIGINT'), }; } else { return undefined; } } /** @returns The retry options used for starting resources and service */ get retryOptions() { return { logger: this._logger.crash, ...this._settingsManager.retryOptions, }; } /** @returns Service Register health information */ get errors() { return this._observability.errors; } /** @returns Service Register health information */ get health() { return this._observability.health; } /** @returns Service Register status */ get status() { return this._observability.status; } /** @returns Service Register settings */ get serviceRegistrySettings() { return this._settingsManager.serviceRegistrySettings; } /** @returns Custom settings */ get customSettings() { return this._settingsManager.customSettings; } /** @returns Service settings */ get settings() { return this._settingsManager.settings; } /** @returns The logger instance */ get logger() { return this._logger; } /** @return The application identification string */ get identification() { return `${this.health.name} - ${this.health.serviceId}/${this.health.serviceGroupId} - ${this.health.release} - running with instanceId: [${this.health.instanceId}]`; } /** * Register a resource within the service observability * @param resource - The resource or resources to be register */ register(resource) { const resources = Array.isArray(resource) ? resource : [resource]; for (const entry of resources) { this._resources.push(entry); // Stryker disable next-line all this._logger.debug(`Registering resource: ${entry.name}`); this._observability.attach(entry); } } get(path, defaultValue) { return this._settingsManager.customRegisterConfigManager.get(path, defaultValue); } } exports.ServiceRegistry = ServiceRegistry; //# sourceMappingURL=ServiceRegistry.js.map