Kea  1.9.9-git
database_connection.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <cc/cfg_to_element.h>
11 #include <database/db_exceptions.h>
12 #include <database/db_log.h>
13 #include <database/db_messages.h>
14 #include <exceptions/exceptions.h>
15 #include <util/strutil.h>
16 
17 #include <boost/algorithm/string.hpp>
18 #include <boost/foreach.hpp>
19 #include <vector>
20 
21 using namespace std;
22 
23 namespace isc {
24 namespace db {
25 
26 const time_t DatabaseConnection::MAX_DB_TIME = 2147483647;
27 
28 std::string
29 DatabaseConnection::getParameter(const std::string& name) const {
30  ParameterMap::const_iterator param = parameters_.find(name);
31  if (param == parameters_.end()) {
32  isc_throw(BadValue, "Parameter " << name << " not found");
33  }
34  return (param->second);
35 }
36 
38 DatabaseConnection::parse(const std::string& dbaccess) {
40  std::string dba = dbaccess;
41 
42  if (!dba.empty()) {
43  try {
44  vector<string> tokens;
45 
46  // Handle the special case of a password which is enclosed in apostrophes.
47  // Such password may include whitespace.
48  std::string password_prefix = "password='";
49  auto password_pos = dba.find(password_prefix);
50  if (password_pos != string::npos) {
51  // Password starts with apostrophe, so let's find ending apostrophe.
52  auto password_end_pos = dba.find('\'', password_pos + password_prefix.length());
53  if (password_end_pos == string::npos) {
54  // No ending apostrophe. This is wrong.
55  isc_throw(InvalidParameter, "Apostrophe (') expected at the end of password");
56  }
57  // Extract the password value. It starts after the password=' prefix and ends
58  // at the position of ending apostrophe.
59  auto password = dba.substr(password_pos + password_prefix.length(),
60  password_end_pos - password_pos - password_prefix.length());
61  mapped_tokens.insert(make_pair("password", password));
62 
63  // We need to erase the password from the access string because the generic
64  // algorithm parsing other parameters requires that there are no whitespaces
65  // within the parameter values.
66  dba.erase(password_pos, password_prefix.length() + password.length() + 2);
67  // Leading or trailing whitespace may remain after the password removal.
68  dba = util::str::trim(dba);
69  // If the password was the only parameter in the access string, there is
70  // nothing more to do.
71  if (dba.empty()) {
72  return (mapped_tokens);
73  }
74  }
75 
76  // We need to pass a string to is_any_of, not just char*. Otherwise
77  // there are cryptic warnings on Debian6 running g++ 4.4 in
78  // /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
79  // array bounds"
80  boost::split(tokens, dba, boost::is_any_of(string("\t ")));
81  BOOST_FOREACH(std::string token, tokens) {
82  size_t pos = token.find("=");
83  if (pos != string::npos) {
84  string name = token.substr(0, pos);
85  string value = token.substr(pos + 1);
86  mapped_tokens.insert(make_pair(name, value));
87  } else {
88  isc_throw(InvalidParameter, "Cannot parse " << token
89  << ", expected format is name=value");
90  }
91  }
92  } catch (const std::exception& ex) {
93  // We'd obscure the password if we could parse the access string.
95  throw;
96  }
97  }
98 
99  return (mapped_tokens);
100 }
101 
102 std::string
103 DatabaseConnection::redactedAccessString(const ParameterMap& parameters) {
104  // Reconstruct the access string: start of with an empty string, then
105  // work through all the parameters in the original string and add them.
106  std::string access;
107  for (DatabaseConnection::ParameterMap::const_iterator i = parameters.begin();
108  i != parameters.end(); ++i) {
109 
110  // Separate second and subsequent tokens are preceded by a space.
111  if (!access.empty()) {
112  access += " ";
113  }
114 
115  // Append name of parameter...
116  access += i->first;
117  access += "=";
118 
119  // ... and the value, except in the case of the password, where a
120  // redacted value is appended.
121  if (i->first == std::string("password")) {
122  access += "*****";
123  } else {
124  access += i->second;
125  }
126  }
127 
128  return (access);
129 }
130 
131 bool
132 DatabaseConnection::configuredReadOnly() const {
133  std::string readonly_value = "false";
134  try {
135  readonly_value = getParameter("readonly");
136  boost::algorithm::to_lower(readonly_value);
137  } catch (...) {
138  // Parameter "readonly" hasn't been specified so we simply use
139  // the default value of "false".
140  }
141 
142  if ((readonly_value != "false") && (readonly_value != "true")) {
143  isc_throw(DbInvalidReadOnly, "invalid value '" << readonly_value
144  << "' specified for boolean parameter 'readonly'");
145  }
146 
147  return (readonly_value == "true");
148 }
149 
150 void
151 DatabaseConnection::makeReconnectCtl(const std::string& timer_name) {
152  string type = "unknown";
153  unsigned int retries = 0;
154  unsigned int interval = 0;
155 
156  // Assumes that parsing ensures only valid values are present
157  try {
158  type = getParameter("type");
159  } catch (...) {
160  // Wasn't specified so we'll use default of "unknown".
161  }
162 
163  std::string parm_str;
164  try {
165  parm_str = getParameter("max-reconnect-tries");
166  retries = boost::lexical_cast<unsigned int>(parm_str);
167  } catch (...) {
168  // Wasn't specified so we'll use default of 0;
169  }
170 
171  try {
172  parm_str = getParameter("reconnect-wait-time");
173  interval = boost::lexical_cast<unsigned int>(parm_str);
174  } catch (...) {
175  // Wasn't specified so we'll use default of 0;
176  }
177 
178  OnFailAction action = OnFailAction::STOP_RETRY_EXIT;
179  try {
180  parm_str = getParameter("on-fail");
181  action = ReconnectCtl::onFailActionFromText(parm_str);
182  } catch (...) {
183  // Wasn't specified so we'll use default of "stop-retry-exit";
184  }
185 
186  reconnect_ctl_ = boost::make_shared<ReconnectCtl>(type, timer_name, retries,
187  interval, action);
188 }
189 
190 bool
191 DatabaseConnection::invokeDbLostCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
192  if (DatabaseConnection::db_lost_callback_) {
193  return (DatabaseConnection::db_lost_callback_(db_reconnect_ctl));
194  }
195 
196  return (false);
197 }
198 
199 bool
200 DatabaseConnection::invokeDbRecoveredCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
201  if (DatabaseConnection::db_recovered_callback_) {
202  return (DatabaseConnection::db_recovered_callback_(db_reconnect_ctl));
203  }
204 
205  return (false);
206 }
207 
208 bool
209 DatabaseConnection::invokeDbFailedCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
210  if (DatabaseConnection::db_failed_callback_) {
211  return (DatabaseConnection::db_failed_callback_(db_reconnect_ctl));
212  }
213 
214  return (false);
215 }
216 
218 DatabaseConnection::toElement(const ParameterMap& params) {
220 
221  for (auto param: params) {
222  std::string keyword = param.first;
223  std::string value = param.second;
224 
225  if ((keyword == "lfc-interval") ||
226  (keyword == "connect-timeout") ||
227  (keyword == "reconnect-wait-time") ||
228  (keyword == "max-reconnect-tries") ||
229  (keyword == "request-timeout") ||
230  (keyword == "tcp-keepalive") ||
231  (keyword == "port") ||
232  (keyword == "max-row-errors")) {
233  // integer parameters
234  int64_t int_value;
235  try {
236  int_value = boost::lexical_cast<int64_t>(value);
237  result->set(keyword, isc::data::Element::create(int_value));
238  } catch (...) {
240  .arg("integer").arg(keyword).arg(value);
241  }
242  } else if ((keyword == "persist") ||
243  (keyword == "tcp-nodelay") ||
244  (keyword == "readonly")) {
245  if (value == "true") {
246  result->set(keyword, isc::data::Element::create(true));
247  } else if (value == "false") {
248  result->set(keyword, isc::data::Element::create(false));
249  } else {
251  .arg("boolean").arg(keyword).arg(value);
252  }
253  } else if ((keyword == "type") ||
254  (keyword == "user") ||
255  (keyword == "password") ||
256  (keyword == "host") ||
257  (keyword == "name") ||
258  (keyword == "contact-points") ||
259  (keyword == "consistency") ||
260  (keyword == "serial-consistency") ||
261  (keyword == "keyspace") ||
262  (keyword == "on-fail")) {
263  result->set(keyword, isc::data::Element::create(value));
264  } else {
266  .arg("unknown").arg(keyword).arg(value);
267  }
268  }
269 
270  return (result);
271 }
272 
274 DatabaseConnection::toElementDbAccessString(const std::string& dbaccess) {
275  ParameterMap params = parse(dbaccess);
276  return (toElement(params));
277 }
278 
279 std::string
280 ReconnectCtl::onFailActionToText(OnFailAction action) {
281  switch (action) {
282  case OnFailAction::STOP_RETRY_EXIT:
283  return ("stop-retry-exit");
284  case OnFailAction::SERVE_RETRY_EXIT:
285  return ("serve-retry-exit");
286  case OnFailAction::SERVE_RETRY_CONTINUE:
287  return ("serve-retry-continue");
288  }
289  return ("invalid-action-type");
290 }
291 
293 ReconnectCtl::onFailActionFromText(const std::string& text) {
294  if (text == "stop-retry-exit") {
295  return (OnFailAction::STOP_RETRY_EXIT);
296  } else if (text == "serve-retry-exit") {
297  return (OnFailAction::SERVE_RETRY_EXIT);
298  } else if (text == "serve-retry-continue") {
299  return (OnFailAction::SERVE_RETRY_CONTINUE);
300  } else {
301  isc_throw(BadValue, "Invalid action on connection loss: " << text);
302  }
303 }
304 
305 DbCallback DatabaseConnection::db_lost_callback_ = 0;
306 DbCallback DatabaseConnection::db_recovered_callback_ = 0;
307 DbCallback DatabaseConnection::db_failed_callback_ = 0;
308 
309 } // namespace db
310 } // namespace isc
We want to reuse the database backend connection and exchange code for other uses, in particular for hook libraries.
A generic exception that is thrown if a parameter given to a method or function is considered invalid...
DB_LOG & arg(T first, Args...args)
Pass parameters to replace logger placeholders.
Definition: db_log.h:144
const isc::log::MessageID DATABASE_TO_JSON_ERROR
Definition: db_messages.h:25
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
boost::shared_ptr< Element > ElementPtr
Definition: data.h:20
STL namespace.
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition: data.cc:267
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
OnFailAction
Type of action to take on connection loss.
vector< string > tokens(const std::string &text, const std::string &delim, bool escape)
Split String into Tokens.
Definition: strutil.cc:77
Defines the logger used by the top-level component of kea-dhcp-ddns.
std::function< bool(ReconnectCtlPtr db_reconnect_ctl)> DbCallback
Defines a callback prototype for propagating events upward.
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:222
Invalid 'readonly' value specification.
string trim(const string &instring)
Trim Leading and Trailing Spaces.
Definition: strutil.cc:53
isc::log::Logger database_logger("database")
Common database library logger.
Definition: db_log.h:46
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
boost::shared_ptr< ReconnectCtl > ReconnectCtlPtr
Pointer to an instance of ReconnectCtl.