// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifndef DATABASE_CONNECTION_H #define DATABASE_CONNECTION_H #include #include #include #include #include #include #include #include #include namespace isc { namespace db { /// @brief Exception thrown if name of database is not specified class NoDatabaseName : public Exception { public: NoDatabaseName(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Exception thrown on failure to open database class DbOpenError : public Exception { public: DbOpenError(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Exception thrown on failure to open database but permit retries class DbOpenErrorWithRetry : public Exception { public: DbOpenErrorWithRetry(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Exception thrown on failure to execute a database function class DbOperationError : public Exception { public: DbOperationError(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Exception thrown when a specific connection has been rendered unusable /// either through loss of connectivity or API lib error class DbConnectionUnusable : public Exception { public: DbConnectionUnusable(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Invalid type exception /// /// Thrown when the factory doesn't recognize the type of the backend. class InvalidType : public Exception { public: InvalidType(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Invalid Timeout /// /// Thrown when the timeout specified for the database connection is invalid. class DbInvalidTimeout : public Exception { public: DbInvalidTimeout(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Invalid port number /// /// Thrown when the port number specified for the database connection is invalid. class DbInvalidPort : public Exception { public: DbInvalidPort(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Invalid 'readonly' value specification. /// /// Thrown when the value of the 'readonly' boolean parameter is invalid. class DbInvalidReadOnly : public Exception { public: DbInvalidReadOnly(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Thrown when an initialization of the schema failed. class SchemaInitializationFailed : public Exception { public: SchemaInitializationFailed(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) {} }; /// @brief Defines a callback prototype for propagating events upward typedef std::function DbCallback; /// @brief Function which returns the IOService that can be used to recover the /// connection. /// /// This accessor is used to lazy retrieve the IOService when the connection is /// lost. It is useful to retrieve it at a later time to support hook libraries /// which create managers on load and set IOService later on by using the /// dhcp4_srv_configured and dhcp6_srv_configured hooks. typedef std::function IOServiceAccessor; /// @brief Pointer to an instance of IOServiceAccessor typedef boost::shared_ptr IOServiceAccessorPtr; /// @brief Common database connection class. /// /// This class provides functions that are common for establishing /// connection with different types of databases; enables operations /// on access parameters strings. In particular, it provides a way /// to parse parameters in key=value format. This class is expected /// to be a base class for all @ref isc::dhcp::LeaseMgr and possibly /// @ref isc::dhcp::BaseHostDataSource derived classes. class DatabaseConnection : public boost::noncopyable { public: /// @brief Defines maximum value for time that can be reliably stored. /// /// @todo: Is this common for MySQL and Postgres? Maybe we should have /// specific values for each backend? /// /// If I'm still alive I'll be too old to care. You fix it. static const time_t MAX_DB_TIME; /// @brief Database configuration parameter map typedef std::map ParameterMap; /// @brief Constructor /// /// @param parameters A data structure relating keywords and values /// concerned with the database. /// @param callback The connection recovery callback. DatabaseConnection(const ParameterMap& parameters, DbCallback callback = DbCallback()) : parameters_(parameters), callback_(callback), unusable_(false) { } /// @brief Destructor virtual ~DatabaseConnection(){}; /// @brief Instantiates a ReconnectCtl based on the connection's /// reconnect parameters /// /// @param timer_name of the timer used for the ReconnectCtl object. virtual void makeReconnectCtl(const std::string& timer_name); /// @brief The reconnect settings. /// /// @return The reconnect settings. util::ReconnectCtlPtr reconnectCtl() { return (reconnect_ctl_); } /// @brief Returns value of a connection parameter. /// /// @param name Name of the parameter which value should be returned. /// @return Value of one of the connection parameters. /// @throw BadValue if parameter is not found std::string getParameter(const std::string& name) const; /// @brief Parse database access string /// /// Parses the string of "keyword=value" pairs and separates them /// out into the map. A value of the password parameter may include /// whitespace in which case it must be surrounded by apostrophes. /// /// @param dbaccess Database access string. /// /// @return @ref ParameterMap of keyword/value pairs. static ParameterMap parse(const std::string& dbaccess); /// @brief Redact database access string /// /// Takes the database parameters and returns a database access string /// passwords replaced by asterisks. This string is used in log messages. /// /// @param parameters Database access parameters (output of "parse"). /// /// @return Redacted database access string. static std::string redactedAccessString(const ParameterMap& parameters); /// @brief Convenience method checking if database should be opened with /// read only access. /// /// @return true if "readonly" parameter is specified and set to true; /// false if "readonly" parameter is not specified or it is specified /// and set to false. bool configuredReadOnly() const; /// @brief Invokes the connection's lost connectivity callback /// /// @return Returns the result of the callback or false if there is no /// callback. static bool invokeDbLostCallback(const util::ReconnectCtlPtr& db_reconnect_ctl); /// @brief Invokes the connection's restored connectivity callback /// /// @return Returns the result of the callback or false if there is no /// callback. static bool invokeDbRecoveredCallback(const util::ReconnectCtlPtr& db_reconnect_ctl); /// @brief Invokes the connection's restore failed connectivity callback /// /// @return Returns the result of the callback or false if there is no /// callback. static bool invokeDbFailedCallback(const util::ReconnectCtlPtr& db_reconnect_ctl); /// @brief Unparse a parameter map /// /// @param params the parameter map to unparse /// @return a pointer to configuration static isc::data::ElementPtr toElement(const ParameterMap& params); /// @brief Unparse an access string /// /// @param dbaccess the database access string /// @return a pointer to configuration static isc::data::ElementPtr toElementDbAccessString(const std::string& dbaccess); /// @brief Sets IO service to be used by the database backends. /// /// @param io_service IOService object, used for all ASIO operations. static void setIOService(const isc::asiolink::IOServicePtr& io_service) { io_service_ = io_service; } /// @brief Returns pointer to the IO service. static isc::asiolink::IOServicePtr& getIOService() { return (io_service_); } /// @brief Optional callback function to invoke if an opened connection is /// lost static DbCallback db_lost_callback_; /// @brief Optional callback function to invoke if an opened connection /// recovery succeeded static DbCallback db_recovered_callback_; /// @brief Optional callback function to invoke if an opened connection /// recovery failed static DbCallback db_failed_callback_; /// @brief Flag which indicates if the database connection should be retried /// on fail. /// /// Allow the first database connection attempt to fail and start recovery. /// Sequential tries invoked by the dbReconnect callback should not start yet /// another database connection attempt. static bool retry_; /// @brief Throws an exception if the connection is not usable. /// @throw DbConnectionUnusable void checkUnusable() { if (unusable_) { isc_throw (DbConnectionUnusable, "Attempt to use an invalid connection"); } } /// @brief Flag which indicates if connection is unusable. /// /// @return true if the connection is unusable, false otherwise bool isUnusable() { return (unusable_); } /// @brief Test mode flag (default false). static bool test_mode_; /// @brief RAII device to set the test mode. class EnterTest { public: /// @brief Constructor. /// Set the test mode to true. EnterTest() { DatabaseConnection::test_mode_ = true; } /// @brief Destructor. /// Reset the test mode to false. ~EnterTest() { DatabaseConnection::test_mode_ = false; } }; protected: /// @brief Sets the unusable flag to true. void markUnusable() { unusable_ = true; } private: /// @brief List of parameters passed in dbconfig /// /// That will be mostly used for storing database name, username, /// password and other parameters required for DB access. It is not /// intended to keep any DHCP-related parameters. ParameterMap parameters_; protected: /// @brief The callback used to recover the connection. DbCallback callback_; private: /// @brief Indicates if the connection can no longer be used for normal /// operations. Typically a connection is marked unusable after an unrecoverable /// DB error. There may be a time when the connection exists, after /// such an event, during which it cannot be used for anything beyond checking /// parameters and error information. This flag can be used as a guard in /// code to prevent inadvertent use of a broken connection. bool unusable_; /// @brief Reconnect settings. util::ReconnectCtlPtr reconnect_ctl_; /// The IOService object, used for all ASIO operations. static isc::asiolink::IOServicePtr io_service_; }; /// @brief RAII class to enable DB reconnect retries on server startup. class DbConnectionInitWithRetry { public: /// @brief Constructor. /// /// Enable DB reconnect retries on server startup. DbConnectionInitWithRetry() { DatabaseConnection::retry_ = true; } /// @brief Destructor. /// /// Disable DB reconnect retries. ~DbConnectionInitWithRetry() { DatabaseConnection::retry_ = false; } }; template struct Initializer { /// @brief Constructor. Initializer() : init_(new T()) { } /// @brief Destructo. ~Initializer() = default; /// @brief smart pointer to an instance of an initializer. std::unique_ptr init_; }; } // namespace db } // namespace isc #endif // DATABASE_CONNECTION_H