UNPKG

pandora

Version:

A powerful and lightweight application manager for Node.js applications powered by TypeScript.

242 lines (220 loc) 6.53 kB
'use strict'; import {ComplexHandler} from './ComplexHandler'; import Base = require('sdk-base'); import {DAEMON_MESSENGER, SEND_DAEMON_MESSAGE, State} from '../const'; import * as fs from 'fs'; import assert = require('assert'); import messenger from 'pandora-messenger'; import {getDaemonLogger, getAppLogPath} from '../universal/LoggerBroker'; import {ApplicationRepresentation} from '../domain'; import {Monitor} from '../monitor/Monitor'; import {DaemonIntrospection} from './DaemonIntrospection'; const daemonLogger = getDaemonLogger(); /** * Class Daemon */ export class Daemon extends Base { private messengerServer: any; private monitor: Monitor; private introspection: DaemonIntrospection; public state: State; public apps: Map<any, ComplexHandler>; constructor() { super(); this.state = State.pending; this.apps = new Map(); } /** * Start Daemon * @return {Promise<any>} */ async start() { if(this.state === State.complete) { return; } return new Promise((resolve, reject) => { this.messengerServer = messenger.getServer({ name: DAEMON_MESSENGER, }); this.messengerServer.on(SEND_DAEMON_MESSAGE, (message, reply) => { this.handleCommand(message, reply); }); this.messengerServer.ready(() => { return this.startMonitor().then(() => { this.state = State.complete; this.ready(true); resolve(); }).catch(err => { daemonLogger.error(err); process.exit(1); reject(err); }); }); this.handleExit(); }); } /** * Handing all passive exit events, such as process signal, uncaughtException */ handleExit() { process.on('uncaughtException', (err) => { daemonLogger.error(err); this.stop(); }); // SIGTERM AND SIGINT will trigger the exit event. ['SIGQUIT', 'SIGTERM', 'SIGINT'].forEach(sig => { process.once(sig, () => { this.stop(); }); }); process.once('exit', () => { this.stop(); }); } /** * Start an application * * @param {ApplicationRepresentation} applicationRepresentation * @returns {Promise<ComplexHandler>} */ async startApp(applicationRepresentation: ApplicationRepresentation): Promise<ComplexHandler> { // require appName and appDir when app start const appName = applicationRepresentation.appName; const appDir = applicationRepresentation.appDir; assert(appName, `options.appName is required!`); assert(appDir, `options.appDir is required!`); assert(fs.existsSync(appDir), `${appDir} does not exists!`); assert(!this.apps.has(appName), `app[${appName}] has been initialized!`); const complexHandler = new ComplexHandler(applicationRepresentation); await complexHandler.start(); this.apps.set(appName, complexHandler); return complexHandler; } /** * Reload an application * @param appName * @param processName * @return {Promise<ComplexHandler>} */ async reloadApp(appName, processName?): Promise<ComplexHandler> { const complex = this.apps.get(appName); if (!complex) { throw new Error(`${appName} does not exists!`); } await complex.reload(processName); return complex; } /** * stop an application * @param appName * @return {Promise<ComplexHandler>} */ async stopApp(appName): Promise<ComplexHandler> { const complex = this.apps.get(appName); if (!complex) { throw new Error(`${appName} does not exists!`); } await complex.stop(); this.apps.delete(appName); return complex; } /** * stop all the applications * @return {Promise<void>} */ async stopAllApps(): Promise<void> { for (const appName of this.apps.keys()) { await this.stopApp(appName); } } /** * stop an application * @return {Promise<void>} */ async stop(): Promise<void> { this.state = State.stopped; await this.stopAllApps(); daemonLogger.info('daemon is going to stop'); this.messengerServer.close(); await this.stopMonitor(); this.state = State.stopped; } /** * Start the monitor * @return {Promise<void>} */ private async startMonitor(): Promise<void> { if (!this.monitor) { this.monitor = new Monitor(); } return this.monitor.start(); } /** * Stop the monitor * @return {Promise<void>} */ private async stopMonitor(): Promise<void> { if (this.monitor) { return this.monitor.stop(); } } /** * Handle daemon's command invocations * @param message * @param reply */ handleCommand(message, reply) { const command = message.command; const args = message.args; switch (command) { case 'start': this.startApp(args).then(() => { reply({data: `${args.appName} started successfully! log file: ${getAppLogPath(args.appName, 'nodejs_stdout')}`}); }).catch(err => { reply({error: `${args.appName} started failed, ${err && err.toString()}`}); }); break; case 'stopAll': this.stopAllApps().then(() => { reply({data: `all apps stopped successfully!`}); }).catch(err => { reply({error: `all apps stopped failed, ${err && err.toString()}`}); }); break; case 'stopApp': this.stopApp(args.appName).then(() => { reply({data: `${args.appName} stopped successfully!`}); }).catch(err => { reply({error: `${args.appName} stopped failed, ${err && err.toString()}`}); }); break; case 'restart': this.stopApp(args.appName).then(diedApp => { return this.startApp(diedApp.appRepresentation); }).then(() => { reply({data: `${args.appName} restarted successfully!`}); }).catch(err => { reply({error: `${args.appName} restarted failed, ${err && err.toString()}`}); }); break; case 'exit': this.stop().then(() => { process.exit(0); }); break; case 'list': const introspection = this.getIntrospection(); return introspection.listApplication().then((data) => { reply({data}); }).catch((error) => { reply({error}); }); } } public getIntrospection(): DaemonIntrospection { if(!this.introspection) { this.introspection = new DaemonIntrospection(this); } return this.introspection; } }