nope/lib/cli/runNopeBackend.ts

481 lines
12 KiB
TypeScript
Raw Normal View History

/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @create date 2020-11-11 13:27:58
2021-10-19 08:01:00 +00:00
* @modify date 2021-10-19 09:15:38
* @desc [description]
2021-03-24 06:50:36 +00:00
*
*/
2020-11-23 06:09:31 +00:00
import { ArgumentParser } from "argparse";
import { readFile } from "fs/promises";
import "reflect-metadata";
2021-03-24 06:50:36 +00:00
import {
getLayer,
layerDefaultParameters,
2021-04-12 05:09:47 +00:00
validLayerOrMirror,
2022-01-25 19:44:42 +00:00
validLayers,
2021-09-03 05:42:37 +00:00
} from "../communication/getLayer.nodejs";
import { sleep } from "../helpers/async";
import { generateId } from "../helpers/idMethods";
2022-01-17 17:06:10 +00:00
import { deepClone } from "../helpers/objectMethods";
import { getPackageLoader } from "../loader/getPackageLoader.browser";
2020-11-23 06:09:31 +00:00
import { loadFunctions, loadPackages } from "../loader/loadPackages";
2021-10-18 05:56:24 +00:00
import { generateLogfilePath, useLogFile } from "../logger/fileLogging";
2020-11-23 06:09:31 +00:00
import { getNopeLogger } from "../logger/getLogger";
import { LoggerLevel, LoggerLevels } from "../logger/nopeLogger";
2020-11-23 06:09:31 +00:00
import { setGlobalLoggerLevel } from "../logger/setGlobalLoggerLevel";
2022-03-18 08:06:45 +00:00
import { recordCPUProfile } from "../profiling/index.nodejs";
2021-12-04 07:25:26 +00:00
import {
INopeINopeConnectivityTimeOptions,
2022-01-25 19:44:42 +00:00
ValidDefaultSelectors,
} from "../types/nope";
2020-11-23 06:09:31 +00:00
import { INopePackageLoader } from "../types/nope/nopePackageLoader.interface";
import { NOPELOGO } from "./renderNope";
2022-01-17 17:06:10 +00:00
export interface RunArgs {
file: string;
channel: validLayerOrMirror;
params: string;
// Flag to prevent loading the configuration
skipLoadingConfig: boolean;
// Level of the logger.
log: LoggerLevel;
// The Enable Singletons. Defaults to true
singleton: boolean;
// The default-selector to select the service providers
defaultSelector: ValidDefaultSelectors;
// The default-selector to select the service providers
dispatcherLogLevel: LoggerLevel;
// The default-selector to select the service providers
communicationLogLevel: LoggerLevel;
// Enable File-logging:
logToFile: boolean;
// Delay to wait for system beeing ready.
delay: number;
// Flag to force using the selectors. Might have a performance inpact.
forceUsingSelectors: boolean;
// Define the Timingsparameter
timings: Partial<INopeINopeConnectivityTimeOptions>;
// The Id to use.
id: string;
2022-03-18 08:06:45 +00:00
// Flag to enable profiling. Defaults to false.
profile: boolean;
// Flag to controll whether base-services should be loaded or not.
useBaseServices: boolean;
2022-01-17 17:06:10 +00:00
}
export const DEFAULT_SETTINGS: RunArgs = {
file: "./config/settings.json",
channel: "event",
skipLoadingConfig: false,
params: "not-provided",
log: "debug",
singleton: true,
dispatcherLogLevel: "info",
communicationLogLevel: "info",
delay: 2,
timings: {},
defaultSelector: "first",
forceUsingSelectors: false,
logToFile: false,
2022-01-25 19:44:42 +00:00
id: generateId(),
2022-03-18 08:06:45 +00:00
profile: false,
useBaseServices: false,
2022-01-17 17:06:10 +00:00
};
/**
* Helper Function to Read-In the Arguments used by the
* cli-tool
*
* @return {*}
*/
export async function readInArgs(
additionalArguments: {
help: string;
type: "string" | "number";
name: string | string;
defaultValue?: any;
2022-01-17 17:06:10 +00:00
}[] = [],
forcedArgs: Partial<RunArgs> = {}
): Promise<RunArgs> {
const parser = new ArgumentParser({
// version: "1.0.0",
add_help: true,
2021-12-04 07:25:26 +00:00
description: "Command Line interface, determines the available Packages.",
});
for (const arg of additionalArguments) {
parser.add_argument(arg.name, {
help: arg.help,
default: arg.defaultValue,
2021-12-04 07:25:26 +00:00
type: arg.type,
});
}
parser.add_argument("-f", "--file", {
help: "File containing containing the package definitions.",
default: "./config/settings.json",
type: "str",
2021-12-04 07:25:26 +00:00
dest: "file",
});
parser.add_argument("-c", "--channel", {
help:
"The Communication Channel, which should be used. Possible Values are: " +
// Display all Options:
Object.getOwnPropertyNames(validLayers)
2021-12-04 07:25:26 +00:00
.map((item) => '"' + item + '"')
.join(", "),
default: "event",
type: "str",
2021-12-04 07:25:26 +00:00
dest: "channel",
});
parser.add_argument("-p", "--params", {
help:
"Paramas for the Channel, to connect to. The Following Defaults are used: \n" +
2021-03-24 06:50:36 +00:00
JSON.stringify(layerDefaultParameters, undefined, 4),
default: "not-provided",
type: "str",
2021-12-04 07:25:26 +00:00
dest: "params",
});
parser.add_argument("-s", "--skip-loading-config", {
help: "Flag to prevent loading the elements defined in the settings.json.",
action: "append",
nargs: "?",
2021-12-04 07:25:26 +00:00
dest: "skipLoadingConfig",
});
2021-08-04 12:55:56 +00:00
parser.add_argument("--default-selector", {
2021-12-04 07:25:26 +00:00
help:
"The default-selector to select the service providers. Possible Values are: " +
// Display all Options:
Object.getOwnPropertyNames(ValidDefaultSelectors)
2021-12-04 07:25:26 +00:00
.map((item) => '"' + item + '"')
.join(", "),
default: "first",
type: "str",
2021-12-04 07:25:26 +00:00
dest: "defaultSelector",
});
parser.add_argument("--log-to-file", {
2021-10-18 05:56:24 +00:00
help: "Log will be stored in a logfile.",
action: "append",
nargs: "?",
2021-12-04 07:25:26 +00:00
dest: "logToFile",
2021-10-18 05:56:24 +00:00
});
parser.add_argument("-l", "--log", {
help:
2021-12-04 07:25:26 +00:00
'Specify the Logger Level. Defaults to "info". Valid values are: ' +
LoggerLevels.join(", "),
default: "info",
type: "str",
2021-12-04 07:25:26 +00:00
dest: "log",
});
parser.add_argument("--id", {
help: "Define a custom id to the Dispatcher: " + LoggerLevels.join(", "),
default: generateId({
prestring: "_dispatcher",
useAsVar: true,
}),
type: "str",
dest: "id",
});
parser.add_argument("--dispatcher-log", {
2021-10-17 10:15:30 +00:00
help:
2021-12-04 07:25:26 +00:00
'Specify the Logger Level of the Dispatcher. Defaults to "info". Valid values are: ' +
2021-10-17 10:15:30 +00:00
LoggerLevels.join(", "),
default: "info",
type: "str",
2021-12-04 07:25:26 +00:00
dest: "dispatcherLogLevel",
2021-10-17 10:15:30 +00:00
});
parser.add_argument("--force-selector", {
help: "Forces to use the Selector. Otherwise a smart approach is used, which only enables them if required.",
action: "append",
nargs: "?",
2021-12-04 07:25:26 +00:00
dest: "forceUsingSelectors",
});
parser.add_argument("-d", "--delay", {
2021-12-04 07:25:26 +00:00
help: 'Adds an delay, which will be waited, after the system connected. Parmeter is provided in [s]. Defaults to "2"',
default: 2,
type: "float",
2021-12-04 07:25:26 +00:00
dest: "delay",
});
parser.add_argument("--communication-log", {
2021-10-18 05:56:24 +00:00
help:
2021-12-04 07:25:26 +00:00
'Specify the Logger Level of the Communication. Defaults to "info". Valid values are: ' +
2021-10-18 05:56:24 +00:00
LoggerLevels.join(", "),
default: "info",
type: "str",
2021-12-04 07:25:26 +00:00
dest: "communicationLogLevel",
2021-10-18 05:56:24 +00:00
});
2022-03-18 08:06:45 +00:00
parser.add_argument("--profile", {
help: "Flag to enable Profiling",
action: "append",
nargs: "?",
dest: "profile",
});
parser.add_argument("--noBaseServices", {
help: "Flag to enable prevent the base Services to be loaded",
action: "append",
nargs: "?",
dest: "useBaseServices",
});
2022-01-17 17:06:10 +00:00
const args: RunArgs = parser.parse_args();
if (args.params === "not-provided") {
delete args.params;
}
args.skipLoadingConfig = Array.isArray(args.skipLoadingConfig);
2022-03-18 08:06:45 +00:00
args.profile = Array.isArray(args.profile);
2021-10-18 05:56:24 +00:00
args.logToFile = Array.isArray(args.logToFile);
args.forceUsingSelectors = Array.isArray(args.forceUsingSelectors);
args.useBaseServices = !Array.isArray(args.useBaseServices);
2022-01-17 17:06:10 +00:00
return Object.assign(args, forcedArgs);
}
// Define the Main Function.
// This function is used as cli tool.
2021-01-08 15:58:55 +00:00
export async function runNopeBackend(
2022-01-17 17:06:10 +00:00
_args: Partial<RunArgs> = {}
2021-08-17 15:52:46 +00:00
): Promise<INopePackageLoader> {
let opts: {
2021-03-24 06:50:36 +00:00
params: string | number;
};
2022-01-17 17:06:10 +00:00
// Default Settings
const _defaultSettings: RunArgs = deepClone(DEFAULT_SETTINGS);
// Use a different ID.
_defaultSettings.id = generateId();
2022-01-17 17:06:10 +00:00
const args = Object.assign(_defaultSettings, _args);
if (args.channel === "io-server") {
args.skipLoadingConfig = true;
}
2022-03-18 08:06:45 +00:00
const closeCallbacks = [];
try {
// Try to read in the default config file.
opts = JSON.parse(
await readFile("./nopeconfig.json", {
2021-12-04 07:25:26 +00:00
encoding: "utf-8",
})
);
} catch (error) {
opts = {} as any;
}
if (LoggerLevels.includes(args.log)) {
setGlobalLoggerLevel(args.log);
}
// Define a Logger
const logger = getNopeLogger("starter");
if (args.logToFile) {
const fileName = generateLogfilePath("run");
logger.warn("Using File Logger. Logging to", fileName);
closeCallbacks.push(useLogFile(fileName, 200));
}
2022-03-18 08:06:45 +00:00
if (args.profile) {
logger.warn("Enabled Profiling.");
closeCallbacks.push(recordCPUProfile());
}
if (args.channel === "io-server") {
logger.warn("Running as Server. Wont load any module!");
args.skipLoadingConfig = true;
}
if (!Object.getOwnPropertyNames(validLayers).includes(args.channel)) {
logger.error(
"Invalid Channel. Please use the following values. " +
2021-12-04 07:25:26 +00:00
Object.getOwnPropertyNames(validLayers)
.map((item) => '"' + item + '"')
.join(", ")
);
2022-01-18 07:01:50 +00:00
const error = Error(
"Invalid Channel. Please use the following values. " +
Object.getOwnPropertyNames(validLayers)
.map((item) => '"' + item + '"')
.join(", ")
);
logger.error(error);
throw error;
}
2022-03-18 08:06:45 +00:00
let _closing = false;
2022-07-17 20:26:33 +00:00
const _dispose = (reason, p) => {
2022-03-18 08:06:45 +00:00
if (_closing) {
return;
}
_closing = true;
if (reason) {
// If there is a reason
logger.error("Unhandled Rejection at: Promise", p, "reason:", reason);
logger.error(reason);
} else {
// We should close the Process:
logger.warn("received 'ctrl+c'. Shutting down the Instances");
}
// Exit the Process
const promises = [];
for (const callback of closeCallbacks) {
try {
promises.push(callback());
} catch (e) {
logger.error("During exiting, an error occourd");
logger.error(e);
}
}
// Wait for all Promises to finish.
Promise.all(promises).then(() => {
process.exit();
});
};
// Subscribe to unhandled Reactions.
process.on("unhandledRejection", (reason, p) => _dispose(reason, p));
process.on("SIGINT", () => _dispose());
process.on("SIGTERM", () => _dispose());
process.on("exit", () => {
logger.info("Completed. Goodbye");
});
// Assign the Default Setting for the Channel.
2021-03-24 06:50:36 +00:00
opts.params = layerDefaultParameters[args.channel];
if (args.params != "not-provided") {
try {
2021-08-04 12:55:56 +00:00
try {
// We try to parse the data.
opts.params = JSON.parse(args.params);
} catch (e) {
2021-12-04 07:25:26 +00:00
opts.params = JSON.parse('"' + args.params + '"');
2021-08-04 12:55:56 +00:00
}
} catch (e) {
logger.error(
"Unable to parse the Parameters for the channel. Please use valid JSON!"
);
2021-08-04 12:55:56 +00:00
logger.error(args.params[0]);
logger.error(e);
2022-01-18 07:01:50 +00:00
throw e;
}
}
// If required load all Packages.
if (!args.skipLoadingConfig) {
// Try to load the Modules.
try {
logger.info("loading Functions");
await loadFunctions(args.file);
} catch (e) {
2022-01-17 17:06:10 +00:00
logger.error("Unable to load the Packages defined in " + args.file);
logger.error("Returning without config. ");
args.skipLoadingConfig = true;
}
}
let loader: INopePackageLoader;
try {
2021-09-03 05:42:37 +00:00
loader = getPackageLoader(
{
2021-12-04 07:25:26 +00:00
communicator: getLayer(
args.channel,
opts.params,
args.communicationLogLevel
),
2021-10-17 10:15:30 +00:00
logger: getNopeLogger("dispatcher", args.dispatcherLogLevel),
defaultSelector: args.defaultSelector,
2021-12-04 07:25:26 +00:00
forceUsingSelectors: args.forceUsingSelectors,
2022-01-25 19:44:42 +00:00
id: args.id,
2022-04-20 12:20:48 +00:00
isMaster: args.channel !== "io-server" ? null : false,
2021-09-03 05:42:37 +00:00
},
{
singleton: _args.singleton,
useBaseServices: _args.useBaseServices,
}
2021-09-03 05:42:37 +00:00
);
2022-03-18 08:06:45 +00:00
// Add the Dispatcher
closeCallbacks.push(async () => {
await loader.dispatcher.dispose();
});
2022-03-18 08:06:45 +00:00
// If required load all Packages.
if (!args.skipLoadingConfig) {
// Try to load the Modules.
2022-03-18 08:06:45 +00:00
if (args.delay > 0) {
logger.info(`Waiting ${args.delay} [s] to get all information.`);
await sleep(args.delay * 1000);
}
try {
logger.info("loading Packages");
await loadPackages(loader, args.file, args.delay);
} catch (e) {
logger.error("Unable to load the Packages defined in " + args.file);
}
}
2022-03-18 08:06:45 +00:00
} catch (e) {
getNopeLogger("cli", "info").error("failed to load the Packages", e);
2022-03-18 08:06:45 +00:00
throw e;
}
2021-05-13 11:26:45 +00:00
2021-08-17 15:52:46 +00:00
return loader;
}
2021-01-08 15:58:55 +00:00
/**
* Main Function.
*
* @export
*/
2022-01-17 14:39:48 +00:00
export async function run(
additionalArguments: {
help: string;
type: "string" | "number";
name: string | string;
defaultValue?: any;
2022-01-17 17:06:10 +00:00
}[] = [],
forcedArgs: Partial<RunArgs> = {},
quite = false
2022-01-17 14:39:48 +00:00
) {
2022-01-17 17:06:10 +00:00
if (!quite) {
console.log(NOPELOGO);
console.log("\n\n");
}
2022-01-17 14:39:48 +00:00
2022-01-17 17:06:10 +00:00
const args = await readInArgs(additionalArguments, forcedArgs);
2022-01-17 14:39:48 +00:00
return await runNopeBackend(args);
}
2021-01-08 15:58:55 +00:00
2022-01-17 14:39:48 +00:00
export default run;
2021-01-08 15:58:55 +00:00
// If requested As Main => Perform the Operation.
if (require.main === module) {
2022-01-17 14:39:48 +00:00
run();
2021-01-08 15:58:55 +00:00
}