4b5de3be0214634150d5f74fdd6eefe8d704ec83
[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 #include "private/abort.hpp"
18 #include "private/configurationreader.hpp"
19 #include <boost/property_tree/json_parser.hpp>
20 #include <sdl/exception.hpp>
21 #include "private/createlogger.hpp"
22 #include "private/databaseconfiguration.hpp"
23 #include "private/logger.hpp"
24 #include "private/namespaceconfigurations.hpp"
25 #include "private/namespacevalidator.hpp"
26 #include "private/system.hpp"
27
28 using namespace shareddatalayer;
29
30 namespace
31 {
32     template <typename T>
33     T get(const boost::property_tree::ptree& ptree, const std::string& param, const std::string& sourceName)
34     {
35         try
36         {
37             return ptree.get<T>(param);
38         }
39         catch (const boost::property_tree::ptree_bad_path&)
40         {
41             std::ostringstream os;
42             os << "Configuration error in " << sourceName << ": "
43                << "missing \"" << param << '\"';
44             throw Exception(os.str());
45         }
46         catch (const boost::property_tree::ptree_bad_data& e)
47         {
48             std::ostringstream os;
49             os << "Configuration error in " << sourceName << ": "
50                << "invalid \"" << param << "\": \"" << e.data<boost::property_tree::ptree::data_type>() << '\"';
51             throw Exception(os.str());
52         }
53     }
54
55     void validateAndSetDbType(const std::string& type, DatabaseConfiguration& databaseConfiguration,
56                               const std::string& sourceName)
57     {
58         try
59         {
60             databaseConfiguration.checkAndApplyDbType(type);
61         }
62         catch (const std::exception& e)
63         {
64             std::ostringstream os;
65             os << "Configuration error in " << sourceName << ": "
66                << e.what();
67             throw Exception(os.str());
68         }
69     }
70
71     void validateAndSetDbServerAddress(const std::string& address, DatabaseConfiguration& databaseConfiguration,
72                                        const std::string& sourceName)
73     {
74         try
75         {
76             databaseConfiguration.checkAndApplyServerAddress(address);
77         }
78         catch (const std::exception& e)
79         {
80             std::ostringstream os;
81             os << "Configuration error in " << sourceName << ": "
82                << "invalid \"address\": \"" << address << "\" " << e.what();
83             throw Exception(os.str());
84         }
85     }
86
87     void parseDatabaseServerConfiguration(DatabaseConfiguration& databaseConfiguration,
88                                           const boost::property_tree::ptree& ptree,
89                                           const std::string& sourceName)
90     {
91         const auto address(get<std::string>(ptree, "address", sourceName));
92         validateAndSetDbServerAddress(address, databaseConfiguration, sourceName);
93     }
94
95     void parseDatabaseServersConfiguration(DatabaseConfiguration& databaseConfiguration,
96                                            const boost::property_tree::ptree& ptree,
97                                            const std::string& sourceName)
98     {
99         const auto servers(ptree.get_child_optional("servers"));
100         if (servers)
101             for(const auto& server : *servers)
102                 parseDatabaseServerConfiguration(databaseConfiguration, server.second, sourceName);
103         else
104         {
105             std::ostringstream os;
106             os << "Configuration error in " << sourceName << ": "
107                << "missing \"servers\"";
108             throw Exception(os.str());
109         }
110     }
111
112     void parseDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration,
113                                     const boost::property_tree::ptree& ptree,
114                                     const std::string& sourceName)
115     {
116         const auto type(get<std::string>(ptree, "type", sourceName));
117         validateAndSetDbType(type, databaseConfiguration, sourceName);
118
119         parseDatabaseServersConfiguration(databaseConfiguration, ptree, sourceName);
120     }
121
122     void parseDatabaseConfigurationTree(DatabaseConfiguration& databaseConfiguration,
123                                         const boost::optional<boost::property_tree::ptree>& databaseConfigurationPtree,
124                                         const std::string& sourceName)
125     {
126         if (databaseConfigurationPtree)
127             parseDatabaseConfiguration(databaseConfiguration, *databaseConfigurationPtree, sourceName);
128     }
129
130     void parseDatabaseServersConfigurationFromString(DatabaseConfiguration& databaseConfiguration,
131                                                      const std::string& serverConfiguration,
132                                                      const std::string& sourceName)
133     {
134         size_t base(0);
135         auto done(false);
136         do
137         {
138             auto split = serverConfiguration.find(',', base);
139             done = std::string::npos == split;
140             validateAndSetDbServerAddress(serverConfiguration.substr(base, done ? std::string::npos : split-base),
141                                           databaseConfiguration,
142                                           sourceName);
143             base = split+1;
144         } while (!done);
145     }
146
147     void validateNamespacePrefix(const std::string& prefix,
148                                  const std::string& sourceName)
149     {
150         if (!isValidNamespaceSyntax(prefix))
151         {
152             std::ostringstream os;
153             os << "Configuration error in " << sourceName << ": "
154                << "\"namespacePrefix\": \"" << prefix << "\""
155                << " contains some of these disallowed characters: "
156                << getDisallowedCharactersInNamespace();
157             throw Exception(os.str());
158         }
159     }
160
161     void validateEnableNotifications(bool enableNotifications, bool useDbBackend,
162                                      const std::string& sourceName)
163     {
164         if (enableNotifications && !useDbBackend)
165         {
166             std::ostringstream os;
167             os << "Configuration error in " << sourceName << ": "
168                << "\"enableNotifications\" cannot be true, when \"useDbBackend\" is false";
169             throw Exception(os.str());
170         }
171     }
172
173     void parseNsConfiguration(NamespaceConfigurations& namespaceConfigurations,
174                               const std::string& namespacePrefix,
175                               const boost::property_tree::ptree& ptree,
176                               const std::string& sourceName)
177     {
178         const auto useDbBackend(get<bool>(ptree, "useDbBackend", sourceName));
179         const auto enableNotifications(get<bool>(ptree, "enableNotifications", sourceName));
180
181         validateNamespacePrefix(namespacePrefix, sourceName);
182         validateEnableNotifications(enableNotifications, useDbBackend, sourceName);
183
184         namespaceConfigurations.addNamespaceConfiguration({namespacePrefix, useDbBackend, enableNotifications, sourceName});
185     }
186
187     void parseNsConfigurationMap(NamespaceConfigurations& namespaceConfigurations,
188                                  std::unordered_map<std::string, std::pair<boost::property_tree::ptree, std::string>>& namespaceConfigurationMap)
189     {
190         for (const auto &namespaceConfigurationMapItem : namespaceConfigurationMap )
191             parseNsConfiguration(namespaceConfigurations, namespaceConfigurationMapItem.first, namespaceConfigurationMapItem.second.first, namespaceConfigurationMapItem.second.second);
192     }
193 }
194
195 ConfigurationReader::ConfigurationReader(std::shared_ptr<Logger> logger):
196     ConfigurationReader(getDefaultConfDirectories(), System::getSystem(), logger)
197 {
198 }
199
200 ConfigurationReader::ConfigurationReader(const Directories& directories,
201                                          System& system,
202                                          std::shared_ptr<Logger> logger):
203     dbHostEnvVariableName(DB_HOST_ENV_VAR_NAME),
204     dbHostEnvVariableValue({}),
205     dbPortEnvVariableName(DB_PORT_ENV_VAR_NAME),
206     dbPortEnvVariableValue({}),
207     sentinelPortEnvVariableName(SENTINEL_PORT_ENV_VAR_NAME),
208     sentinelPortEnvVariableValue({}),
209     sentinelMasterNameEnvVariableName(SENTINEL_MASTER_NAME_ENV_VAR_NAME),
210     sentinelMasterNameEnvVariableValue({}),
211     jsonDatabaseConfiguration(boost::none),
212     logger(logger)
213 {
214     auto envStr = system.getenv(dbHostEnvVariableName.c_str());
215     if (envStr)
216     {
217         dbHostEnvVariableValue = envStr;
218         sourceForDatabaseConfiguration = dbHostEnvVariableName;
219         auto envStr = system.getenv(dbPortEnvVariableName.c_str());
220         if (envStr)
221             dbPortEnvVariableValue = envStr;
222         envStr = system.getenv(sentinelPortEnvVariableName.c_str());
223         if (envStr)
224             sentinelPortEnvVariableValue = envStr;
225         envStr = system.getenv(sentinelMasterNameEnvVariableName.c_str());
226         if (envStr)
227             sentinelMasterNameEnvVariableValue = envStr;
228     }
229
230     readConfigurationFromDirectories(directories);
231 }
232
233 ConfigurationReader::~ConfigurationReader()
234 {
235 }
236
237 void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
238 {
239     for (const auto& i : findConfigurationFiles(directories))
240         readConfiguration(i, i);
241 }
242
243 void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
244 {
245     jsonNamespaceConfigurations.clear();
246     readConfiguration(const_cast<std::istream&>(input), "<istream>");
247 }
248
249 template<typename T>
250 void ConfigurationReader::readConfiguration(T& input, const std::string& currentSourceName)
251 {
252     boost::property_tree::ptree propertyTree;
253
254     try
255     {
256         boost::property_tree::read_json(input, propertyTree);
257     }
258     catch (const boost::property_tree::json_parser::json_parser_error& e)
259     {
260         std::ostringstream os;
261         os << "error in SDL configuration " << currentSourceName << " at line " << e.line() << ": ";
262         os << e.message();
263         logger->error() << os.str();
264         throw Exception(os.str());
265     }
266
267     // Environment variable configuration overrides json configuration
268     if (sourceForDatabaseConfiguration != dbHostEnvVariableName)
269     {
270         const auto databaseConfiguration(propertyTree.get_child_optional("database"));
271         if (databaseConfiguration)
272         {
273             jsonDatabaseConfiguration = databaseConfiguration;
274             sourceForDatabaseConfiguration = currentSourceName;
275         }
276     }
277
278     const auto namespaceConfigurations(propertyTree.get_child_optional("sharedDataLayer"));
279     if (namespaceConfigurations)
280     {
281         for(const auto& namespaceConfiguration : *namespaceConfigurations)
282         {
283             const auto namespacePrefix = get<std::string>(namespaceConfiguration.second, "namespacePrefix", currentSourceName);
284             jsonNamespaceConfigurations[namespacePrefix] = std::make_pair(namespaceConfiguration.second, currentSourceName);
285         }
286     }
287 }
288
289 void ConfigurationReader::readDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration)
290 {
291     if (!databaseConfiguration.isEmpty())
292         SHAREDDATALAYER_ABORT("Database configuration can be read only to empty container");
293
294     try
295     {
296         if (sourceForDatabaseConfiguration == dbHostEnvVariableName)
297         {
298             // NOTE: Redis cluster is not currently configurable via environment variables.
299             if (sentinelPortEnvVariableValue.empty())
300             {
301                 validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
302                 if (dbPortEnvVariableValue.empty())
303                     parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue, sourceForDatabaseConfiguration);
304                 else
305                     parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue + ":" + dbPortEnvVariableValue, sourceForDatabaseConfiguration);
306             }
307             else
308             {
309                 validateAndSetDbType("redis-sentinel", databaseConfiguration, sourceForDatabaseConfiguration);
310                 databaseConfiguration.checkAndApplySentinelAddress(dbHostEnvVariableValue + ":" + sentinelPortEnvVariableValue);
311                 databaseConfiguration.checkAndApplySentinelMasterName(sentinelMasterNameEnvVariableValue);
312             }
313         }
314         else
315             parseDatabaseConfigurationTree(databaseConfiguration, jsonDatabaseConfiguration, sourceForDatabaseConfiguration);
316     }
317     catch (const std::exception& e)
318     {
319         logger->error() << e.what();
320         throw;
321     }
322 }
323
324 void ConfigurationReader::readNamespaceConfigurations(NamespaceConfigurations& namespaceConfigurations)
325 {
326     if (!namespaceConfigurations.isEmpty())
327         SHAREDDATALAYER_ABORT("Namespace configurations can be read only to empty container");
328
329     try
330     {
331         parseNsConfigurationMap(namespaceConfigurations, jsonNamespaceConfigurations);
332     }
333     catch(const std::exception& e)
334     {
335         logger->error() << e.what();
336         throw;
337     }
338 }
339
340
341 template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
342 template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);