/** * This module is available throughout xyz using `xyz.CONFIG`. It sores important information * about the node and the system. Most of the properties are read-only and should be updated ONLY using * defined methods. * * @module Configuration */ let argParser = require('./../Util/commandline.parser') import { IConfig } from './config.interfaces' import { logger } from './../Log/Logger' import { CONSTANTS } from './Constants' import {ISelfConfValue, ISystemConfValue} from './config.interfaces' let systemConf: ISystemConfValue let selfConf: ISelfConfValue function MergeRecursive (obj1, obj2) { for (let p in obj2) { try { if (obj2[p].constructor === Object) { obj1[p] = MergeRecursive(obj1[p], obj2[p]) } else { obj1[p] = obj2[p] } } catch (e) { obj1[p] = obj2[p] } } return obj1 } export let CONFIG: IConfig = { /** * should be called to inform that a new node has joined the system. * This will log the results automatically. * Note that a middleware or bootstrap function should call a method with the * same name in `serviceRepository` layer. This method should not be called directly. * @method joinNode * @param {String} aNode netId of a node * @return {Number} 1 if ok, -1 if fail. */ joinNode: (aNode) => { if (systemConf.nodes.indexOf(aNode) > -1) { logger.warn(`Node ${aNode} already in systemConf. Passing.`) return -1 } else { logger.info(`A new node {${aNode}} added to systemConf`) systemConf.nodes.push(aNode) return 1 } }, /** * Similar to joinNode(). The only difference is that it will not log a warning * if a node already exists. * Note that a middleware or bootstrap function should call a method with the * same name in `serviceRepository` layer. This method should not be called directly. * @method ensureNode * @param {String} aNode netId of a node * @return {Number} 1 if ok, -1 if fail. */ ensureNode: (aNode) => { if (systemConf.nodes.indexOf(aNode) === -1) { logger.info(`CONFIG :: A new node {${aNode}} added to systemConf`) systemConf.nodes.push(aNode) return 1 } return -1 }, /** * should be called to inform that a node has left the system. * This will log the results automatically. * Note that a middleware or bootstrap function should call a method with the * same name in `serviceRepository` layer. This method should not be called directly. * @method joinNode * @param {String} aNode netId of a node * @return {Number} 1 if ok, -1 if fail. */ kickNode: (aNode) => { let index = systemConf.nodes.indexOf(aNode) if (index > -1) { logger.warn(`node ${aNode} removed from systemConf.`) systemConf.nodes.splice(systemConf.nodes.indexOf(aNode), 1) return 1 } else { logger.warn(`CONFIG :: Attempting to remove ${aNode} which does not exist`) return -1 } }, /** * a Duplicate function to joinNode. The only difference is logging and the fact * that this one takes an array of nodes * @param {String} someNodes a list of nodes */ ensureNodes: (someNodes) => { for (let aNode of someNodes) { if (systemConf.nodes.indexOf(aNode) === -1) { logger.info(`CONFIG :: A new node {${aNode}} added to systemConf`) systemConf.nodes.push(aNode) } } }, /** * will override the configurations passed to XYZ constructor inside internal variable. * Note that the `xyz.js` constructor should call this and there is no other use case for it. * @method setSelfConf * @param {Object} aConf Object with the same format of xyz's constructor `selfConf` * @param {String} cmdLineArgs Manual command line arguments. When this has a value, it will be * used insteat of process.argv[1] */ setSelfConf: (aConf, cmdLineArgs) => { logger.info('CONFIG :: Setting default selfConf') selfConf = CONSTANTS.defaultConfig.selfConf logger.info('CONFIG :: Reading selfConf from user') selfConf = MergeRecursive(selfConf, aConf) logger.info('CONFIG :: Reading selfConf from command line') // TODO use MergeRecursive function to get rid of this shitty code // this is to allow cli admin to inject some args like commandline arguments let args = cmdLineArgs ? cmdLineArgs : argParser.xyzGeneric() for (let arg in args) { logger.verbose(`CONFIG :: overriding selfConf.${arg} from command line value {${args[arg]}}`) let keys = arg.split('.') // ensure that port is Number not String if (keys.length === 3 && keys[2] === 'port') { args[arg] = Number(args[arg]) } // length 1 if (keys.length === 1) { if (keys[0] === 'seed') { selfConf[keys[0]].push(args[arg]) } else if (keys[0] === 'defaultBootstrap') { // could also use eval here if (args[arg] === '0' || args[arg] === 'false') { selfConf[keys[0]] = false } else { selfConf[keys[0]] = true } } else { selfConf[keys[0]] = args[arg] } } else if (keys.length === 2) { if (!selfConf[keys[0]]) selfConf[keys[0]] = {} selfConf[keys[0]][keys[1]] = keys[1] === 'enable' ? eval(args[arg]) : args[arg] } else if (keys.length === 3) { if (!selfConf[keys[0]]) selfConf[keys[0]] = {} if (!selfConf[keys[0]][keys[1]]) selfConf[keys[1]] = {} selfConf[keys[0]][keys[1]][keys[2]] = args[arg] } else { logger.error('CONFIG :: command line arguments with more than three sub-keys are not allowed. passing') } } logger.transports.console.level = selfConf.logLevel logger.debug(`log level set to ${logger.transports.console.level}`) }, /** * Overrides the configuration's `systemConf` inside internal variables * @method setSystemConf * @param {Object} aConf systemConf with the same format like `xyz`'s constructor */ setSystemConf: (aConf) => { logger.info('CONFIG :: Setting default systemConf') systemConf = CONSTANTS.defaultConfig.systemConf logger.info('CONFIG :: reading systemConf from user') systemConf = MergeRecursive(systemConf, aConf) logger.info('CONFIG :: Reading selfConf from command line') let args = argParser.xyzGeneric('--xys-') for (let arg in args) { logger.verbose(`CONFIG :: overriding systemConf.${arg} from command line value {${args[arg]}}`) if (arg === 'node') { if (typeof (args[arg]) === 'object') { systemConf.nodes = systemConf.nodes.concat(args[arg]) } else { systemConf.nodes.push(args[arg]) } } } logger.debug('CONFIG :: Adding self to systemConf by default') if (systemConf.nodes.indexOf(`${selfConf.host}:${selfConf.transport[0].port}`) === -1) { systemConf.nodes.push(`${selfConf.host}:${selfConf.transport[0].port}`) } }, /** * should be called to update the values of `selfConf.transport` when a new server is added. * Should be called from Transport layer, not directly. * @method addServer * @param {Object} aServer An object with keys `type`, `port` and `event`. Similar to `selfConf.transport` */ addServer: (aServer) => { for (let s of selfConf.transport) { if (s.port === aServer.port) { logger.warn(`CONFIG :: cannot add a server with port ${aServer.port} to selfConf. already exists`) return false } } logger.info(`CONFIG :: new server ${JSON.stringify(aServer)} added at runtime to selfConf`) selfConf.transport.push(aServer) }, /** * Will remove a server from the confg object * @method removeServer * @param {Number} aPort port of the server * @return {Boolean} Status of the operation */ removeServer: (aPort) => { for (let s of selfConf.transport) { if (aPort === s.port) { let index = selfConf.transport.indexOf(s) selfConf.transport.splice(index, 1) logger.info(`CONFIG :: removing server ${aPort} from selfConf.`) return true } } logger.error(`CONFIG :: server ${aPort} could not be removed. Not exists`) return false }, /** * Will return the internal systemConf variable * @method getSystemConf * @return {Object} node's `systemConf` */ getSystemConf: () => systemConf, /** * will return the internal selfConf variable * @method getSelfConf * @return {Object} node's `selfConf` */ getSelfConf: () => selfConf, /** * Will cause the system to forget all of the nodes. Should not be used only in * test environemts * @method forget * @return {Null} */ forget: () => { systemConf.nodes = [`${selfConf.host}:${selfConf.transport[0].port}`] } }