blob: 18945b6ecd653d4c52ca11d9ca098fa0be51cdac [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 }
198}
199
200ConfigurationReader::ConfigurationReader(std::shared_ptr<Logger> logger):
201 ConfigurationReader(getDefaultConfDirectories(), System::getSystem(), logger)
202{
203}
204
205ConfigurationReader::ConfigurationReader(const Directories& directories,
206 System& system,
207 std::shared_ptr<Logger> logger):
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300208 dbHostEnvVariableName(DB_HOST_ENV_VAR_NAME),
209 dbHostEnvVariableValue({}),
210 dbPortEnvVariableName(DB_PORT_ENV_VAR_NAME),
211 dbPortEnvVariableValue({}),
212 sentinelPortEnvVariableName(SENTINEL_PORT_ENV_VAR_NAME),
213 sentinelPortEnvVariableValue({}),
214 sentinelMasterNameEnvVariableName(SENTINEL_MASTER_NAME_ENV_VAR_NAME),
215 sentinelMasterNameEnvVariableValue({}),
Petri Ovaskaece67082021-04-15 11:08:13 +0300216 dbClusterAddrListEnvVariableName(DB_CLUSTER_ADDR_LIST_ENV_VAR_NAME),
217 dbClusterAddrListEnvVariableValue({}),
Rolf Badorekef2bf512019-08-20 11:17:15 +0300218 jsonDatabaseConfiguration(boost::none),
219 logger(logger)
220{
221 auto envStr = system.getenv(dbHostEnvVariableName.c_str());
222 if (envStr)
223 {
224 dbHostEnvVariableValue = envStr;
225 sourceForDatabaseConfiguration = dbHostEnvVariableName;
226 auto envStr = system.getenv(dbPortEnvVariableName.c_str());
227 if (envStr)
228 dbPortEnvVariableValue = envStr;
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300229 envStr = system.getenv(sentinelPortEnvVariableName.c_str());
230 if (envStr)
231 sentinelPortEnvVariableValue = envStr;
232 envStr = system.getenv(sentinelMasterNameEnvVariableName.c_str());
233 if (envStr)
234 sentinelMasterNameEnvVariableValue = envStr;
Petri Ovaskaece67082021-04-15 11:08:13 +0300235 envStr = system.getenv(dbClusterAddrListEnvVariableName.c_str());
236 if (envStr)
237 dbClusterAddrListEnvVariableValue = envStr;
Rolf Badorekef2bf512019-08-20 11:17:15 +0300238 }
239
240 readConfigurationFromDirectories(directories);
241}
242
243ConfigurationReader::~ConfigurationReader()
244{
245}
246
247void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
248{
249 for (const auto& i : findConfigurationFiles(directories))
250 readConfiguration(i, i);
251}
252
253void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
254{
255 jsonNamespaceConfigurations.clear();
256 readConfiguration(const_cast<std::istream&>(input), "<istream>");
257}
258
259template<typename T>
260void ConfigurationReader::readConfiguration(T& input, const std::string& currentSourceName)
261{
262 boost::property_tree::ptree propertyTree;
263
264 try
265 {
266 boost::property_tree::read_json(input, propertyTree);
267 }
268 catch (const boost::property_tree::json_parser::json_parser_error& e)
269 {
270 std::ostringstream os;
271 os << "error in SDL configuration " << currentSourceName << " at line " << e.line() << ": ";
272 os << e.message();
273 logger->error() << os.str();
274 throw Exception(os.str());
275 }
276
277 // Environment variable configuration overrides json configuration
278 if (sourceForDatabaseConfiguration != dbHostEnvVariableName)
279 {
280 const auto databaseConfiguration(propertyTree.get_child_optional("database"));
281 if (databaseConfiguration)
282 {
283 jsonDatabaseConfiguration = databaseConfiguration;
284 sourceForDatabaseConfiguration = currentSourceName;
285 }
286 }
287
288 const auto namespaceConfigurations(propertyTree.get_child_optional("sharedDataLayer"));
289 if (namespaceConfigurations)
290 {
291 for(const auto& namespaceConfiguration : *namespaceConfigurations)
292 {
293 const auto namespacePrefix = get<std::string>(namespaceConfiguration.second, "namespacePrefix", currentSourceName);
294 jsonNamespaceConfigurations[namespacePrefix] = std::make_pair(namespaceConfiguration.second, currentSourceName);
295 }
296 }
297}
298
299void ConfigurationReader::readDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration)
300{
301 if (!databaseConfiguration.isEmpty())
302 SHAREDDATALAYER_ABORT("Database configuration can be read only to empty container");
303
304 try
305 {
306 if (sourceForDatabaseConfiguration == dbHostEnvVariableName)
307 {
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300308 // NOTE: Redis cluster is not currently configurable via environment variables.
309 if (sentinelPortEnvVariableValue.empty())
310 {
311 validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
312 if (dbPortEnvVariableValue.empty())
313 parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue, sourceForDatabaseConfiguration);
314 else
315 parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue + ":" + dbPortEnvVariableValue, sourceForDatabaseConfiguration);
316 }
317 else
318 {
Petri Ovaskaece67082021-04-15 11:08:13 +0300319 if (dbClusterAddrListEnvVariableValue.empty())
320 validateAndSetDbType("redis-sentinel", databaseConfiguration, sourceForDatabaseConfiguration);
321 else {
322 validateAndSetDbType("sdl-cluster", databaseConfiguration, sourceForDatabaseConfiguration);
323 parseDatabaseServersConfigurationFromString(databaseConfiguration,
324 dbClusterAddrListEnvVariableValue,
325 dbClusterAddrListEnvVariableName);
326 }
Rolf Badorek2dcf9402019-10-01 18:33:58 +0300327 databaseConfiguration.checkAndApplySentinelAddress(dbHostEnvVariableValue + ":" + sentinelPortEnvVariableValue);
328 databaseConfiguration.checkAndApplySentinelMasterName(sentinelMasterNameEnvVariableValue);
329 }
Rolf Badorekef2bf512019-08-20 11:17:15 +0300330 }
331 else
332 parseDatabaseConfigurationTree(databaseConfiguration, jsonDatabaseConfiguration, sourceForDatabaseConfiguration);
333 }
334 catch (const std::exception& e)
335 {
336 logger->error() << e.what();
337 throw;
338 }
339}
340
341void ConfigurationReader::readNamespaceConfigurations(NamespaceConfigurations& namespaceConfigurations)
342{
343 if (!namespaceConfigurations.isEmpty())
344 SHAREDDATALAYER_ABORT("Namespace configurations can be read only to empty container");
345
346 try
347 {
348 parseNsConfigurationMap(namespaceConfigurations, jsonNamespaceConfigurations);
349 }
350 catch(const std::exception& e)
351 {
352 logger->error() << e.what();
353 throw;
354 }
355}
356
357
358template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
359template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);