diff options
-rw-r--r-- | src/server/DashSession.ts | 27 | ||||
-rw-r--r-- | src/server/Session/session.ts | 55 | ||||
-rw-r--r-- | src/server/repl.ts | 9 |
3 files changed, 60 insertions, 31 deletions
diff --git a/src/server/DashSession.ts b/src/server/DashSession.ts index 47a63c64f..7a1547e2f 100644 --- a/src/server/DashSession.ts +++ b/src/server/DashSession.ts @@ -1,8 +1,7 @@ import { Session } from "./Session/session"; import { Email } from "./ActionUtilities"; -import { red, yellow, cyan } from "colors"; -import { SolrManager } from "./ApiManagers/SearchManager"; -import { exec } from "child_process"; +import { red, yellow } from "colors"; +import { get } from "request-promise"; import { Utils } from "../Utils"; import { WebSocket } from "./Websocket/Websocket"; import { MessageStore } from "./Message"; @@ -26,7 +25,7 @@ export class DashSessionAgent extends Session.AppliedSessionAgent { const content = `The key for this session (started @ ${new Date().toUTCString()}) is ${key}.\n\n${this.signature}`; const failures = await Email.dispatchAll(this.notificationRecipients, "Server Termination Key", content); if (failures) { - failures.map(({ recipient, error: { message } }) => monitor.log(red(`dispatch failure @ ${recipient} (${yellow(message)})`))); + failures.map(({ recipient, error: { message } }) => monitor.mainLog(red(`dispatch failure @ ${recipient} (${yellow(message)})`))); return false; } return true; @@ -42,21 +41,23 @@ export class DashSessionAgent extends Session.AppliedSessionAgent { const content = `${body}\n\n${this.signature}`; const failures = await Email.dispatchAll(this.notificationRecipients, "Dash Web Server Crash", content); if (failures) { - failures.map(({ recipient, error: { message } }) => monitor.log(red(`dispatch failure @ ${recipient} (${yellow(message)})`))); + failures.map(({ recipient, error: { message } }) => monitor.mainLog(red(`dispatch failure @ ${recipient} (${yellow(message)})`))); return false; } return true; } }); - monitor.addReplCommand("pull", [], () => exec("git pull", (error, stdout, stderr) => { - if (error) { - monitor.log(red("unable to pull from version control")); - monitor.log(red(error.message)); + monitor.addReplCommand("pull", [], () => monitor.exec("git pull")); + monitor.addReplCommand("solr", [/start|stop/], async args => { + const command = args[0] === "start" ? "start" : "stop -p 8983"; + await monitor.exec(command, { cwd: "./solr-8.3.1/bin" }); + try { + await get("http://localhost:8983"); + return true; + } catch { + return false; } - stdout.split("\n").forEach(line => line.length && monitor.execLog(cyan(line))); - stderr.split("\n").forEach(line => line.length && monitor.execLog(yellow(line))); - })); - monitor.addReplCommand("solr", [/start|stop/], args => SolrManager.SetRunning(args[0] === "start")); + }); return monitor; } diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts index 9a222b2eb..867d02a0f 100644 --- a/src/server/Session/session.ts +++ b/src/server/Session/session.ts @@ -6,6 +6,7 @@ import Repl, { ReplAction } from "../repl"; import { readFileSync } from "fs"; import { validate, ValidationError } from "jsonschema"; import { configurationSchema } from "./session_config_schema"; +import { exec, ExecOptions } from "child_process"; /** * This namespace relies on NodeJS's cluster module, which allows a parent (master) process to share @@ -191,8 +192,8 @@ export namespace Session { * requests to complete) or immediately. */ public killSession = async (reason: string, graceful = true, errorCode = 0) => { - this.log(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`)); - this.log(`reason: ${(red(reason))}`); + this.mainLog(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`)); + this.mainLog(`reason: ${(red(reason))}`); await this.executeExitHandlers(null); this.tryKillActiveWorker(graceful); process.exit(errorCode); @@ -212,6 +213,26 @@ export namespace Session { this.repl.registerCommand(basename, argPatterns, action); } + public exec = (command: string, options?: ExecOptions) => { + return new Promise<void>(resolve => { + exec(command, { ...options, encoding: "utf8" }, (error, stdout, stderr) => { + if (error) { + this.execLog(red(`unable to execute ${white(command)}`)); + error.message.split("\n").forEach(line => line.length && this.execLog(red(`(error) ${line}`))); + } else { + let outLines: string[], errorLines: string[]; + if ((outLines = stdout.split("\n").filter(line => line.length)).length) { + outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`))); + } + if ((errorLines = stderr.split("\n").filter(line => line.length)).length) { + errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`))); + } + } + resolve(); + }); + }); + } + /** * Add a listener at this message. When the monitor process * receives a message, it will invoke all registered functions. @@ -259,9 +280,9 @@ export namespace Session { // to be caught in a try catch, and is inconsequential, so it is ignored process.on("uncaughtException", ({ message, stack }): void => { if (message !== "Channel closed") { - this.log(red(message)); + this.mainLog(red(message)); if (stack) { - this.log(`uncaught exception\n${red(stack)}`); + this.mainLog(`uncaught exception\n${red(stack)}`); } } }); @@ -269,7 +290,7 @@ export namespace Session { // a helpful cluster event called on the master thread each time a child process exits on("exit", ({ process: { pid } }, code, signal) => { const prompt = `server worker with process id ${pid} has exited with code ${code}${signal === null ? "" : `, having encountered signal ${signal}`}.`; - this.log(cyan(prompt)); + this.mainLog(cyan(prompt)); // to make this a robust, continuous session, every time a child process dies, we immediately spawn a new one this.spawn(); }); @@ -287,14 +308,14 @@ export namespace Session { /** * A formatted, identified and timestamped log in color */ - public log = (...optionalParams: any[]) => { + public mainLog = (...optionalParams: any[]) => { console.log(this.timestamp(), this.config.identifiers.master.text, ...optionalParams); } /** * A formatted, identified and timestamped log in color for non- */ - public execLog = (...optionalParams: any[]) => { + private execLog = (...optionalParams: any[]) => { console.log(this.timestamp(), this.config.identifiers.exec.text, ...optionalParams); } @@ -310,7 +331,7 @@ export namespace Session { this.key = Utils.GenerateGuid(); const success = await this.notifiers.key(this.key); const statement = success ? green("distributed session key to recipients") : red("distribution of session key failed"); - this.log(statement); + this.mainLog(statement); } } @@ -394,7 +415,7 @@ export namespace Session { repl.registerCommand("set", [/polling/, number, boolean], args => { const newPollingIntervalSeconds = Math.floor(Number(args[2])); if (newPollingIntervalSeconds < 0) { - this.log(red("the polling interval must be a non-negative integer")); + this.mainLog(red("the polling interval must be a non-negative integer")); } else { if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) { this.config.polling.intervalSeconds = newPollingIntervalSeconds; @@ -413,11 +434,12 @@ export namespace Session { * Attempts to kill the active worker gracefully, unless otherwise specified. */ private tryKillActiveWorker = (graceful = true): boolean => { - if (!this.activeWorker?.isDead()) { + if (this.activeWorker && !this.activeWorker.isDead()) { + this.mainLog(cyan(`${graceful ? "graceful" : "immediate"}ly killing the active server worker`)); if (graceful) { - this.activeWorker?.send({ manualExit: true }); + this.activeWorker.send({ manualExit: true }); } else { - this.activeWorker?.process.kill(); + this.activeWorker.process.kill(); } return true; } @@ -438,7 +460,7 @@ export namespace Session { this.tryKillActiveWorker(); } } else { - this.log(red(`${port} is an invalid port number`)); + this.mainLog(red(`${port} is an invalid port number`)); } } @@ -464,7 +486,7 @@ export namespace Session { pollingIntervalSeconds: intervalSeconds, session_key: this.key }); - this.log(cyan(`spawned new server worker with process id ${this.activeWorker.process.pid}`)); + this.mainLog(cyan(`spawned new server worker with process id ${this.activeWorker.process.pid}`)); // an IPC message handler that executes actions on the master thread when prompted by the active worker this.activeWorker.on("message", async ({ lifecycle, action }) => { if (action) { @@ -480,7 +502,7 @@ export namespace Session { const { error } = args; const success = await this.notifiers.crash(error); const statement = success ? green("distributed crash notification to recipients") : red("distribution of crash notification failed"); - this.log(statement); + this.mainLog(statement); } break; case "set_port": @@ -492,7 +514,8 @@ export namespace Session { if (handlers) { handlers.forEach(handler => handler({ message, args })); } - } else if (lifecycle) { + } + if (lifecycle) { console.log(this.timestamp(), `${this.config.identifiers.worker.text} lifecycle phase (${lifecycle})`); } }); diff --git a/src/server/repl.ts b/src/server/repl.ts index c4526528e..ad55b6aaa 100644 --- a/src/server/repl.ts +++ b/src/server/repl.ts @@ -109,8 +109,13 @@ export default class Repl { } } if (!length || matched) { - await action(parsed); - this.valid(`${command} ${parsed.join(" ")}`); + const result = action(parsed); + const resolve = () => this.valid(`${command} ${parsed.join(" ")}`); + if (result instanceof Promise) { + result.then(resolve); + } else { + resolve(); + } return; } } |