blob: cc5e33e2c1fe8adece7f8986d0f05d830c96570e [file] [log] [blame]
/*
Copyright (c) 2018-2019 Nokia.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* This source code is part of the near-RT RIC (RAN Intelligent Controller)
* platform project (RICP).
*/
#include "private/abort.hpp"
#include "private/configurationreader.hpp"
#include <boost/property_tree/json_parser.hpp>
#include <sdl/exception.hpp>
#include "private/createlogger.hpp"
#include "private/databaseconfiguration.hpp"
#include "private/logger.hpp"
#include "private/namespaceconfigurations.hpp"
#include "private/namespacevalidator.hpp"
#include "private/system.hpp"
using namespace shareddatalayer;
namespace
{
template <typename T>
T get(const boost::property_tree::ptree& ptree, const std::string& param, const std::string& sourceName)
{
try
{
return ptree.get<T>(param);
}
catch (const boost::property_tree::ptree_bad_path&)
{
std::ostringstream os;
os << "Configuration error in " << sourceName << ": "
<< "missing \"" << param << '\"';
throw Exception(os.str());
}
catch (const boost::property_tree::ptree_bad_data& e)
{
std::ostringstream os;
os << "Configuration error in " << sourceName << ": "
<< "invalid \"" << param << "\": \"" << e.data<boost::property_tree::ptree::data_type>() << '\"';
throw Exception(os.str());
}
}
void validateAndSetDbType(const std::string& type, DatabaseConfiguration& databaseConfiguration,
const std::string& sourceName)
{
try
{
databaseConfiguration.checkAndApplyDbType(type);
}
catch (const std::exception& e)
{
std::ostringstream os;
os << "Configuration error in " << sourceName << ": "
<< e.what();
throw Exception(os.str());
}
}
void validateAndSetDbServerAddress(const std::string& address, DatabaseConfiguration& databaseConfiguration,
const std::string& sourceName)
{
try
{
databaseConfiguration.checkAndApplyServerAddress(address);
}
catch (const std::exception& e)
{
std::ostringstream os;
os << "Configuration error in " << sourceName << ": "
<< "invalid \"address\": \"" << address << "\" " << e.what();
throw Exception(os.str());
}
}
void parseDatabaseServerConfiguration(DatabaseConfiguration& databaseConfiguration,
const boost::property_tree::ptree& ptree,
const std::string& sourceName)
{
const auto address(get<std::string>(ptree, "address", sourceName));
validateAndSetDbServerAddress(address, databaseConfiguration, sourceName);
}
void parseDatabaseServersConfiguration(DatabaseConfiguration& databaseConfiguration,
const boost::property_tree::ptree& ptree,
const std::string& sourceName)
{
const auto servers(ptree.get_child_optional("servers"));
if (servers)
for(const auto& server : *servers)
parseDatabaseServerConfiguration(databaseConfiguration, server.second, sourceName);
else
{
std::ostringstream os;
os << "Configuration error in " << sourceName << ": "
<< "missing \"servers\"";
throw Exception(os.str());
}
}
void parseDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration,
const boost::property_tree::ptree& ptree,
const std::string& sourceName)
{
const auto type(get<std::string>(ptree, "type", sourceName));
validateAndSetDbType(type, databaseConfiguration, sourceName);
parseDatabaseServersConfiguration(databaseConfiguration, ptree, sourceName);
}
void parseDatabaseConfigurationTree(DatabaseConfiguration& databaseConfiguration,
const boost::optional<boost::property_tree::ptree>& databaseConfigurationPtree,
const std::string& sourceName)
{
if (databaseConfigurationPtree)
parseDatabaseConfiguration(databaseConfiguration, *databaseConfigurationPtree, sourceName);
}
void parseDatabaseServersConfigurationFromString(DatabaseConfiguration& databaseConfiguration,
const std::string& serverConfiguration,
const std::string& sourceName)
{
size_t base(0);
auto done(false);
do
{
auto split = serverConfiguration.find(',', base);
done = std::string::npos == split;
validateAndSetDbServerAddress(serverConfiguration.substr(base, done ? std::string::npos : split-base),
databaseConfiguration,
sourceName);
base = split+1;
} while (!done);
}
void validateNamespacePrefix(const std::string& prefix,
const std::string& sourceName)
{
if (!isValidNamespaceSyntax(prefix))
{
std::ostringstream os;
os << "Configuration error in " << sourceName << ": "
<< "\"namespacePrefix\": \"" << prefix << "\""
<< " contains some of these disallowed characters: "
<< getDisallowedCharactersInNamespace();
throw Exception(os.str());
}
}
void validateEnableNotifications(bool enableNotifications, bool useDbBackend,
const std::string& sourceName)
{
if (enableNotifications && !useDbBackend)
{
std::ostringstream os;
os << "Configuration error in " << sourceName << ": "
<< "\"enableNotifications\" cannot be true, when \"useDbBackend\" is false";
throw Exception(os.str());
}
}
void parseNsConfiguration(NamespaceConfigurations& namespaceConfigurations,
const std::string& namespacePrefix,
const boost::property_tree::ptree& ptree,
const std::string& sourceName)
{
const auto useDbBackend(get<bool>(ptree, "useDbBackend", sourceName));
const auto enableNotifications(get<bool>(ptree, "enableNotifications", sourceName));
validateNamespacePrefix(namespacePrefix, sourceName);
validateEnableNotifications(enableNotifications, useDbBackend, sourceName);
namespaceConfigurations.addNamespaceConfiguration({namespacePrefix, useDbBackend, enableNotifications, sourceName});
}
void parseNsConfigurationMap(NamespaceConfigurations& namespaceConfigurations,
std::unordered_map<std::string, std::pair<boost::property_tree::ptree, std::string>>& namespaceConfigurationMap)
{
for (const auto &namespaceConfigurationMapItem : namespaceConfigurationMap )
parseNsConfiguration(namespaceConfigurations, namespaceConfigurationMapItem.first, namespaceConfigurationMapItem.second.first, namespaceConfigurationMapItem.second.second);
}
}
ConfigurationReader::ConfigurationReader(std::shared_ptr<Logger> logger):
ConfigurationReader(getDefaultConfDirectories(), System::getSystem(), logger)
{
}
ConfigurationReader::ConfigurationReader(const Directories& directories,
System& system,
std::shared_ptr<Logger> logger):
dbHostEnvVariableName(DB_HOST_ENV_VAR_NAME),
dbHostEnvVariableValue({}),
dbPortEnvVariableName(DB_PORT_ENV_VAR_NAME),
dbPortEnvVariableValue({}),
sentinelPortEnvVariableName(SENTINEL_PORT_ENV_VAR_NAME),
sentinelPortEnvVariableValue({}),
sentinelMasterNameEnvVariableName(SENTINEL_MASTER_NAME_ENV_VAR_NAME),
sentinelMasterNameEnvVariableValue({}),
jsonDatabaseConfiguration(boost::none),
logger(logger)
{
auto envStr = system.getenv(dbHostEnvVariableName.c_str());
if (envStr)
{
dbHostEnvVariableValue = envStr;
sourceForDatabaseConfiguration = dbHostEnvVariableName;
auto envStr = system.getenv(dbPortEnvVariableName.c_str());
if (envStr)
dbPortEnvVariableValue = envStr;
envStr = system.getenv(sentinelPortEnvVariableName.c_str());
if (envStr)
sentinelPortEnvVariableValue = envStr;
envStr = system.getenv(sentinelMasterNameEnvVariableName.c_str());
if (envStr)
sentinelMasterNameEnvVariableValue = envStr;
}
readConfigurationFromDirectories(directories);
}
ConfigurationReader::~ConfigurationReader()
{
}
void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
{
for (const auto& i : findConfigurationFiles(directories))
readConfiguration(i, i);
}
void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
{
jsonNamespaceConfigurations.clear();
readConfiguration(const_cast<std::istream&>(input), "<istream>");
}
template<typename T>
void ConfigurationReader::readConfiguration(T& input, const std::string& currentSourceName)
{
boost::property_tree::ptree propertyTree;
try
{
boost::property_tree::read_json(input, propertyTree);
}
catch (const boost::property_tree::json_parser::json_parser_error& e)
{
std::ostringstream os;
os << "error in SDL configuration " << currentSourceName << " at line " << e.line() << ": ";
os << e.message();
logger->error() << os.str();
throw Exception(os.str());
}
// Environment variable configuration overrides json configuration
if (sourceForDatabaseConfiguration != dbHostEnvVariableName)
{
const auto databaseConfiguration(propertyTree.get_child_optional("database"));
if (databaseConfiguration)
{
jsonDatabaseConfiguration = databaseConfiguration;
sourceForDatabaseConfiguration = currentSourceName;
}
}
const auto namespaceConfigurations(propertyTree.get_child_optional("sharedDataLayer"));
if (namespaceConfigurations)
{
for(const auto& namespaceConfiguration : *namespaceConfigurations)
{
const auto namespacePrefix = get<std::string>(namespaceConfiguration.second, "namespacePrefix", currentSourceName);
jsonNamespaceConfigurations[namespacePrefix] = std::make_pair(namespaceConfiguration.second, currentSourceName);
}
}
}
void ConfigurationReader::readDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration)
{
if (!databaseConfiguration.isEmpty())
SHAREDDATALAYER_ABORT("Database configuration can be read only to empty container");
try
{
if (sourceForDatabaseConfiguration == dbHostEnvVariableName)
{
// NOTE: Redis cluster is not currently configurable via environment variables.
if (sentinelPortEnvVariableValue.empty())
{
validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
if (dbPortEnvVariableValue.empty())
parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue, sourceForDatabaseConfiguration);
else
parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue + ":" + dbPortEnvVariableValue, sourceForDatabaseConfiguration);
}
else
{
validateAndSetDbType("redis-sentinel", databaseConfiguration, sourceForDatabaseConfiguration);
databaseConfiguration.checkAndApplySentinelAddress(dbHostEnvVariableValue + ":" + sentinelPortEnvVariableValue);
databaseConfiguration.checkAndApplySentinelMasterName(sentinelMasterNameEnvVariableValue);
}
}
else
parseDatabaseConfigurationTree(databaseConfiguration, jsonDatabaseConfiguration, sourceForDatabaseConfiguration);
}
catch (const std::exception& e)
{
logger->error() << e.what();
throw;
}
}
void ConfigurationReader::readNamespaceConfigurations(NamespaceConfigurations& namespaceConfigurations)
{
if (!namespaceConfigurations.isEmpty())
SHAREDDATALAYER_ABORT("Namespace configurations can be read only to empty container");
try
{
parseNsConfigurationMap(namespaceConfigurations, jsonNamespaceConfigurations);
}
catch(const std::exception& e)
{
logger->error() << e.what();
throw;
}
}
template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);