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