blob: 1870a873b08f7b4547d7ad953cfd0e3eba91c68a [file] [log] [blame]
Rolf Badorekef2bf512019-08-20 11:17:15 +03001/*
2 Copyright (c) 2018-2019 Nokia.
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15*/
16
Timo Tietavainena0745d22019-11-28 09:55:22 +020017/*
18 * This source code is part of the near-RT RIC (RAN Intelligent Controller)
19 * platform project (RICP).
20*/
21
Rolf Badorekef2bf512019-08-20 11:17:15 +030022#include "private/abort.hpp"
23#include "private/configurationreader.hpp"
24#include <boost/property_tree/json_parser.hpp>
25#include <sdl/exception.hpp>
26#include "private/createlogger.hpp"
27#include "private/databaseconfiguration.hpp"
28#include "private/logger.hpp"
29#include "private/namespaceconfigurations.hpp"
30#include "private/namespacevalidator.hpp"
31#include "private/system.hpp"
32
33using namespace shareddatalayer;
34
35namespace
36{
37 template <typename T>
38 T get(const boost::property_tree::ptree& ptree, const std::string& param, const std::string& sourceName)
39 {
40 try
41 {
42 return ptree.get<T>(param);
43 }
44 catch (const boost::property_tree::ptree_bad_path&)
45 {
46 std::ostringstream os;
47 os << "Configuration error in " << sourceName << ": "
48 << "missing \"" << param << '\"';
49 throw Exception(os.str());
50 }
51 catch (const boost::property_tree::ptree_bad_data& e)
52 {
53 std::ostringstream os;
54 os << "Configuration error in " << sourceName << ": "
55 << "invalid \"" << param << "\": \"" << e.data<boost::property_tree::ptree::data_type>() << '\"';
56 throw Exception(os.str());
57 }
58 }
59
60 void validateAndSetDbType(const std::string& type, DatabaseConfiguration& databaseConfiguration,
61 const std::string& sourceName)
62 {
63 try
64 {
65 databaseConfiguration.checkAndApplyDbType(type);
66 }
67 catch (const std::exception& e)
68 {
69 std::ostringstream os;
70 os << "Configuration error in " << sourceName << ": "
71 << e.what();
72 throw Exception(os.str());
73 }
74 }
75
76 void validateAndSetDbServerAddress(const std::string& address, DatabaseConfiguration& databaseConfiguration,
77 const std::string& sourceName)
78 {
79 try
80 {
81 databaseConfiguration.checkAndApplyServerAddress(address);
82 }
83 catch (const std::exception& e)
84 {
85 std::ostringstream os;
86 os << "Configuration error in " << sourceName << ": "
87 << "invalid \"address\": \"" << address << "\" " << e.what();
88 throw Exception(os.str());
89 }
90 }
91
92 void parseDatabaseServerConfiguration(DatabaseConfiguration& databaseConfiguration,
93 const boost::property_tree::ptree& ptree,
94 const std::string& sourceName)
95 {
96 const auto address(get<std::string>(ptree, "address", sourceName));
97 validateAndSetDbServerAddress(address, databaseConfiguration, sourceName);
98 }
99
100 void parseDatabaseServersConfiguration(DatabaseConfiguration& databaseConfiguration,
101 const boost::property_tree::ptree& ptree,
102 const std::string& sourceName)
103 {
104 const auto servers(ptree.get_child_optional("servers"));
105 if (servers)
106 for(const auto& server : *servers)
107 parseDatabaseServerConfiguration(databaseConfiguration, server.second, sourceName);
108 else
109 {
110 std::ostringstream os;
111 os << "Configuration error in " << sourceName << ": "
112 << "missing \"servers\"";
113 throw Exception(os.str());
114 }
115 }
116
117 void parseDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration,
118 const boost::property_tree::ptree& ptree,
119 const std::string& sourceName)
120 {
121 const auto type(get<std::string>(ptree, "type", sourceName));
122 validateAndSetDbType(type, databaseConfiguration, sourceName);
123
124 parseDatabaseServersConfiguration(databaseConfiguration, ptree, sourceName);
125 }
126
127 void parseDatabaseConfigurationTree(DatabaseConfiguration& databaseConfiguration,
128 const boost::optional<boost::property_tree::ptree>& databaseConfigurationPtree,
129 const std::string& sourceName)
130 {
131 if (databaseConfigurationPtree)
132 parseDatabaseConfiguration(databaseConfiguration, *databaseConfigurationPtree, sourceName);
133 }
134
135 void parseDatabaseServersConfigurationFromString(DatabaseConfiguration& databaseConfiguration,
136 const std::string& serverConfiguration,
137 const std::string& sourceName)
138 {
139 size_t base(0);
140 auto done(false);
141 do
142 {
143 auto split = serverConfiguration.find(',', base);
144 done = std::string::npos == split;
145 validateAndSetDbServerAddress(serverConfiguration.substr(base, done ? std::string::npos : split-base),
146 databaseConfiguration,
147 sourceName);
148 base = split+1;
149 } while (!done);
150 }
151
152 void validateNamespacePrefix(const std::string& prefix,
153 const std::string& sourceName)
154 {
155 if (!isValidNamespaceSyntax(prefix))
156 {
157 std::ostringstream os;
158 os << "Configuration error in " << sourceName << ": "
159 << "\"namespacePrefix\": \"" << prefix << "\""
160 << " contains some of these disallowed characters: "
161 << getDisallowedCharactersInNamespace();
162 throw Exception(os.str());
163 }
164 }
165
166 void validateEnableNotifications(bool enableNotifications, bool useDbBackend,
167 const std::string& sourceName)
168 {
169 if (enableNotifications && !useDbBackend)
170 {
171 std::ostringstream os;
172 os << "Configuration error in " << sourceName << ": "
173 << "\"enableNotifications\" cannot be true, when \"useDbBackend\" is false";
174 throw Exception(os.str());
175 }
176 }
177
178 void parseNsConfiguration(NamespaceConfigurations& namespaceConfigurations,
179 const std::string& namespacePrefix,
180 const boost::property_tree::ptree& ptree,
181 const std::string& sourceName)
182 {
183 const auto useDbBackend(get<bool>(ptree, "useDbBackend", sourceName));
184 const auto enableNotifications(get<bool>(ptree, "enableNotifications", sourceName));
185
186 validateNamespacePrefix(namespacePrefix, sourceName);
187 validateEnableNotifications(enableNotifications, useDbBackend, sourceName);
188
189 namespaceConfigurations.addNamespaceConfiguration({namespacePrefix, useDbBackend, enableNotifications, sourceName});
190 }
191
192 void parseNsConfigurationMap(NamespaceConfigurations& namespaceConfigurations,
193 std::unordered_map<std::string, std::pair<boost::property_tree::ptree, std::string>>& namespaceConfigurationMap)
194 {
195 for (const auto &namespaceConfigurationMapItem : namespaceConfigurationMap )
196 parseNsConfiguration(namespaceConfigurations, namespaceConfigurationMapItem.first, namespaceConfigurationMapItem.second.first, namespaceConfigurationMapItem.second.second);
197 }
Petri Ovaska1c4a6052021-05-31 14:06:32 +0300198
199 void appendDBPortToAddrList(std::string& addresses, const std::string& port)
200 {
201 size_t base(0);
202 auto pos = addresses.find(',', base);
203 while (std::string::npos != pos)
204 {
205 addresses.insert(pos, ":" + port);
206 base = pos + 2 + port.size();
207 pos = addresses.find(',', base);
208 }
209 addresses.append(":" + port);
210 }
Rolf Badorekef2bf512019-08-20 11:17:15 +0300211}
212
213ConfigurationReader::ConfigurationReader(std::shared_ptr<Logger> logger):
214 ConfigurationReader(getDefaultConfDirectories(), System::getSystem(), logger)
215{
216}
217
218ConfigurationReader::ConfigurationReader(const Directories& directories,
219 System& system,
220 std::shared_ptr<Logger> logger):
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300221 dbHostEnvVariableName(DB_HOST_ENV_VAR_NAME),
222 dbHostEnvVariableValue({}),
223 dbPortEnvVariableName(DB_PORT_ENV_VAR_NAME),
224 dbPortEnvVariableValue({}),
225 sentinelPortEnvVariableName(SENTINEL_PORT_ENV_VAR_NAME),
226 sentinelPortEnvVariableValue({}),
227 sentinelMasterNameEnvVariableName(SENTINEL_MASTER_NAME_ENV_VAR_NAME),
228 sentinelMasterNameEnvVariableValue({}),
Petri Ovaskaece67082021-04-15 11:08:13 +0300229 dbClusterAddrListEnvVariableName(DB_CLUSTER_ADDR_LIST_ENV_VAR_NAME),
230 dbClusterAddrListEnvVariableValue({}),
Rolf Badorekef2bf512019-08-20 11:17:15 +0300231 jsonDatabaseConfiguration(boost::none),
232 logger(logger)
233{
234 auto envStr = system.getenv(dbHostEnvVariableName.c_str());
235 if (envStr)
236 {
237 dbHostEnvVariableValue = envStr;
238 sourceForDatabaseConfiguration = dbHostEnvVariableName;
239 auto envStr = system.getenv(dbPortEnvVariableName.c_str());
240 if (envStr)
241 dbPortEnvVariableValue = envStr;
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300242 envStr = system.getenv(sentinelPortEnvVariableName.c_str());
243 if (envStr)
244 sentinelPortEnvVariableValue = envStr;
245 envStr = system.getenv(sentinelMasterNameEnvVariableName.c_str());
246 if (envStr)
247 sentinelMasterNameEnvVariableValue = envStr;
Petri Ovaskaece67082021-04-15 11:08:13 +0300248 envStr = system.getenv(dbClusterAddrListEnvVariableName.c_str());
249 if (envStr)
250 dbClusterAddrListEnvVariableValue = envStr;
Rolf Badorekef2bf512019-08-20 11:17:15 +0300251 }
252
253 readConfigurationFromDirectories(directories);
254}
255
256ConfigurationReader::~ConfigurationReader()
257{
258}
259
260void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
261{
262 for (const auto& i : findConfigurationFiles(directories))
263 readConfiguration(i, i);
264}
265
266void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
267{
268 jsonNamespaceConfigurations.clear();
269 readConfiguration(const_cast<std::istream&>(input), "<istream>");
270}
271
272template<typename T>
273void ConfigurationReader::readConfiguration(T& input, const std::string& currentSourceName)
274{
275 boost::property_tree::ptree propertyTree;
276
277 try
278 {
279 boost::property_tree::read_json(input, propertyTree);
280 }
281 catch (const boost::property_tree::json_parser::json_parser_error& e)
282 {
283 std::ostringstream os;
284 os << "error in SDL configuration " << currentSourceName << " at line " << e.line() << ": ";
285 os << e.message();
286 logger->error() << os.str();
287 throw Exception(os.str());
288 }
289
290 // Environment variable configuration overrides json configuration
291 if (sourceForDatabaseConfiguration != dbHostEnvVariableName)
292 {
293 const auto databaseConfiguration(propertyTree.get_child_optional("database"));
294 if (databaseConfiguration)
295 {
296 jsonDatabaseConfiguration = databaseConfiguration;
297 sourceForDatabaseConfiguration = currentSourceName;
298 }
299 }
300
301 const auto namespaceConfigurations(propertyTree.get_child_optional("sharedDataLayer"));
302 if (namespaceConfigurations)
303 {
304 for(const auto& namespaceConfiguration : *namespaceConfigurations)
305 {
306 const auto namespacePrefix = get<std::string>(namespaceConfiguration.second, "namespacePrefix", currentSourceName);
307 jsonNamespaceConfigurations[namespacePrefix] = std::make_pair(namespaceConfiguration.second, currentSourceName);
308 }
309 }
310}
311
312void ConfigurationReader::readDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration)
313{
314 if (!databaseConfiguration.isEmpty())
315 SHAREDDATALAYER_ABORT("Database configuration can be read only to empty container");
316
317 try
318 {
319 if (sourceForDatabaseConfiguration == dbHostEnvVariableName)
320 {
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300321 // NOTE: Redis cluster is not currently configurable via environment variables.
Petri Ovaska1c4a6052021-05-31 14:06:32 +0300322 std::string dbHostAddrs;
323 if (!dbHostEnvVariableValue.empty() && sentinelPortEnvVariableValue.empty() && dbClusterAddrListEnvVariableValue.empty())
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300324 {
325 validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
Petri Ovaska1c4a6052021-05-31 14:06:32 +0300326 dbHostAddrs = dbHostEnvVariableValue;
327 }
328 else if (!dbHostEnvVariableValue.empty() && !sentinelPortEnvVariableValue.empty() && dbClusterAddrListEnvVariableValue.empty())
329 {
330 validateAndSetDbType("redis-sentinel", databaseConfiguration, sourceForDatabaseConfiguration);
331 dbHostAddrs = dbHostEnvVariableValue;
332 }
333 else if (sentinelPortEnvVariableValue.empty() && !dbClusterAddrListEnvVariableValue.empty())
334 {
335 validateAndSetDbType("sdl-standalone-cluster", databaseConfiguration, sourceForDatabaseConfiguration);
336 dbHostAddrs = dbClusterAddrListEnvVariableValue;
337 }
338 else if (!sentinelPortEnvVariableValue.empty() && !dbClusterAddrListEnvVariableValue.empty())
339 {
340 validateAndSetDbType("sdl-sentinel-cluster", databaseConfiguration, sourceForDatabaseConfiguration);
341 dbHostAddrs = dbClusterAddrListEnvVariableValue;
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300342 }
343 else
344 {
Petri Ovaska1c4a6052021-05-31 14:06:32 +0300345 std::ostringstream os;
346 os << "Configuration error in " << sourceForDatabaseConfiguration << ": "
347 << "Missing environment variable configuration!";
348 throw Exception(os.str());
349 }
350
351 if (!dbPortEnvVariableValue.empty())
352 appendDBPortToAddrList(dbHostAddrs, dbPortEnvVariableValue);
353 parseDatabaseServersConfigurationFromString(databaseConfiguration,
354 dbHostAddrs,
355 sourceForDatabaseConfiguration);
356 auto dbType = databaseConfiguration.getDbType();
357 if (DatabaseConfiguration::DbType::REDIS_SENTINEL == dbType ||
358 DatabaseConfiguration::DbType::SDL_SENTINEL_CLUSTER == dbType)
359 {
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300360 databaseConfiguration.checkAndApplySentinelAddress(dbHostEnvVariableValue + ":" + sentinelPortEnvVariableValue);
361 databaseConfiguration.checkAndApplySentinelMasterName(sentinelMasterNameEnvVariableValue);
362 }
Rolf Badorekef2bf512019-08-20 11:17:15 +0300363 }
364 else
365 parseDatabaseConfigurationTree(databaseConfiguration, jsonDatabaseConfiguration, sourceForDatabaseConfiguration);
366 }
367 catch (const std::exception& e)
368 {
369 logger->error() << e.what();
370 throw;
371 }
372}
373
374void ConfigurationReader::readNamespaceConfigurations(NamespaceConfigurations& namespaceConfigurations)
375{
376 if (!namespaceConfigurations.isEmpty())
377 SHAREDDATALAYER_ABORT("Namespace configurations can be read only to empty container");
378
379 try
380 {
381 parseNsConfigurationMap(namespaceConfigurations, jsonNamespaceConfigurations);
382 }
383 catch(const std::exception& e)
384 {
385 logger->error() << e.what();
386 throw;
387 }
388}
389
390
391template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
392template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);