diff options
| author | bob <bcz@cs.brown.edu> | 2020-01-06 09:42:11 -0500 |
|---|---|---|
| committer | bob <bcz@cs.brown.edu> | 2020-01-06 09:42:11 -0500 |
| commit | df12bd5432f24796de25f9aa85e278eae944edf7 (patch) | |
| tree | a3d4916891e0b6860e18c547cbdb23fcb620e947 /src/server/session_manager | |
| parent | 8ae1d973896f8629e5d2030f756b7b63df530ad2 (diff) | |
| parent | e4d9fd09f6ede20e79d58119d239bb7a3a7dae25 (diff) | |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web
Diffstat (limited to 'src/server/session_manager')
| -rw-r--r-- | src/server/session_manager/config.ts | 33 | ||||
| -rw-r--r-- | src/server/session_manager/input_manager.ts | 101 | ||||
| -rw-r--r-- | src/server/session_manager/logs/current_daemon_pid.log | 1 | ||||
| -rw-r--r-- | src/server/session_manager/logs/current_server_pid.log | 1 | ||||
| -rw-r--r-- | src/server/session_manager/logs/current_session_manager_pid.log | 1 | ||||
| -rw-r--r-- | src/server/session_manager/session_manager.ts | 201 |
6 files changed, 0 insertions, 338 deletions
diff --git a/src/server/session_manager/config.ts b/src/server/session_manager/config.ts deleted file mode 100644 index ebbd999c6..000000000 --- a/src/server/session_manager/config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { resolve } from 'path'; -import { yellow } from "colors"; - -export const latency = 10; -export const ports = [1050, 4321]; -export const onWindows = process.platform === "win32"; -export const heartbeat = `http://localhost:1050/serverHeartbeat`; -export const recipient = "samuel_wilkins@brown.edu"; -export const { pid, platform } = process; - -/** - * Logging - */ -export const identifier = yellow("__session_manager__:"); - -/** - * Paths - */ -export const logPath = resolve(__dirname, "./logs"); -export const crashPath = resolve(logPath, "./crashes"); - -/** - * State - */ -export enum SessionState { - STARTING = "STARTING", - INITIALIZED = "INITIALIZED", - LISTENING = "LISTENING", - AUTOMATICALLY_RESTARTING = "CRASH_RESTARTING", - MANUALLY_RESTARTING = "MANUALLY_RESTARTING", - EXITING = "EXITING", - UPDATING = "UPDATING" -}
\ No newline at end of file diff --git a/src/server/session_manager/input_manager.ts b/src/server/session_manager/input_manager.ts deleted file mode 100644 index a95e6baae..000000000 --- a/src/server/session_manager/input_manager.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { createInterface, Interface } from "readline"; -import { red } from "colors"; - -export interface Configuration { - identifier: string; - onInvalid?: (culprit?: string) => string | string; - isCaseSensitive?: boolean; -} - -export interface Registration { - argPattern: RegExp[]; - action: (parsedArgs: IterableIterator<string>) => any | Promise<any>; -} - -export default class InputManager { - private identifier: string; - private onInvalid: ((culprit?: string) => string) | string; - private isCaseSensitive: boolean; - private commandMap = new Map<string, Registration[]>(); - public interface: Interface; - private busy = false; - private keys: string | undefined; - - constructor({ identifier: prompt, onInvalid, isCaseSensitive }: Configuration) { - this.identifier = prompt; - this.onInvalid = onInvalid || this.usage; - this.isCaseSensitive = isCaseSensitive ?? true; - this.interface = createInterface(process.stdin, process.stdout).on('line', this.considerInput); - } - - private usage = () => { - const resolved = this.keys; - if (resolved) { - return resolved; - } - const members: string[] = []; - const keys = this.commandMap.keys(); - let next: IteratorResult<string>; - while (!(next = keys.next()).done) { - members.push(next.value); - } - return `${this.identifier} commands: { ${members.sort().join(", ")} }`; - } - - public registerCommand = (basename: string, argPattern: RegExp[], action: any | Promise<any>) => { - const existing = this.commandMap.get(basename); - const registration = { argPattern, action }; - if (existing) { - existing.push(registration); - } else { - this.commandMap.set(basename, [registration]); - } - } - - private invalid = (culprit?: string) => { - console.log(red(typeof this.onInvalid === "string" ? this.onInvalid : this.onInvalid(culprit))); - this.busy = false; - } - - private considerInput = async (line: string) => { - if (this.busy) { - console.log(red("Busy")); - return; - } - this.busy = true; - line = line.trim(); - if (this.isCaseSensitive) { - line = line.toLowerCase(); - } - const [command, ...args] = line.split(/\s+/g); - if (!command) { - return this.invalid(); - } - const registered = this.commandMap.get(command); - if (registered) { - const { length } = args; - const candidates = registered.filter(({ argPattern: { length: count } }) => count === length); - for (const { argPattern, action } of candidates) { - const parsed: string[] = []; - let matched = false; - if (length) { - for (let i = 0; i < length; i++) { - let matches: RegExpExecArray | null; - if ((matches = argPattern[i].exec(args[i])) === null) { - break; - } - parsed.push(matches[0]); - } - matched = true; - } - if (!length || matched) { - await action(parsed[Symbol.iterator]()); - this.busy = false; - return; - } - } - } - this.invalid(command); - } - -}
\ No newline at end of file diff --git a/src/server/session_manager/logs/current_daemon_pid.log b/src/server/session_manager/logs/current_daemon_pid.log deleted file mode 100644 index 557e3d7c3..000000000 --- a/src/server/session_manager/logs/current_daemon_pid.log +++ /dev/null @@ -1 +0,0 @@ -26860 diff --git a/src/server/session_manager/logs/current_server_pid.log b/src/server/session_manager/logs/current_server_pid.log deleted file mode 100644 index 85fdb7ae0..000000000 --- a/src/server/session_manager/logs/current_server_pid.log +++ /dev/null @@ -1 +0,0 @@ -54649 created @ 2019-12-14T08:04:42.391Z diff --git a/src/server/session_manager/logs/current_session_manager_pid.log b/src/server/session_manager/logs/current_session_manager_pid.log deleted file mode 100644 index 75c23b35a..000000000 --- a/src/server/session_manager/logs/current_session_manager_pid.log +++ /dev/null @@ -1 +0,0 @@ -54643 diff --git a/src/server/session_manager/session_manager.ts b/src/server/session_manager/session_manager.ts deleted file mode 100644 index 6084d9a77..000000000 --- a/src/server/session_manager/session_manager.ts +++ /dev/null @@ -1,201 +0,0 @@ -import * as request from "request-promise"; -import { log_execution, pathFromRoot } from "../ActionUtilities"; -import { red, yellow, cyan, green, Color } from "colors"; -import * as nodemailer from "nodemailer"; -import { MailOptions } from "nodemailer/lib/json-transport"; -import { writeFileSync, existsSync, mkdirSync } from "fs"; -import { resolve } from 'path'; -import { ChildProcess, exec, execSync } from "child_process"; -import InputManager from "./input_manager"; -import { identifier, logPath, crashPath, onWindows, pid, ports, heartbeat, recipient, latency, SessionState } from "./config"; -const killport = require("kill-port"); - -process.on('SIGINT', endPrevious); -let state: SessionState = SessionState.STARTING; -const is = (...reference: SessionState[]) => reference.includes(state); -const set = (reference: SessionState) => state = reference; - - - -const { registerCommand } = new InputManager({ identifier }); - -registerCommand("restart", [], async () => { - set(SessionState.MANUALLY_RESTARTING); - identifiedLog(cyan("Initializing manual restart...")); - await endPrevious(); -}); - -registerCommand("exit", [], exit); - -async function exit() { - set(SessionState.EXITING); - identifiedLog(cyan("Initializing session end")); - await endPrevious(); - identifiedLog("Cleanup complete. Exiting session...\n"); - execSync(killAllCommand()); -} - -registerCommand("update", [], async () => { - set(SessionState.UPDATING); - identifiedLog(cyan("Initializing server update from version control...")); - await endPrevious(); - await new Promise<void>(resolve => { - exec(updateCommand(), error => { - if (error) { - identifiedLog(red(error.message)); - } - resolve(); - }); - }); - await exit(); -}); - -registerCommand("state", [], () => identifiedLog(state)); - -if (!existsSync(logPath)) { - mkdirSync(logPath); -} -if (!existsSync(crashPath)) { - mkdirSync(crashPath); -} - -function addLogEntry(message: string, color: Color) { - const formatted = color(`${message} ${timestamp()}.`); - identifiedLog(formatted); - // appendFileSync(resolve(crashPath, `./session_crashes_${new Date().toISOString()}.log`), `${formatted}\n`); -} - -function identifiedLog(message?: any, ...optionalParams: any[]) { - console.log(identifier, message, ...optionalParams); -} - -if (!["win32", "darwin"].includes(process.platform)) { - identifiedLog(red("Invalid operating system: this script is supported only on Mac and Windows.")); - process.exit(1); -} - -const windowsPrepend = (command: string) => `"C:\\Program Files\\Git\\git-bash.exe" -c "${command}"`; -const macPrepend = (command: string) => `osascript -e 'tell app "Terminal"\ndo script "cd ${pathFromRoot()} && ${command}"\nend tell'`; - -function updateCommand() { - const command = "git pull && npm install"; - if (onWindows) { - return windowsPrepend(command); - } - return macPrepend(command); -} - -function startServerCommand() { - const command = "npm run start-release"; - if (onWindows) { - return windowsPrepend(command); - } - return macPrepend(command); -} - -function killAllCommand() { - if (onWindows) { - return "taskkill /f /im node.exe"; - } - return "killall -9 node"; -} - -identifiedLog("Initializing session..."); - -writeLocalPidLog("session_manager", pid); - -function writeLocalPidLog(filename: string, contents: any) { - const path = `./logs/current_${filename}_pid.log`; - identifiedLog(cyan(`${contents} written to ${path}`)); - writeFileSync(resolve(__dirname, path), `${contents}\n`); -} - -function timestamp() { - return `@ ${new Date().toISOString()}`; -} - -async function endPrevious() { - identifiedLog(yellow("Cleaning up previous connections...")); - current_backup?.kill("SIGKILL"); - await Promise.all(ports.map(port => { - const task = killport(port, 'tcp'); - return task.catch((error: any) => identifiedLog(red(error))); - })); - identifiedLog(yellow("Done. Any failures will be printed in red immediately above.")); -} - -let current_backup: ChildProcess | undefined = undefined; - -async function checkHeartbeat() { - const listening = is(SessionState.LISTENING); - let error: any; - try { - listening && process.stdout.write(`${identifier} 👂 `); - await request.get(heartbeat); - listening && console.log('⇠💚'); - if (!listening) { - addLogEntry(is(SessionState.INITIALIZED) ? "Server successfully started" : "Backup server successfully restarted", green); - set(SessionState.LISTENING); - } - } catch (e) { - listening && console.log("⇠💔\n"); - error = e; - } finally { - if (error && !is(SessionState.AUTOMATICALLY_RESTARTING, SessionState.INITIALIZED, SessionState.UPDATING)) { - if (is(SessionState.STARTING)) { - set(SessionState.INITIALIZED); - } else if (is(SessionState.MANUALLY_RESTARTING)) { - set(SessionState.AUTOMATICALLY_RESTARTING); - } else { - set(SessionState.AUTOMATICALLY_RESTARTING); - addLogEntry("Detected a server crash", red); - identifiedLog(red(error.message)); - await endPrevious(); - await log_execution({ - startMessage: identifier + " Sending crash notification email", - endMessage: ({ error, result }) => { - const success = error === null && result === true; - return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; - }, - action: async () => notify(error || "Hmm, no error to report..."), - color: cyan - }); - identifiedLog(green("Initiating server restart...")); - } - current_backup = exec(startServerCommand(), err => identifiedLog(err?.message || is(SessionState.INITIALIZED) ? "Spawned initial server." : "Previous server process exited.")); - writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); - } - setTimeout(checkHeartbeat, 1000 * latency); - } -} - -function emailText(error: any) { - return [ - `Hey ${recipient.split("@")[0]},`, - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `Location: ${heartbeat}\nError: ${error}`, - "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." - ].join("\n\n"); -} - -async function notify(error: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: recipient, - from: 'brownptcdash@gmail.com', - subject: 'Dash Server Crash', - text: emailText(error) - } as MailOptions; - return new Promise<boolean>(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); - }); -} - -identifiedLog(yellow(`After initialization, will poll server heartbeat repeatedly...\n`)); -checkHeartbeat();
\ No newline at end of file |
