aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2020-01-08 22:04:58 -0500
committerSam Wilkins <samwilkins333@gmail.com>2020-01-08 22:04:58 -0500
commitd8361df45515c9724dcf0400a2d9484118b4cd71 (patch)
treee76f64a3aeb5715041573c511d625877b68b1f95
parent20af902684185bb85e38cf97afc951d95411640b (diff)
configuration assignment improvements, exec log and more granularity for identifiers
-rw-r--r--session.config.json8
-rw-r--r--solr-8.3.1/server/solr/dash/data/tlog/tlog.0000000000000000014bin0 -> 56466 bytes
-rw-r--r--src/server/ApiManagers/SearchManager.ts16
-rw-r--r--src/server/DashSession.ts13
-rw-r--r--src/server/Session/session.ts201
-rw-r--r--src/server/Session/session_config_schema.ts72
6 files changed, 208 insertions, 102 deletions
diff --git a/session.config.json b/session.config.json
index 57ca9e3cc..f613dd904 100644
--- a/session.config.json
+++ b/session.config.json
@@ -4,7 +4,9 @@
"server": 1050,
"socket": 4321
},
- "pollingRoute": "/serverHeartbeat",
- "pollingIntervalSeconds": 15,
- "pollingFailureTolerance": 0
+ "polling": {
+ "route": "/serverHeartbeat",
+ "intervalSeconds": 15,
+ "failureTolerance": 0
+ }
} \ No newline at end of file
diff --git a/solr-8.3.1/server/solr/dash/data/tlog/tlog.0000000000000000014 b/solr-8.3.1/server/solr/dash/data/tlog/tlog.0000000000000000014
new file mode 100644
index 000000000..e39ac337f
--- /dev/null
+++ b/solr-8.3.1/server/solr/dash/data/tlog/tlog.0000000000000000014
Binary files differ
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 316ba09ed..4ce12f9f3 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -8,6 +8,7 @@ import { red, cyan, yellow } from "colors";
import RouteSubscriber from "../RouteSubscriber";
import { exec } from "child_process";
import { onWindows } from "..";
+import { get } from "request-promise";
export class SearchManager extends ApiManager {
@@ -68,18 +69,25 @@ export class SearchManager extends ApiManager {
export namespace SolrManager {
+ const command = onWindows ? "solr.cmd" : "solr";
+
export async function SetRunning(status: boolean): Promise<boolean> {
const args = status ? "start" : "stop -p 8983";
- console.log(`Solr management: trying to ${args}`);
- exec(`${onWindows ? "solr.cmd" : "solr"} ${args}`, { cwd: "./solr-8.3.1/bin" }, (error, stdout, stderr) => {
+ console.log(`solr management: trying to ${args}`);
+ exec(`${command} ${args}`, { cwd: "./solr-8.3.1/bin" }, (error, stdout, stderr) => {
if (error) {
+ console.log(red(`solr management error: unable to ${args} server`));
console.log(red(error.message));
- console.log(red(`Solr management error: unable to ${args}`));
}
console.log(cyan(stdout));
console.log(yellow(stderr));
});
- return true;
+ try {
+ await get("http://localhost:8983");
+ return true;
+ } catch {
+ return false;
+ }
}
} \ No newline at end of file
diff --git a/src/server/DashSession.ts b/src/server/DashSession.ts
index 83ce7caaf..47a63c64f 100644
--- a/src/server/DashSession.ts
+++ b/src/server/DashSession.ts
@@ -1,8 +1,8 @@
import { Session } from "./Session/session";
import { Email } from "./ActionUtilities";
-import { red, yellow } from "colors";
+import { red, yellow, cyan } from "colors";
import { SolrManager } from "./ApiManagers/SearchManager";
-import { execSync } from "child_process";
+import { exec } from "child_process";
import { Utils } from "../Utils";
import { WebSocket } from "./Websocket/Websocket";
import { MessageStore } from "./Message";
@@ -48,7 +48,14 @@ export class DashSessionAgent extends Session.AppliedSessionAgent {
return true;
}
});
- monitor.addReplCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] }));
+ 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));
+ }
+ 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 06a076ae4..9a222b2eb 100644
--- a/src/server/Session/session.ts
+++ b/src/server/Session/session.ts
@@ -1,4 +1,4 @@
-import { red, cyan, green, yellow, magenta, blue, white } from "colors";
+import { red, cyan, green, yellow, magenta, blue, white, Color, grey, gray, black } from "colors";
import { on, fork, setupMaster, Worker, isMaster, isWorker } from "cluster";
import { get } from "request-promise";
import { Utils } from "../../Utils";
@@ -20,6 +20,20 @@ import { configurationSchema } from "./session_config_schema";
*/
export namespace Session {
+ type ColorLabel = "yellow" | "red" | "cyan" | "green" | "blue" | "magenta" | "grey" | "gray" | "white" | "black";
+ const colorMapping: Map<ColorLabel, Color> = new Map([
+ ["yellow", yellow],
+ ["red", red],
+ ["cyan", cyan],
+ ["green", green],
+ ["blue", blue],
+ ["magenta", magenta],
+ ["grey", grey],
+ ["gray", gray],
+ ["white", white],
+ ["black", black]
+ ]);
+
export abstract class AppliedSessionAgent {
// the following two methods allow the developer to create a custom
@@ -70,25 +84,50 @@ export namespace Session {
}
+ interface Identifier {
+ text: string;
+ color: ColorLabel;
+ }
+
+ interface Identifiers {
+ master: Identifier;
+ worker: Identifier;
+ exec: Identifier;
+ }
+
interface Configuration {
showServerOutput: boolean;
- masterIdentifier: string;
- workerIdentifier: string;
+ identifiers: Identifiers;
ports: { [description: string]: number };
- pollingRoute: string;
- pollingIntervalSeconds: number;
- pollingFailureTolerance: number;
- [key: string]: any;
+ polling: {
+ route: string;
+ intervalSeconds: number;
+ failureTolerance: number;
+ };
}
- const defaultConfiguration: Configuration = {
+ const defaultConfig: Configuration = {
showServerOutput: false,
- masterIdentifier: yellow("__monitor__:"),
- workerIdentifier: magenta("__server__:"),
+ identifiers: {
+ master: {
+ text: "__monitor__",
+ color: "yellow"
+ },
+ worker: {
+ text: "__server__",
+ color: "magenta"
+ },
+ exec: {
+ text: "__exec__",
+ color: "green"
+ }
+ },
ports: { server: 3000 },
- pollingRoute: "/",
- pollingIntervalSeconds: 30,
- pollingFailureTolerance: 0
+ polling: {
+ route: "/",
+ intervalSeconds: 30,
+ failureTolerance: 0
+ }
};
export type ExitHandler = (reason: Error | null) => void | Promise<void>;
@@ -118,7 +157,7 @@ export namespace Session {
private static count = 0;
private exitHandlers: ExitHandler[] = [];
private readonly notifiers: Monitor.NotifierHooks | undefined;
- private readonly configuration: Configuration;
+ private readonly config: Configuration;
private onMessage: { [message: string]: Monitor.ServerMessageHandler[] | undefined } = {};
private activeWorker: Worker | undefined;
private key: string | undefined;
@@ -209,10 +248,11 @@ export namespace Session {
console.log(this.timestamp(), cyan("initializing session..."));
- this.configuration = this.loadAndValidateConfiguration();
+ this.config = this.loadAndValidateConfiguration();
+
this.initializeSessionKey();
// determines whether or not we see the compilation / initialization / runtime output of each child server process
- setupMaster({ silent: !this.configuration.showServerOutput });
+ setupMaster({ silent: !this.config.showServerOutput });
// handle exceptions in the master thread - there shouldn't be many of these
// the IPC (inter process communication) channel closed exception can't seem
@@ -238,7 +278,6 @@ export namespace Session {
this.spawn();
}
-
/**
* Generates a blue UTC string associated with the time
* of invocation.
@@ -249,7 +288,14 @@ export namespace Session {
* A formatted, identified and timestamped log in color
*/
public log = (...optionalParams: any[]) => {
- console.log(this.timestamp(), this.configuration.masterIdentifier, ...optionalParams);
+ console.log(this.timestamp(), this.config.identifiers.master.text, ...optionalParams);
+ }
+
+ /**
+ * A formatted, identified and timestamped log in color for non-
+ */
+ public execLog = (...optionalParams: any[]) => {
+ console.log(this.timestamp(), this.config.identifiers.exec.text, ...optionalParams);
}
/**
@@ -269,30 +315,24 @@ export namespace Session {
}
/**
- * Builds the repl that allows the following commands to be typed into stdin of the master thread.
+ * At any arbitrary layer of nesting within the configuration objects, any single value that
+ * is not specified by the configuration is given the default counterpart. If, within an object,
+ * one peer is given by configuration and two are not, the one is preserved while the two are given
+ * the default value.
*/
- private initializeRepl = (): Repl => {
- const repl = new Repl({ identifier: () => `${this.timestamp()} ${this.configuration.masterIdentifier}` });
- const boolean = /true|false/;
- const number = /\d+/;
- const letters = /[a-zA-Z]+/;
- repl.registerCommand("exit", [/clean|force/], args => this.killSession("manual exit requested by repl", args[0] === "clean", 0));
- repl.registerCommand("restart", [/clean|force/], args => this.tryKillActiveWorker(args[0] === "clean"));
- repl.registerCommand("set", [letters, "port", number, boolean], args => this.setPort(args[0], Number(args[2]), args[3] === "true"));
- 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"));
- } else {
- if (newPollingIntervalSeconds !== this.configuration.pollingIntervalSeconds) {
- this.configuration.pollingIntervalSeconds = newPollingIntervalSeconds;
- if (args[3] === "true") {
- this.activeWorker?.send({ newPollingIntervalSeconds });
- }
+ private assign = (defaultObject: any, specifiedObject: any, collector: any) => {
+ Array.from(new Set([...Object.keys(defaultObject), ...Object.keys(specifiedObject)])).map(property => {
+ let defaultValue: any, specifiedValue: any;
+ if (specifiedValue = specifiedObject[property]) {
+ if (typeof specifiedValue === "object" && typeof (defaultValue = defaultObject[property]) === "object") {
+ this.assign(defaultValue, specifiedValue, collector[property] = {});
+ } else {
+ collector[property] = specifiedValue;
}
+ } else {
+ collector[property] = defaultObject[property];
}
});
- return repl;
}
/**
@@ -300,34 +340,19 @@ export namespace Session {
* and pass down any variables the pertinent to the child processes as environment variables.
*/
private loadAndValidateConfiguration = (): Configuration => {
+ let config: Configuration;
try {
console.log(this.timestamp(), cyan("validating configuration..."));
- const configuration: Configuration = JSON.parse(readFileSync('./session.config.json', 'utf8'));
+ config = JSON.parse(readFileSync('./session.config.json', 'utf8'));
const options = {
throwError: true,
allowUnknownAttributes: false
};
// ensure all necessary and no excess information is specified by the configuration file
- validate(configuration, configurationSchema, options);
- let formatMaster = true;
- let formatWorker = true;
- Object.keys(defaultConfiguration).forEach(property => {
- if (!configuration[property]) {
- if (property === "masterIdentifier") {
- formatMaster = false;
- } else if (property === "workerIdentifier") {
- formatWorker = false;
- }
- configuration[property] = defaultConfiguration[property];
- }
- });
- if (formatMaster) {
- configuration.masterIdentifier = yellow(configuration.masterIdentifier + ":");
- }
- if (formatWorker) {
- configuration.workerIdentifier = magenta(configuration.workerIdentifier + ":");
- }
- return configuration;
+ validate(config, configurationSchema, options);
+ const results: any = {};
+ this.assign(defaultConfig, config, results);
+ config = results;
} catch (error) {
if (error instanceof ValidationError) {
console.log(red("\nSession configuration failed."));
@@ -337,16 +362,50 @@ export namespace Session {
} else if (error.code === "ENOENT" && error.path === "./session.config.json") {
console.log(cyan("Loading default session parameters..."));
console.log("Consider including a session.config.json configuration file in your project root for customization.");
- return defaultConfiguration;
+ config = { ...defaultConfig };
} else {
console.log(red("\nSession configuration failed."));
console.log("The following unknown error occurred during configuration.");
console.log(error.stack);
process.exit(0);
}
+ } finally {
+ const { identifiers } = config!;
+ Object.keys(identifiers).forEach(key => {
+ const resolved = key as keyof Identifiers;
+ const { text, color } = identifiers[resolved];
+ identifiers[resolved].text = (colorMapping.get(color) || white)(`${text}:`);
+ });
+ return config!;
}
}
+ /**
+ * Builds the repl that allows the following commands to be typed into stdin of the master thread.
+ */
+ private initializeRepl = (): Repl => {
+ const repl = new Repl({ identifier: () => `${this.timestamp()} ${this.config.identifiers.master.text}` });
+ const boolean = /true|false/;
+ const number = /\d+/;
+ const letters = /[a-zA-Z]+/;
+ repl.registerCommand("exit", [/clean|force/], args => this.killSession("manual exit requested by repl", args[0] === "clean", 0));
+ repl.registerCommand("restart", [/clean|force/], args => this.tryKillActiveWorker(args[0] === "clean"));
+ repl.registerCommand("set", [letters, "port", number, boolean], args => this.setPort(args[0], Number(args[2]), args[3] === "true"));
+ 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"));
+ } else {
+ if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
+ this.config.polling.intervalSeconds = newPollingIntervalSeconds;
+ if (args[3] === "true") {
+ this.activeWorker?.send({ newPollingIntervalSeconds });
+ }
+ }
+ }
+ });
+ return repl;
+ }
private executeExitHandlers = async (reason: Error | null) => Promise.all(this.exitHandlers.map(handler => handler(reason)));
@@ -374,7 +433,7 @@ export namespace Session {
*/
private setPort = (port: "server" | "socket" | string, value: number, immediateRestart: boolean): void => {
if (value > 1023 && value < 65536) {
- this.configuration.ports[port] = value;
+ this.config.ports[port] = value;
if (immediateRestart) {
this.tryKillActiveWorker();
}
@@ -389,18 +448,20 @@ export namespace Session {
*/
private spawn = (): void => {
const {
- pollingRoute,
- pollingFailureTolerance,
- pollingIntervalSeconds,
+ polling: {
+ route,
+ failureTolerance,
+ intervalSeconds
+ },
ports
- } = this.configuration;
+ } = this.config;
this.tryKillActiveWorker();
this.activeWorker = fork({
- pollingRoute,
- pollingFailureTolerance,
+ pollingRoute: route,
+ pollingFailureTolerance: failureTolerance,
serverPort: ports.server,
socketPort: ports.socket,
- pollingIntervalSeconds,
+ pollingIntervalSeconds: intervalSeconds,
session_key: this.key
});
this.log(cyan(`spawned new server worker with process id ${this.activeWorker.process.pid}`));
@@ -408,7 +469,7 @@ export namespace Session {
this.activeWorker.on("message", async ({ lifecycle, action }) => {
if (action) {
const { message, args } = action as Monitor.Action;
- console.log(this.timestamp(), `${this.configuration.workerIdentifier} action requested (${cyan(message)})`);
+ console.log(this.timestamp(), `${this.config.identifiers.worker.text} action requested (${cyan(message)})`);
switch (message) {
case "kill":
const { reason, graceful, errorCode } = args;
@@ -432,7 +493,7 @@ export namespace Session {
handlers.forEach(handler => handler({ message, args }));
}
} else if (lifecycle) {
- console.log(this.timestamp(), `${this.configuration.workerIdentifier} lifecycle phase (${lifecycle})`);
+ console.log(this.timestamp(), `${this.config.identifiers.worker.text} lifecycle phase (${lifecycle})`);
}
});
}
diff --git a/src/server/Session/session_config_schema.ts b/src/server/Session/session_config_schema.ts
index 5a85a45e3..e32cf8c6a 100644
--- a/src/server/Session/session_config_schema.ts
+++ b/src/server/Session/session_config_schema.ts
@@ -1,39 +1,67 @@
import { Schema } from "jsonschema";
+const colorPattern = /black|red|green|yellow|blue|magenta|cyan|white|gray|grey/;
+
+const identifierProperties: Schema = {
+ type: "object",
+ properties: {
+ text: {
+ type: "string",
+ minLength: 1
+ },
+ color: {
+ type: "string",
+ pattern: colorPattern
+ }
+ }
+};
+
+const portProperties: Schema = {
+ type: "number",
+ minimum: 1024,
+ maximum: 65535
+};
+
export const configurationSchema: Schema = {
id: "/configuration",
type: "object",
properties: {
+ showServerOutput: { type: "boolean" },
ports: {
type: "object",
properties: {
- server: { type: "number", minimum: 1024, maximum: 65535 },
- socket: { type: "number", minimum: 1024, maximum: 65535 }
+ server: portProperties,
+ socket: portProperties
},
required: ["server"],
additionalProperties: true
},
- pollingRoute: {
- type: "string",
- pattern: /\/[a-zA-Z]*/g
- },
- masterIdentifier: {
- type: "string",
- minLength: 1
- },
- workerIdentifier: {
- type: "string",
- minLength: 1
+ identifiers: {
+ type: "object",
+ properties: {
+ master: identifierProperties,
+ worker: identifierProperties,
+ exec: identifierProperties
+ }
},
- showServerOutput: { type: "boolean" },
- pollingIntervalSeconds: {
- type: "number",
- minimum: 1,
- maximum: 86400
+ polling: {
+ type: "object",
+ additionalProperties: false,
+ properties: {
+ intervalSeconds: {
+ type: "number",
+ minimum: 1,
+ maximum: 86400
+ },
+ route: {
+ type: "string",
+ pattern: /\/[a-zA-Z]*/g
+ },
+ failureTolerance: {
+ type: "number",
+ minimum: 0,
+ }
+ }
},
- pollingFailureTolerance: {
- type: "number",
- minimum: 0,
- }
}
}; \ No newline at end of file