summaryrefslogtreecommitdiffstats
path: root/src/lib/database/database_connection.h
blob: 0ea887e1d22971bf5e4abbc9b24cd34148ac2f38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
// 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 <asiolink/io_service.h>
#include <cc/data.h>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <exceptions/exceptions.h>
#include <util/reconnect_ctl.h>
#include <functional>
#include <map>
#include <string>

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<bool (util::ReconnectCtlPtr db_reconnect_ctl)> 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<isc::asiolink::IOServicePtr ()> IOServiceAccessor;

/// @brief Pointer to an instance of IOServiceAccessor
typedef boost::shared_ptr<IOServiceAccessor> 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<std::string, std::string> 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 <typename T>
struct Initializer {
    /// @brief Constructor.
    Initializer() : init_(new T()) {
    }

    /// @brief Destructo.
    ~Initializer() = default;

    /// @brief smart pointer to an instance of an initializer.
    std::unique_ptr<T> init_;
};

}  // namespace db
}  // namespace isc

#endif // DATABASE_CONNECTION_H