RIC:1060: Change in PTL
[ric-plt/sdl.git] / src / configurationreader.cpp
1 /*
2    Copyright (c) 2018-2022 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
17 /*
18  * This source code is part of the near-RT RIC (RAN Intelligent Controller)
19  * platform project (RICP).
20 */
21
22 #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 #include <boost/algorithm/string.hpp>
33
34 using namespace shareddatalayer;
35
36 namespace
37 {
38     template <typename T>
39     T get(const boost::property_tree::ptree& ptree, const std::string& param, const std::string& sourceName)
40     {
41         try
42         {
43             return ptree.get<T>(param);
44         }
45         catch (const boost::property_tree::ptree_bad_path&)
46         {
47             std::ostringstream os;
48             os << "Configuration error in " << sourceName << ": "
49                << "missing \"" << param << '\"';
50             throw Exception(os.str());
51         }
52         catch (const boost::property_tree::ptree_bad_data& e)
53         {
54             std::ostringstream os;
55             os << "Configuration error in " << sourceName << ": "
56                << "invalid \"" << param << "\": \"" << e.data<boost::property_tree::ptree::data_type>() << '\"';
57             throw Exception(os.str());
58         }
59     }
60
61     void validateAndSetDbType(const std::string& type, DatabaseConfiguration& databaseConfiguration,
62                               const std::string& sourceName)
63     {
64         try
65         {
66             databaseConfiguration.checkAndApplyDbType(type);
67         }
68         catch (const std::exception& e)
69         {
70             std::ostringstream os;
71             os << "Configuration error in " << sourceName << ": "
72                << e.what();
73             throw Exception(os.str());
74         }
75     }
76
77     void validateAndSetDbServerAddress(const std::string& address, DatabaseConfiguration& databaseConfiguration,
78                                        const std::string& sourceName)
79     {
80         try
81         {
82             databaseConfiguration.checkAndApplyServerAddress(address);
83         }
84         catch (const std::exception& e)
85         {
86             std::ostringstream os;
87             os << "Configuration error in " << sourceName << ": "
88                << "invalid \"address\": \"" << address << "\" " << e.what();
89             throw Exception(os.str());
90         }
91     }
92
93     void parseDatabaseServerConfiguration(DatabaseConfiguration& databaseConfiguration,
94                                           const boost::property_tree::ptree& ptree,
95                                           const std::string& sourceName)
96     {
97         const auto address(get<std::string>(ptree, "address", sourceName));
98         validateAndSetDbServerAddress(address, databaseConfiguration, sourceName);
99     }
100
101     void parseDatabaseServersConfiguration(DatabaseConfiguration& databaseConfiguration,
102                                            const boost::property_tree::ptree& ptree,
103                                            const std::string& sourceName)
104     {
105         const auto servers(ptree.get_child_optional("servers"));
106         if (servers)
107             for(const auto& server : *servers)
108                 parseDatabaseServerConfiguration(databaseConfiguration, server.second, sourceName);
109         else
110         {
111             std::ostringstream os;
112             os << "Configuration error in " << sourceName << ": "
113                << "missing \"servers\"";
114             throw Exception(os.str());
115         }
116     }
117
118     void parseDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration,
119                                     const boost::property_tree::ptree& ptree,
120                                     const std::string& sourceName)
121     {
122         const auto type(get<std::string>(ptree, "type", sourceName));
123         validateAndSetDbType(type, databaseConfiguration, sourceName);
124
125         parseDatabaseServersConfiguration(databaseConfiguration, ptree, sourceName);
126     }
127
128     void parseDatabaseConfigurationTree(DatabaseConfiguration& databaseConfiguration,
129                                         const boost::optional<boost::property_tree::ptree>& databaseConfigurationPtree,
130                                         const std::string& sourceName)
131     {
132         if (databaseConfigurationPtree)
133             parseDatabaseConfiguration(databaseConfiguration, *databaseConfigurationPtree, sourceName);
134     }
135
136     void parseDatabaseServersConfigurationFromString(DatabaseConfiguration& databaseConfiguration,
137                                                      const std::string& serverConfiguration,
138                                                      const std::string& sourceName)
139     {
140         size_t base(0);
141         auto done(false);
142         do
143         {
144             auto split = serverConfiguration.find(',', base);
145             done = std::string::npos == split;
146             validateAndSetDbServerAddress(serverConfiguration.substr(base, done ? std::string::npos : split-base),
147                                           databaseConfiguration,
148                                           sourceName);
149             base = split+1;
150         } while (!done);
151     }
152
153     void validateNamespacePrefix(const std::string& prefix,
154                                  const std::string& sourceName)
155     {
156         if (!isValidNamespaceSyntax(prefix))
157         {
158             std::ostringstream os;
159             os << "Configuration error in " << sourceName << ": "
160                << "\"namespacePrefix\": \"" << prefix << "\""
161                << " contains some of these disallowed characters: "
162                << getDisallowedCharactersInNamespace();
163             throw Exception(os.str());
164         }
165     }
166
167     void validateEnableNotifications(bool enableNotifications, bool useDbBackend,
168                                      const std::string& sourceName)
169     {
170         if (enableNotifications && !useDbBackend)
171         {
172             std::ostringstream os;
173             os << "Configuration error in " << sourceName << ": "
174                << "\"enableNotifications\" cannot be true, when \"useDbBackend\" is false";
175             throw Exception(os.str());
176         }
177     }
178
179     void parseNsConfiguration(NamespaceConfigurations& namespaceConfigurations,
180                               const std::string& namespacePrefix,
181                               const boost::property_tree::ptree& ptree,
182                               const std::string& sourceName)
183     {
184         const auto useDbBackend(get<bool>(ptree, "useDbBackend", sourceName));
185         const auto enableNotifications(get<bool>(ptree, "enableNotifications", sourceName));
186
187         validateNamespacePrefix(namespacePrefix, sourceName);
188         validateEnableNotifications(enableNotifications, useDbBackend, sourceName);
189
190         namespaceConfigurations.addNamespaceConfiguration({namespacePrefix, useDbBackend, enableNotifications, sourceName});
191     }
192
193     void parseNsConfigurationMap(NamespaceConfigurations& namespaceConfigurations,
194                                  std::unordered_map<std::string, std::pair<boost::property_tree::ptree, std::string>>& namespaceConfigurationMap)
195     {
196         for (const auto &namespaceConfigurationMapItem : namespaceConfigurationMap )
197             parseNsConfiguration(namespaceConfigurations, namespaceConfigurationMapItem.first, namespaceConfigurationMapItem.second.first, namespaceConfigurationMapItem.second.second);
198     }
199
200     const std::string DEFAULT_REDIS_PORT("6379");
201
202     void appendDBPortToAddrList(std::string& addresses, const std::string& port)
203     {
204         size_t base(0);
205         std::vector<std::string> portList;
206         boost::split(portList, port, boost::is_any_of(","));
207
208         std::size_t idx(0);
209         auto redisPort((portList.size() > 0 && idx < portList.size()) ? portList.at(idx) : DEFAULT_REDIS_PORT);
210         auto pos = addresses.find(',', base);
211         while (std::string::npos != pos)
212         {
213             addresses.insert(pos, ":" + redisPort);
214             base = pos + 2 + redisPort.size();
215             pos = addresses.find(',', base);
216             idx++;
217         }
218         addresses.append(":" + redisPort);
219     }
220 }
221
222 ConfigurationReader::ConfigurationReader(std::shared_ptr<Logger> logger):
223     ConfigurationReader(getDefaultConfDirectories(), System::getSystem(), logger)
224 {
225 }
226
227 ConfigurationReader::ConfigurationReader(const Directories& directories,
228                                          System& system,
229                                          std::shared_ptr<Logger> logger):
230     dbHostEnvVariableName(DB_HOST_ENV_VAR_NAME),
231     dbHostEnvVariableValue({}),
232     dbPortEnvVariableName(DB_PORT_ENV_VAR_NAME),
233     dbPortEnvVariableValue({}),
234     sentinelPortEnvVariableName(SENTINEL_PORT_ENV_VAR_NAME),
235     sentinelPortEnvVariableValue({}),
236     sentinelMasterNameEnvVariableName(SENTINEL_MASTER_NAME_ENV_VAR_NAME),
237     sentinelMasterNameEnvVariableValue({}),
238     dbClusterAddrListEnvVariableName(DB_CLUSTER_ADDR_LIST_ENV_VAR_NAME),
239     dbClusterAddrListEnvVariableValue({}),
240     jsonDatabaseConfiguration(boost::none),
241     logger(logger)
242 {
243     auto envStr = system.getenv(dbHostEnvVariableName.c_str());
244     if (envStr)
245     {
246         dbHostEnvVariableValue = envStr;
247         sourceForDatabaseConfiguration = dbHostEnvVariableName;
248         auto envStr = system.getenv(dbPortEnvVariableName.c_str());
249         if (envStr)
250             dbPortEnvVariableValue = envStr;
251         envStr = system.getenv(sentinelPortEnvVariableName.c_str());
252         if (envStr)
253             sentinelPortEnvVariableValue = envStr;
254         envStr = system.getenv(sentinelMasterNameEnvVariableName.c_str());
255         if (envStr)
256             sentinelMasterNameEnvVariableValue = envStr;
257         envStr = system.getenv(dbClusterAddrListEnvVariableName.c_str());
258         if (envStr)
259             dbClusterAddrListEnvVariableValue = envStr;
260     }
261
262     readConfigurationFromDirectories(directories);
263 }
264
265 ConfigurationReader::~ConfigurationReader()
266 {
267 }
268
269 void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
270 {
271     for (const auto& i : findConfigurationFiles(directories))
272         readConfiguration(i, i);
273 }
274
275 void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
276 {
277     jsonNamespaceConfigurations.clear();
278     readConfiguration(const_cast<std::istream&>(input), "<istream>");
279 }
280
281 template<typename T>
282 void ConfigurationReader::readConfiguration(T& input, const std::string& currentSourceName)
283 {
284     boost::property_tree::ptree propertyTree;
285
286     try
287     {
288         boost::property_tree::read_json(input, propertyTree);
289     }
290     catch (const boost::property_tree::json_parser::json_parser_error& e)
291     {
292         std::ostringstream os;
293         os << "error in SDL configuration " << currentSourceName << " at line " << e.line() << ": ";
294         os << e.message();
295         logger->error() << os.str();
296         throw Exception(os.str());
297     }
298
299     // Environment variable configuration overrides json configuration
300     if (sourceForDatabaseConfiguration != dbHostEnvVariableName)
301     {
302         const auto databaseConfiguration(propertyTree.get_child_optional("database"));
303         if (databaseConfiguration)
304         {
305             jsonDatabaseConfiguration = databaseConfiguration;
306             sourceForDatabaseConfiguration = currentSourceName;
307         }
308     }
309
310     const auto namespaceConfigurations(propertyTree.get_child_optional("sharedDataLayer"));
311     if (namespaceConfigurations)
312     {
313         for(const auto& namespaceConfiguration : *namespaceConfigurations)
314         {
315             const auto namespacePrefix = get<std::string>(namespaceConfiguration.second, "namespacePrefix", currentSourceName);
316             jsonNamespaceConfigurations[namespacePrefix] = std::make_pair(namespaceConfiguration.second, currentSourceName);
317         }
318     }
319 }
320
321 void ConfigurationReader::readDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration)
322 {
323     if (!databaseConfiguration.isEmpty())
324         SHAREDDATALAYER_ABORT("Database configuration can be read only to empty container");
325
326     try
327     {
328         if (sourceForDatabaseConfiguration == dbHostEnvVariableName)
329         {
330             // NOTE: Redis cluster is not currently configurable via environment variables.
331             std::string dbHostAddrs;
332             if (!dbHostEnvVariableValue.empty() && sentinelPortEnvVariableValue.empty() && dbClusterAddrListEnvVariableValue.empty())
333             {
334                 validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
335                 dbHostAddrs = dbHostEnvVariableValue;
336             }
337             else if (!dbHostEnvVariableValue.empty() && !sentinelPortEnvVariableValue.empty() && dbClusterAddrListEnvVariableValue.empty())
338             {
339                 validateAndSetDbType("redis-sentinel", databaseConfiguration, sourceForDatabaseConfiguration);
340                 dbHostAddrs = dbHostEnvVariableValue;
341             }
342             else if (sentinelPortEnvVariableValue.empty() && !dbClusterAddrListEnvVariableValue.empty())
343             {
344                 validateAndSetDbType("sdl-standalone-cluster", databaseConfiguration, sourceForDatabaseConfiguration);
345                 dbHostAddrs = dbClusterAddrListEnvVariableValue;
346             }
347             else if (!sentinelPortEnvVariableValue.empty() && !dbClusterAddrListEnvVariableValue.empty())
348             {
349                 validateAndSetDbType("sdl-sentinel-cluster", databaseConfiguration, sourceForDatabaseConfiguration);
350                 dbHostAddrs = dbClusterAddrListEnvVariableValue;
351             }
352             else
353             {
354                 std::ostringstream os;
355                 os << "Configuration error in " << sourceForDatabaseConfiguration << ": "
356                    << "Missing environment variable configuration!";
357                 throw Exception(os.str());
358             }
359
360             if (!dbPortEnvVariableValue.empty())
361                 appendDBPortToAddrList(dbHostAddrs, dbPortEnvVariableValue);
362             parseDatabaseServersConfigurationFromString(databaseConfiguration,
363                                                         dbHostAddrs,
364                                                         sourceForDatabaseConfiguration);
365             auto dbType = databaseConfiguration.getDbType();
366             if (DatabaseConfiguration::DbType::REDIS_SENTINEL == dbType ||
367                 DatabaseConfiguration::DbType::SDL_SENTINEL_CLUSTER == dbType)
368             {
369                 databaseConfiguration.checkAndApplySentinelPorts(sentinelPortEnvVariableValue);
370                 databaseConfiguration.checkAndApplySentinelMasterNames(sentinelMasterNameEnvVariableValue);
371             }
372         }
373         else
374             parseDatabaseConfigurationTree(databaseConfiguration, jsonDatabaseConfiguration, sourceForDatabaseConfiguration);
375     }
376     catch (const std::exception& e)
377     {
378         logger->error() << e.what();
379         throw;
380     }
381 }
382
383 void ConfigurationReader::readNamespaceConfigurations(NamespaceConfigurations& namespaceConfigurations)
384 {
385     if (!namespaceConfigurations.isEmpty())
386         SHAREDDATALAYER_ABORT("Namespace configurations can be read only to empty container");
387
388     try
389     {
390         parseNsConfigurationMap(namespaceConfigurations, jsonNamespaceConfigurations);
391     }
392     catch(const std::exception& e)
393     {
394         logger->error() << e.what();
395         throw;
396     }
397 }
398
399
400 template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
401 template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);