From c2ff43818236d44801a6fed6490f0edc3903955a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 8 Apr 2014 13:37:07 -0700 Subject: [PATCH] add a CL + JSON settings reader --- domain-server/src/DomainServer.cpp | 196 ++++++------------ domain-server/src/DomainServer.h | 8 +- libraries/shared/src/HifiConfigVariantMap.cpp | 92 ++++++++ libraries/shared/src/HifiConfigVariantMap.h | 19 ++ 4 files changed, 181 insertions(+), 134 deletions(-) create mode 100644 libraries/shared/src/HifiConfigVariantMap.cpp create mode 100644 libraries/shared/src/HifiConfigVariantMap.h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index af3e9a643f..ad531998c3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -47,7 +48,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : setApplicationName("domain-server"); QSettings::setDefaultFormat(QSettings::IniFormat); - _argumentList = arguments(); + _argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); if (optionallySetupDTLS()) { // we either read a certificate and private key or were not passed one, good to load assignments @@ -132,14 +133,14 @@ bool DomainServer::optionallySetupDTLS() { } bool DomainServer::readX509KeyAndCertificate() { - const QString X509_CERTIFICATE_OPTION = "--cert"; - const QString X509_PRIVATE_KEY_OPTION = "--key"; + const QString X509_CERTIFICATE_OPTION = "cert"; + const QString X509_PRIVATE_KEY_OPTION = "key"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; - int certIndex = _argumentList.indexOf(X509_CERTIFICATE_OPTION); - int keyIndex = _argumentList.indexOf(X509_PRIVATE_KEY_OPTION); + QString certPath = _argumentVariantMap.value(X509_CERTIFICATE_OPTION).toString(); + QString keyPath = _argumentVariantMap.value(X509_PRIVATE_KEY_OPTION).toString(); - if (certIndex != -1 && keyIndex != -1) { + if (!certPath.isEmpty() && !keyPath.isEmpty()) { // the user wants to use DTLS to encrypt communication with nodes // let's make sure we can load the key and certificate _x509Credentials = new gnutls_certificate_credentials_t; @@ -148,8 +149,8 @@ bool DomainServer::readX509KeyAndCertificate() { QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV); int gnutlsReturn = gnutls_certificate_set_x509_key_file2(*_x509Credentials, - _argumentList[certIndex + 1].toLocal8Bit().constData(), - _argumentList[keyIndex + 1].toLocal8Bit().constData(), + certPath.toLocal8Bit().constData(), + keyPath.toLocal8Bit().constData(), GNUTLS_X509_FMT_PEM, keyPassphraseString.toLocal8Bit().constData(), 0); @@ -162,7 +163,7 @@ bool DomainServer::readX509KeyAndCertificate() { qDebug() << "Successfully read certificate and private key."; - } else if (certIndex != -1 || keyIndex != -1) { + } else if (!certPath.isEmpty() || !keyPath.isEmpty()) { qDebug() << "Missing certificate or private key. domain-server will now quit."; QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); return false; @@ -193,13 +194,11 @@ void DomainServer::processCreateResponseFromDataServer(const QJsonObject& jsonOb void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { - int argumentIndex = 0; - - const QString CUSTOM_PORT_OPTION = "-p"; + const QString CUSTOM_PORT_OPTION = "port"; unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT; - if ((argumentIndex = _argumentList.indexOf(CUSTOM_PORT_OPTION)) != -1) { - domainServerPort = _argumentList.value(argumentIndex + 1).toUShort(); + if (_argumentVariantMap.contains(CUSTOM_PORT_OPTION)) { + domainServerPort = (unsigned short) _argumentVariantMap.value(CUSTOM_PORT_OPTION).toUInt(); } unsigned short domainServerDTLSPort = 0; @@ -207,21 +206,15 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { if (_isUsingDTLS) { domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT; - const QString CUSTOM_DTLS_PORT_OPTION = "--dtlsPort"; + const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; - if ((argumentIndex = _argumentList.indexOf(CUSTOM_DTLS_PORT_OPTION)) != -1) { - domainServerDTLSPort = _argumentList.value(argumentIndex + 1).toUShort(); + if (_argumentVariantMap.contains(CUSTOM_DTLS_PORT_OPTION)) { + domainServerDTLSPort = (unsigned short) _argumentVariantMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); } } QSet parsedTypes(QSet() << Assignment::AgentType); - parseCommandLineTypeConfigs(_argumentList, parsedTypes); - - const QString CONFIG_FILE_OPTION = "--configFile"; - if ((argumentIndex = _argumentList.indexOf(CONFIG_FILE_OPTION)) != -1) { - QString configFilePath = _argumentList.value(argumentIndex + 1); - readConfigFile(configFilePath, parsedTypes); - } + parseAssignmentConfigs(parsedTypes); populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); @@ -240,130 +233,75 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { addStaticAssignmentsToQueue(); } -void DomainServer::parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes) { +void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { // check for configs from the command line, these take precedence - const QString CONFIG_TYPE_OPTION = "--configType"; - int clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION); + const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; + QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING); - // enumerate all CL config overrides and parse them to files - while (clConfigIndex != -1) { - int clConfigType = argumentList.value(clConfigIndex + 1).toInt(); - if (clConfigType < Assignment::AllTypes && !excludedTypes.contains((Assignment::Type) clConfigIndex)) { - Assignment::Type assignmentType = (Assignment::Type) clConfigType; - createStaticAssignmentsForTypeGivenConfigString((Assignment::Type) assignmentType, - argumentList.value(clConfigIndex + 2)); - excludedTypes.insert(assignmentType); + // scan for assignment config keys + QStringList variantMapKeys = _argumentVariantMap.keys(); + int configIndex = variantMapKeys.indexOf(assignmentConfigRegex); + + while (configIndex != -1) { + // figure out which assignment type this matches + Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap().toInt(); + + QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]]; + + if (mapValue.type() == QVariant::String) { + QJsonDocument deserializedDocument = QJsonDocument::fromJson(mapValue.toString().toUtf8()); + createStaticAssignmentsForType(assignmentType, deserializedDocument.array()); + } else { + createStaticAssignmentsForType(assignmentType, mapValue.toJsonArray()); } - clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION, clConfigIndex + 1); - } -} - -// Attempts to read configuration from specified path -// returns true on success, false otherwise -void DomainServer::readConfigFile(const QString& path, QSet& excludedTypes) { - if (path.isEmpty()) { - // config file not specified - return; - } - - if (!QFile::exists(path)) { - qWarning("Specified configuration file does not exist!"); - return; - } - - QFile configFile(path); - if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning("Can't open specified configuration file!"); - return; - } else { - qDebug() << "Reading configuration from" << path; - } - - QTextStream configStream(&configFile); - QByteArray configStringByteArray = configStream.readAll().toUtf8(); - QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object(); - configFile.close(); - - QSet appendedExcludedTypes = excludedTypes; - - foreach (const QString& rootStringValue, configDocObject.keys()) { - int possibleConfigType = rootStringValue.toInt(); + excludedTypes.insert(assignmentType); - if (possibleConfigType < Assignment::AllTypes - && !excludedTypes.contains((Assignment::Type) possibleConfigType)) { - // this is an appropriate config type and isn't already in our excluded types - // we are good to parse it - Assignment::Type assignmentType = (Assignment::Type) possibleConfigType; - QString configString = readServerAssignmentConfig(configDocObject, rootStringValue); - createStaticAssignmentsForTypeGivenConfigString(assignmentType, configString); - - excludedTypes.insert(assignmentType); - } + configIndex = variantMapKeys.indexOf(assignmentConfigRegex); } } -// find assignment configurations on the specified node name and json object -// returns a string in the form of its equivalent cmd line params -QString DomainServer::readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName) { - QJsonArray nodeArray = jsonObject[nodeName].toArray(); - - QStringList serverConfig; - foreach (const QJsonValue& childValue, nodeArray) { - QString cmdParams; - QJsonObject childObject = childValue.toObject(); - QStringList keys = childObject.keys(); - for (int i = 0; i < keys.size(); i++) { - QString key = keys[i]; - QString value = childObject[key].toString(); - // both cmd line params and json keys are the same - cmdParams += QString("--%1 %2 ").arg(key, value); - } - serverConfig << cmdParams; - } - - // according to split() calls from DomainServer::prepopulateStaticAssignmentFile - // we shold simply join them with semicolons - return serverConfig.join(';'); -} - void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) { qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash."; _staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment)); } -void DomainServer::createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString) { +void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray) { // we have a string for config for this type qDebug() << "Parsing command line config for assignment type" << type; - QStringList multiConfigList = configString.split(";", QString::SkipEmptyParts); + int configCounter = 0; - const QString ASSIGNMENT_CONFIG_POOL_REGEX = "--pool\\s*([\\w-]+)"; - QRegExp poolRegex(ASSIGNMENT_CONFIG_POOL_REGEX); - - // read each config to a payload for this type of assignment - for (int i = 0; i < multiConfigList.size(); i++) { - QString config = multiConfigList.at(i); - - // check the config string for a pool - QString assignmentPool; - - int poolIndex = poolRegex.indexIn(config); - - if (poolIndex != -1) { - assignmentPool = poolRegex.cap(1); + foreach(const QJsonValue& jsonValue, configArray) { + if (jsonValue.isObject()) { + QJsonObject jsonObject = jsonValue.toObject(); - // remove the pool from the config string, the assigned node doesn't need it - config.remove(poolIndex, poolRegex.matchedLength()); + // check the config string for a pool + const QString ASSIGNMENT_POOL_KEY = "pool"; + QString assignmentPool; + + QJsonValue poolValue = jsonObject[ASSIGNMENT_POOL_KEY]; + if (!poolValue.isUndefined()) { + assignmentPool = poolValue.toString(); + + jsonObject.remove(ASSIGNMENT_POOL_KEY); + } + + ++configCounter; + qDebug() << "Type" << type << "config" << configCounter << "=" << jsonObject; + + Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool); + + // setup the payload as a semi-colon separated list of key = value + QStringList payloadStringList; + foreach(const QString& payloadKey, jsonObject.keys()) { + payloadStringList << QString("%1=%2").arg(payloadKey).arg(jsonObject[payloadKey].toString()); + } + + configAssignment->setPayload(payloadStringList.join(';').toUtf8()); + + addStaticAssignmentToAssignmentHash(configAssignment); } - - qDebug("Type %d config[%d] = %s", type, i, config.toLocal8Bit().constData()); - - Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool); - - configAssignment->setPayload(config.toUtf8()); - - addStaticAssignmentToAssignmentHash(configAssignment); } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 78e48f1468..2f570fe5ad 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -64,11 +64,9 @@ private: void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, const NodeSet& nodeInterestList); - void parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes); - void readConfigFile(const QString& path, QSet& excludedTypes); - QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName); + void parseAssignmentConfigs(QSet& excludedTypes); void addStaticAssignmentToAssignmentHash(Assignment* newAssignment); - void createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString); + void createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray); void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes); SharedAssignmentPointer matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType); @@ -85,7 +83,7 @@ private: QHash _staticAssignmentHash; QQueue _assignmentQueue; - QStringList _argumentList; + QVariantMap _argumentVariantMap; bool _isUsingDTLS; gnutls_certificate_credentials_t* _x509Credentials; diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp new file mode 100644 index 0000000000..f0dc012ffa --- /dev/null +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -0,0 +1,92 @@ +// +// HifiConfigVariantMap.cpp +// hifi +// +// Created by Stephen Birarda on 2014-04-08. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include +#include +#include +#include + +#include "HifiConfigVariantMap.h" + +QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringList& argumentList) { + + QVariantMap mergedMap; + + // Add anything in the CL parameter list to the variant map. + // Take anything with a dash in it as a key, and the values after it as the value. + + const QString DASHED_KEY_REGEX_STRING = "(^-{1,2})([\\w-]+)"; + QRegExp dashedKeyRegex(DASHED_KEY_REGEX_STRING); + + int keyIndex = argumentList.indexOf(dashedKeyRegex); + int nextKeyIndex = 0; + + // check if there is a config file to read where we can pull config info not passed on command line + const QString CONFIG_FILE_OPTION = "--config"; + + while (keyIndex != -1) { + if (argumentList[keyIndex] != CONFIG_FILE_OPTION) { + // we have a key - look forward to see how many values associate to it + QString key = dashedKeyRegex.cap(2); + + nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); + + if (nextKeyIndex == keyIndex + 1) { + // there's no value associated with this option, it's a boolean + // so add it to the variant map with NULL as value + mergedMap.insertMulti(key, QVariant()); + } else { + int maxIndex = (nextKeyIndex == -1) ? argumentList.size() : nextKeyIndex; + + // there's at least one value associated with the option + // pull the first value to start + QString value = argumentList[keyIndex + 1]; + + // for any extra values, append them, with a space, to the value string + for (int i = keyIndex + 2; i < maxIndex; i++) { + value += " " + argumentList[i]; + } + + // add the finalized value to the merged map + mergedMap.insert(key, value); + } + + keyIndex = nextKeyIndex; + } else { + keyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); + } + } + + int configIndex = argumentList.indexOf(CONFIG_FILE_OPTION); + + if (configIndex != -1) { + // we have a config file - try and read it + QString configFilePath = argumentList[configIndex + 1]; + QFile configFile(configFilePath); + + if (configFile.exists()) { + configFile.open(QIODevice::ReadOnly); + + QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll()); + QJsonObject rootObject = configDocument.object(); + + // enumerate the keys of the configDocument object + foreach(const QString& key, rootObject.keys()) { + + if (!mergedMap.contains(key)) { + // no match in existing list, add it + mergedMap.insert(key, QVariant(rootObject[key])); + } + } + } + } + + return mergedMap; +} diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h new file mode 100644 index 0000000000..c652278b0d --- /dev/null +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -0,0 +1,19 @@ +// +// HifiConfigVariantMap.h +// hifi +// +// Created by Stephen Birarda on 2014-04-08. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__HifiConfigVariantMap__ +#define __hifi__HifiConfigVariantMap__ + +#include + +class HifiConfigVariantMap { +public: + static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); +}; + +#endif /* defined(__hifi__HifiConfigVariantMap__) */