Add first version
[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     jsonDatabaseConfiguration(boost::none),
208     logger(logger)
209 {
210     auto envStr = system.getenv(dbHostEnvVariableName.c_str());
211     if (envStr)
212     {
213         dbHostEnvVariableValue = envStr;
214         sourceForDatabaseConfiguration = dbHostEnvVariableName;
215         auto envStr = system.getenv(dbPortEnvVariableName.c_str());
216         if (envStr)
217             dbPortEnvVariableValue = envStr;
218     }
219
220     readConfigurationFromDirectories(directories);
221 }
222
223 ConfigurationReader::~ConfigurationReader()
224 {
225 }
226
227 void ConfigurationReader::readConfigurationFromDirectories(const Directories& directories)
228 {
229     for (const auto& i : findConfigurationFiles(directories))
230         readConfiguration(i, i);
231 }
232
233 void ConfigurationReader::readConfigurationFromInputStream(const std::istream& input)
234 {
235     jsonNamespaceConfigurations.clear();
236     readConfiguration(const_cast<std::istream&>(input), "<istream>");
237 }
238
239 template<typename T>
240 void ConfigurationReader::readConfiguration(T& input, const std::string& currentSourceName)
241 {
242     boost::property_tree::ptree propertyTree;
243
244     try
245     {
246         boost::property_tree::read_json(input, propertyTree);
247     }
248     catch (const boost::property_tree::json_parser::json_parser_error& e)
249     {
250         std::ostringstream os;
251         os << "error in SDL configuration " << currentSourceName << " at line " << e.line() << ": ";
252         os << e.message();
253         logger->error() << os.str();
254         throw Exception(os.str());
255     }
256
257     // Environment variable configuration overrides json configuration
258     if (sourceForDatabaseConfiguration != dbHostEnvVariableName)
259     {
260         const auto databaseConfiguration(propertyTree.get_child_optional("database"));
261         if (databaseConfiguration)
262         {
263             jsonDatabaseConfiguration = databaseConfiguration;
264             sourceForDatabaseConfiguration = currentSourceName;
265         }
266     }
267
268     const auto namespaceConfigurations(propertyTree.get_child_optional("sharedDataLayer"));
269     if (namespaceConfigurations)
270     {
271         for(const auto& namespaceConfiguration : *namespaceConfigurations)
272         {
273             const auto namespacePrefix = get<std::string>(namespaceConfiguration.second, "namespacePrefix", currentSourceName);
274             jsonNamespaceConfigurations[namespacePrefix] = std::make_pair(namespaceConfiguration.second, currentSourceName);
275         }
276     }
277 }
278
279 void ConfigurationReader::readDatabaseConfiguration(DatabaseConfiguration& databaseConfiguration)
280 {
281     if (!databaseConfiguration.isEmpty())
282         SHAREDDATALAYER_ABORT("Database configuration can be read only to empty container");
283
284     try
285     {
286         if (sourceForDatabaseConfiguration == dbHostEnvVariableName)
287         {
288             // Currently hard coded to redis-standalone, because RIC dbaas does not support Redis cluster configuration.
289                 validateAndSetDbType("redis-standalone", databaseConfiguration, sourceForDatabaseConfiguration);
290                 if (dbPortEnvVariableValue.empty())
291                         parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue, sourceForDatabaseConfiguration);
292                 else
293                         parseDatabaseServersConfigurationFromString(databaseConfiguration, dbHostEnvVariableValue + ":" + dbPortEnvVariableValue, sourceForDatabaseConfiguration);
294         }
295         else
296             parseDatabaseConfigurationTree(databaseConfiguration, jsonDatabaseConfiguration, sourceForDatabaseConfiguration);
297     }
298     catch (const std::exception& e)
299     {
300         logger->error() << e.what();
301         throw;
302     }
303 }
304
305 void ConfigurationReader::readNamespaceConfigurations(NamespaceConfigurations& namespaceConfigurations)
306 {
307     if (!namespaceConfigurations.isEmpty())
308         SHAREDDATALAYER_ABORT("Namespace configurations can be read only to empty container");
309
310     try
311     {
312         parseNsConfigurationMap(namespaceConfigurations, jsonNamespaceConfigurations);
313     }
314     catch(const std::exception& e)
315     {
316         logger->error() << e.what();
317         throw;
318     }
319 }
320
321
322 template void ConfigurationReader::readConfiguration(const std::string&, const std::string&);
323 template void ConfigurationReader::readConfiguration(std::istream&, const std::string&);