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 #ifndef DATABASE_CONNECTION_H 8 #define DATABASE_CONNECTION_H 9 10 #include <asiolink/io_service.h> 11 #include <cc/data.h> 12 #include <boost/noncopyable.hpp> 13 #include <boost/shared_ptr.hpp> 14 #include <exceptions/exceptions.h> 15 #include <functional> 16 #include <map> 17 #include <string> 18 19 namespace isc { 20 namespace db { 21 22 /// @brief Exception thrown if name of database is not specified 23 class NoDatabaseName : public Exception { 24 public: NoDatabaseName(const char * file,size_t line,const char * what)25 NoDatabaseName(const char* file, size_t line, const char* what) : 26 isc::Exception(file, line, what) {} 27 }; 28 29 /// @brief Exception thrown on failure to open database 30 class DbOpenError : public Exception { 31 public: DbOpenError(const char * file,size_t line,const char * what)32 DbOpenError(const char* file, size_t line, const char* what) : 33 isc::Exception(file, line, what) {} 34 }; 35 36 /// @brief Exception thrown on failure to execute a database function 37 class DbOperationError : public Exception { 38 public: DbOperationError(const char * file,size_t line,const char * what)39 DbOperationError(const char* file, size_t line, const char* what) : 40 isc::Exception(file, line, what) {} 41 }; 42 43 /// @brief Exception thrown when a specific connection has been rendered unusable 44 /// either through loss of connectivity or API lib error 45 class DbConnectionUnusable : public Exception { 46 public: DbConnectionUnusable(const char * file,size_t line,const char * what)47 DbConnectionUnusable(const char* file, size_t line, const char* what) : 48 isc::Exception(file, line, what) {} 49 }; 50 51 52 /// @brief Invalid type exception 53 /// 54 /// Thrown when the factory doesn't recognize the type of the backend. 55 class InvalidType : public Exception { 56 public: InvalidType(const char * file,size_t line,const char * what)57 InvalidType(const char* file, size_t line, const char* what) : 58 isc::Exception(file, line, what) {} 59 }; 60 61 /// @brief Invalid Timeout 62 /// 63 /// Thrown when the timeout specified for the database connection is invalid. 64 class DbInvalidTimeout : public Exception { 65 public: DbInvalidTimeout(const char * file,size_t line,const char * what)66 DbInvalidTimeout(const char* file, size_t line, const char* what) : 67 isc::Exception(file, line, what) {} 68 }; 69 70 /// @brief Invalid 'readonly' value specification. 71 /// 72 /// Thrown when the value of the 'readonly' boolean parameter is invalid. 73 class DbInvalidReadOnly : public Exception { 74 public: DbInvalidReadOnly(const char * file,size_t line,const char * what)75 DbInvalidReadOnly(const char* file, size_t line, const char* what) : 76 isc::Exception(file, line, what) {} 77 }; 78 79 /// @brief Type of action to take on connection loss. 80 enum class OnFailAction { 81 STOP_RETRY_EXIT, 82 SERVE_RETRY_EXIT, 83 SERVE_RETRY_CONTINUE 84 }; 85 86 /// @brief Warehouses DB reconnect control values 87 /// 88 /// When a DatabaseConnection loses connectivity to its backend, it 89 /// creates an instance of this class based on its configuration parameters and 90 /// passes the instance into connection's DB lost callback. This allows 91 /// the layer(s) above the connection to know how to proceed. 92 /// 93 class ReconnectCtl { 94 public: 95 /// @brief Constructor. 96 /// 97 /// @param backend_type type of the caller backend. 98 /// @param timer_name timer associated to this object. 99 /// @param max_retries maximum number of reconnect attempts to make. 100 /// @param retry_interval amount of time to between reconnect attempts. 101 /// @param action which should be taken on connection loss. ReconnectCtl(const std::string & backend_type,const std::string & timer_name,unsigned int max_retries,unsigned int retry_interval,OnFailAction action)102 ReconnectCtl(const std::string& backend_type, const std::string& timer_name, 103 unsigned int max_retries, unsigned int retry_interval, 104 OnFailAction action) : 105 backend_type_(backend_type), timer_name_(timer_name), 106 max_retries_(max_retries), retries_left_(max_retries), 107 retry_interval_(retry_interval), action_(action) {} 108 109 /// @brief Returns the type of the caller backend. backendType()110 std::string backendType() const { 111 return (backend_type_); 112 } 113 114 /// @brief Returns the associated timer name. 115 /// 116 /// @return the associated timer. timerName()117 std::string timerName() const { 118 return (timer_name_); 119 } 120 121 /// @brief Decrements the number of retries remaining 122 /// 123 /// Each call decrements the number of retries by one until zero is reached. 124 /// @return true the number of retries remaining is greater than zero. checkRetries()125 bool checkRetries() { 126 return (retries_left_ ? --retries_left_ : false); 127 } 128 129 /// @brief Returns the maximum number of retries allowed. maxRetries()130 unsigned int maxRetries() { 131 return (max_retries_); 132 } 133 134 /// @brief Returns the number for retries remaining. retriesLeft()135 unsigned int retriesLeft() { 136 return (retries_left_); 137 } 138 139 /// @brief Returns the amount of time to wait between reconnect attempts. retryInterval()140 unsigned int retryInterval() { 141 return (retry_interval_); 142 } 143 144 /// @brief Resets the retries count. resetRetries()145 void resetRetries() { 146 retries_left_ = max_retries_; 147 } 148 149 /// @brief Return true if the connection loss should affect the service, 150 /// false otherwise alterServiceState()151 bool alterServiceState() { 152 return (action_ == OnFailAction::STOP_RETRY_EXIT); 153 } 154 155 /// @brief Return true if the connection recovery mechanism should shut down 156 /// the server on failure, false otherwise. exitOnFailure()157 bool exitOnFailure() { 158 return ((action_ == OnFailAction::STOP_RETRY_EXIT) || 159 (action_ == OnFailAction::SERVE_RETRY_EXIT)); 160 } 161 162 /// @brief Convert action to string. 163 /// 164 /// @param action The action type to be converted to text. 165 /// @return The text representation of the action type. 166 static std::string onFailActionToText(OnFailAction action); 167 168 /// @brief Convert string to action. 169 /// 170 /// @param text The text to be converted to action type. 171 /// @return The action type corresponding to the text representation. 172 static OnFailAction onFailActionFromText(const std::string& text); 173 174 private: 175 176 /// @brief Caller backend type. 177 const std::string backend_type_; 178 179 /// @brief Timer associated to this object. 180 std::string timer_name_; 181 182 /// @brief Maximum number of retry attempts to make. 183 unsigned int max_retries_; 184 185 /// @brief Number of attempts remaining. 186 unsigned int retries_left_; 187 188 /// @brief The amount of time to wait between reconnect attempts. 189 unsigned int retry_interval_; 190 191 /// @brief Action to take on connection loss. 192 OnFailAction action_; 193 }; 194 195 /// @brief Pointer to an instance of ReconnectCtl 196 typedef boost::shared_ptr<ReconnectCtl> ReconnectCtlPtr; 197 198 /// @brief Defines a callback prototype for propagating events upward 199 typedef std::function<bool (ReconnectCtlPtr db_reconnect_ctl)> DbCallback; 200 201 /// @brief Function which returns the IOService that can be used to recover the 202 /// connection. 203 /// 204 /// This accessor is used to lazy retrieve the IOService when the connection is 205 /// lost. It is useful to retrieve it at a later time to support hook libraries 206 /// which create managers on load and set IOService later on by using the 207 /// dhcp4_srv_configured and dhcp6_srv_configured hooks. 208 typedef std::function<isc::asiolink::IOServicePtr ()> IOServiceAccessor; 209 210 /// @brief Pointer to an instance of IOServiceAccessor 211 typedef boost::shared_ptr<IOServiceAccessor> IOServiceAccessorPtr; 212 213 /// @brief Common database connection class. 214 /// 215 /// This class provides functions that are common for establishing 216 /// connection with different types of databases; enables operations 217 /// on access parameters strings. In particular, it provides a way 218 /// to parse parameters in key=value format. This class is expected 219 /// to be a base class for all @ref isc::dhcp::LeaseMgr and possibly 220 /// @ref isc::dhcp::BaseHostDataSource derived classes. 221 class DatabaseConnection : public boost::noncopyable { 222 public: 223 224 /// @brief Defines maximum value for time that can be reliably stored. 225 /// 226 /// @todo: Is this common for MySQL and Postgres? Maybe we should have 227 /// specific values for each backend? 228 /// 229 /// If I'm still alive I'll be too old to care. You fix it. 230 static const time_t MAX_DB_TIME; 231 232 /// @brief Database configuration parameter map 233 typedef std::map<std::string, std::string> ParameterMap; 234 235 /// @brief Constructor 236 /// 237 /// @param parameters A data structure relating keywords and values 238 /// concerned with the database. 239 /// @param callback The connection recovery callback. 240 DatabaseConnection(const ParameterMap& parameters, 241 DbCallback callback = DbCallback()) parameters_(parameters)242 : parameters_(parameters), callback_(callback), unusable_(false) { 243 } 244 245 /// @brief Destructor ~DatabaseConnection()246 virtual ~DatabaseConnection(){}; 247 248 /// @brief Instantiates a ReconnectCtl based on the connection's 249 /// reconnect parameters 250 /// 251 /// @param timer_name of the timer used for the ReconnectCtl object. 252 virtual void makeReconnectCtl(const std::string& timer_name); 253 254 /// @brief The reconnect settings. 255 /// 256 /// @return The reconnect settings. reconnectCtl()257 ReconnectCtlPtr reconnectCtl() { 258 return (reconnect_ctl_); 259 } 260 261 /// @brief Returns value of a connection parameter. 262 /// 263 /// @param name Name of the parameter which value should be returned. 264 /// @return Value of one of the connection parameters. 265 /// @throw BadValue if parameter is not found 266 std::string getParameter(const std::string& name) const; 267 268 /// @brief Parse database access string 269 /// 270 /// Parses the string of "keyword=value" pairs and separates them 271 /// out into the map. A value of the password parameter may include 272 /// whitespace in which case it must be surrounded by apostrophes. 273 /// 274 /// @param dbaccess Database access string. 275 /// 276 /// @return @ref ParameterMap of keyword/value pairs. 277 static ParameterMap parse(const std::string& dbaccess); 278 279 /// @brief Redact database access string 280 /// 281 /// Takes the database parameters and returns a database access string 282 /// passwords replaced by asterisks. This string is used in log messages. 283 /// 284 /// @param parameters Database access parameters (output of "parse"). 285 /// 286 /// @return Redacted database access string. 287 static std::string redactedAccessString(const ParameterMap& parameters); 288 289 /// @brief Convenience method checking if database should be opened with 290 /// read only access. 291 /// 292 /// @return true if "readonly" parameter is specified and set to true; 293 /// false if "readonly" parameter is not specified or it is specified 294 /// and set to false. 295 bool configuredReadOnly() const; 296 297 /// @brief Invokes the connection's lost connectivity callback 298 /// 299 /// @return Returns the result of the callback or false if there is no 300 /// callback. 301 static bool invokeDbLostCallback(const ReconnectCtlPtr& db_reconnect_ctl); 302 303 /// @brief Invokes the connection's restored connectivity callback 304 /// 305 /// @return Returns the result of the callback or false if there is no 306 /// callback. 307 static bool invokeDbRecoveredCallback(const ReconnectCtlPtr& db_reconnect_ctl); 308 309 /// @brief Invokes the connection's restore failed connectivity callback 310 /// 311 /// @return Returns the result of the callback or false if there is no 312 /// callback. 313 static bool invokeDbFailedCallback(const ReconnectCtlPtr& db_reconnect_ctl); 314 315 /// @brief Unparse a parameter map 316 /// 317 /// @param params the parameter map to unparse 318 /// @return a pointer to configuration 319 static isc::data::ElementPtr toElement(const ParameterMap& params); 320 321 /// @brief Unparse an access string 322 /// 323 /// @param dbaccess the database access string 324 /// @return a pointer to configuration 325 static isc::data::ElementPtr toElementDbAccessString(const std::string& dbaccess); 326 327 /// @brief Optional callback function to invoke if an opened connection is 328 /// lost 329 static DbCallback db_lost_callback_; 330 331 /// @brief Optional callback function to invoke if an opened connection 332 /// recovery succeeded 333 static DbCallback db_recovered_callback_; 334 335 /// @brief Optional callback function to invoke if an opened connection 336 /// recovery failed 337 static DbCallback db_failed_callback_; 338 339 /// @brief Throws an exception if the connection is not usable. 340 /// @throw DbConnectionUnusable checkUnusable()341 void checkUnusable() { 342 if (unusable_) { 343 isc_throw (DbConnectionUnusable, "Attempt to use an invalid connection"); 344 } 345 } 346 347 /// @brief Flag which indicates if connection is unusable. 348 /// 349 /// @return true if the connection is unusable, false otherwise isUnusable()350 bool isUnusable() { 351 return (unusable_); 352 } 353 354 protected: 355 /// @brief Sets the unusable flag to true. markUnusable()356 void markUnusable() { unusable_ = true; } 357 358 private: 359 360 /// @brief List of parameters passed in dbconfig 361 /// 362 /// That will be mostly used for storing database name, username, 363 /// password and other parameters required for DB access. It is not 364 /// intended to keep any DHCP-related parameters. 365 ParameterMap parameters_; 366 367 protected: 368 369 /// @brief The callback used to recover the connection. 370 DbCallback callback_; 371 372 private: 373 374 /// @brief Indicates if the connection can no longer be used for normal 375 /// operations. Typically a connection is marked unusable after an unrecoverable 376 /// DB error. There may be a time when the connection exists, after 377 /// such an event, during which it cannot be used for anything beyond checking 378 /// parameters and error information. This flag can be used as a guard in 379 /// code to prevent inadvertent use of a broken connection. 380 bool unusable_; 381 382 /// @brief Reconnect settings. 383 ReconnectCtlPtr reconnect_ctl_; 384 }; 385 386 } // namespace db 387 } // namespace isc 388 389 #endif // DATABASE_CONNECTION_H 390