New listKeys() API to support glob-style key search patterns
[ric-plt/sdl.git] / src / configurationreader.cpp
1 /*
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
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
33 using namespace shareddatalayer;
34
35 namespace
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     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     }
211 }
212
213 ConfigurationReader::ConfigurationReader(std::shared_ptr<Logger> logger):
214     ConfigurationReader(getDefaultConfDirectories(), System::getSystem(), logger)
215 {
216 }
217
218 ConfigurationReader::ConfigurationReader(const Directories& directories,
219                                          System& system,
220                                          std::shared_ptr<Logger> logger):
221     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({}),
229     dbClusterAddrListEnvVariableName(DB_CLUSTER_ADDR_LIST_ENV_VAR_NAME),
230     dbClusterAddrListEnvVariableValue({}),
231     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;
242         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;
248         envStr = system.getenv(dbClusterAddrListEnvVariableName.c_str());
249         if (envStr)
250             dbClusterAddrListEnvVariableValue = envStr;
251     }
252
253     readConfigurationFromDirectories(directories);
254 }
255
256 ConfigurationReader::~ConfigurationReader()
257 {
258 }
259
260 void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
261 {
262     for (const auto& i : findConfigurationFiles(directories))
263         readConfiguration(i, i);
264 }
265
266 void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
267 {
268     jsonNamespaceConfigurations.clear();
269     readConfiguration(const_cast<std::istream&>(input), "<istream>");
270 }
271
272 template<typename T>
273 void 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
312 void 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         {
321             // NOTE: Redis cluster is not currently configurable via environment variables.
322             std::string dbHostAddrs;
323             if (!dbHostEnvVariableValue.empty() && sentinelPortEnvVariableValue.empty() && dbClusterAddrListEnvVariableValue.empty())
324             {
325                 validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
326                 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;
342             }
343             else
344             {
345                 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             {
360                 databaseConfiguration.checkAndApplySentinelAddress(dbHostEnvVariableValue + ":" + sentinelPortEnvVariableValue);
361                 databaseConfiguration.checkAndApplySentinelMasterName(sentinelMasterNameEnvVariableValue);
362             }
363         }
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
374 void 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
391 template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
392 template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);