18945b6ecd653d4c52ca11d9ca098fa0be51cdac
[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
200 ConfigurationReader::ConfigurationReader(std::shared_ptr<Logger> logger):
201     ConfigurationReader(getDefaultConfDirectories(), System::getSystem(), logger)
202 {
203 }
204
205 ConfigurationReader::ConfigurationReader(const Directories& directories,
206                                          System& system,
207                                          std::shared_ptr<Logger> logger):
208     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({}),
216     dbClusterAddrListEnvVariableName(DB_CLUSTER_ADDR_LIST_ENV_VAR_NAME),
217     dbClusterAddrListEnvVariableValue({}),
218     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;
229         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;
235         envStr = system.getenv(dbClusterAddrListEnvVariableName.c_str());
236         if (envStr)
237             dbClusterAddrListEnvVariableValue = envStr;
238     }
239
240     readConfigurationFromDirectories(directories);
241 }
242
243 ConfigurationReader::~ConfigurationReader()
244 {
245 }
246
247 void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
248 {
249     for (const auto& i : findConfigurationFiles(directories))
250         readConfiguration(i, i);
251 }
252
253 void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
254 {
255     jsonNamespaceConfigurations.clear();
256     readConfiguration(const_cast<std::istream&>(input), "<istream>");
257 }
258
259 template<typename T>
260 void 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
299 void 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         {
308             // 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             {
319                 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                 }
327                 databaseConfiguration.checkAndApplySentinelAddress(dbHostEnvVariableValue + ":" + sentinelPortEnvVariableValue);
328                 databaseConfiguration.checkAndApplySentinelMasterName(sentinelMasterNameEnvVariableValue);
329             }
330         }
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
341 void 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
358 template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
359 template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);