cc5e33e2c1fe8adece7f8986d0f05d830c96570e
[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     jsonDatabaseConfiguration(boost::none),
217     logger(logger)
218 {
219     auto envStr = system.getenv(dbHostEnvVariableName.c_str());
220     if (envStr)
221     {
222         dbHostEnvVariableValue = envStr;
223         sourceForDatabaseConfiguration = dbHostEnvVariableName;
224         auto envStr = system.getenv(dbPortEnvVariableName.c_str());
225         if (envStr)
226             dbPortEnvVariableValue = envStr;
227         envStr = system.getenv(sentinelPortEnvVariableName.c_str());
228         if (envStr)
229             sentinelPortEnvVariableValue = envStr;
230         envStr = system.getenv(sentinelMasterNameEnvVariableName.c_str());
231         if (envStr)
232             sentinelMasterNameEnvVariableValue = envStr;
233     }
234
235     readConfigurationFromDirectories(directories);
236 }
237
238 ConfigurationReader::~ConfigurationReader()
239 {
240 }
241
242 void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
243 {
244     for (const auto& i : findConfigurationFiles(directories))
245         readConfiguration(i, i);
246 }
247
248 void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
249 {
250     jsonNamespaceConfigurations.clear();
251     readConfiguration(const_cast<std::istream&>(input), "<istream>");
252 }
253
254 template<typename T>
255 void ConfigurationReader::readConfiguration(T& input, const std::string& currentSourceName)
256 {
257     boost::property_tree::ptree propertyTree;
258
259     try
260     {
261         boost::property_tree::read_json(input, propertyTree);
262     }
263     catch (const boost::property_tree::json_parser::json_parser_error& e)
264     {
265         std::ostringstream os;
266         os << "error in SDL configuration " << currentSourceName << " at line " << e.line() << ": ";
267         os << e.message();
268         logger->error() << os.str();
269         throw Exception(os.str());
270     }
271
272     // Environment variable configuration overrides json configuration
273     if (sourceForDatabaseConfiguration != dbHostEnvVariableName)
274     {
275         const auto databaseConfiguration(propertyTree.get_child_optional("database"));
276         if (databaseConfiguration)
277         {
278             jsonDatabaseConfiguration = databaseConfiguration;
279             sourceForDatabaseConfiguration = currentSourceName;
280         }
281     }
282
283     const auto namespaceConfigurations(propertyTree.get_child_optional("sharedDataLayer"));
284     if (namespaceConfigurations)
285     {
286         for(const auto& namespaceConfiguration : *namespaceConfigurations)
287         {
288             const auto namespacePrefix = get<std::string>(namespaceConfiguration.second, "namespacePrefix", currentSourceName);
289             jsonNamespaceConfigurations[namespacePrefix] = std::make_pair(namespaceConfiguration.second, currentSourceName);
290         }
291     }
292 }
293
294 void ConfigurationReader::readDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration)
295 {
296     if (!databaseConfiguration.isEmpty())
297         SHAREDDATALAYER_ABORT("Database configuration can be read only to empty container");
298
299     try
300     {
301         if (sourceForDatabaseConfiguration == dbHostEnvVariableName)
302         {
303             // NOTE: Redis cluster is not currently configurable via environment variables.
304             if (sentinelPortEnvVariableValue.empty())
305             {
306                 validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
307                 if (dbPortEnvVariableValue.empty())
308                     parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue, sourceForDatabaseConfiguration);
309                 else
310                     parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue + ":" + dbPortEnvVariableValue, sourceForDatabaseConfiguration);
311             }
312             else
313             {
314                 validateAndSetDbType("redis-sentinel", databaseConfiguration, sourceForDatabaseConfiguration);
315                 databaseConfiguration.checkAndApplySentinelAddress(dbHostEnvVariableValue + ":" + sentinelPortEnvVariableValue);
316                 databaseConfiguration.checkAndApplySentinelMasterName(sentinelMasterNameEnvVariableValue);
317             }
318         }
319         else
320             parseDatabaseConfigurationTree(databaseConfiguration, jsonDatabaseConfiguration, sourceForDatabaseConfiguration);
321     }
322     catch (const std::exception& e)
323     {
324         logger->error() << e.what();
325         throw;
326     }
327 }
328
329 void ConfigurationReader::readNamespaceConfigurations(NamespaceConfigurations& namespaceConfigurations)
330 {
331     if (!namespaceConfigurations.isEmpty())
332         SHAREDDATALAYER_ABORT("Namespace configurations can be read only to empty container");
333
334     try
335     {
336         parseNsConfigurationMap(namespaceConfigurations, jsonNamespaceConfigurations);
337     }
338     catch(const std::exception& e)
339     {
340         logger->error() << e.what();
341         throw;
342     }
343 }
344
345
346 template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
347 template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);