1 // Copyright (C) 2018-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 BASE_CONFIG_BACKEND_MGR_H
8 #define BASE_CONFIG_BACKEND_MGR_H
9 
10 #include <config_backend/base_config_backend.h>
11 #include <database/database_connection.h>
12 #include <database/backend_selector.h>
13 #include <exceptions/exceptions.h>
14 #include <boost/shared_ptr.hpp>
15 #include <functional>
16 #include <map>
17 #include <string>
18 
19 namespace isc {
20 namespace cb {
21 
22 /// @brief Base class for Configuration Backend Managers (CBM).
23 ///
24 /// Each Kea server supporting Configuration Backend feature implements
25 /// a "manager" class which holds information about supported and
26 /// configured backends and provides access to the backends. This is
27 /// similar to @c HostMgr and @c LeaseMgr singletons being used by the
28 /// DHCP servers.
29 ///
30 /// The Config Backend Managers are typically implemented as singletons
31 /// which can be accessed from any place within the server code. This
32 /// includes server configuration, data fetching during normal server
33 /// operation and data management, including processing of control
34 /// commands implemented within hooks libraries.
35 ///
36 /// The @c BaseConfigBackendMgr is a base class for all CBMs implemented
37 /// for respective Kea servers. It includes mechanisms to register config
38 /// backend factory functions and to create instances of the backends using
39 /// those factory functions as a result of server configuration. The mechanism
40 /// of factory functions registration is useful in cases when the config
41 /// backend is implemented within the hook library. Such hook library
42 /// registers factory function in its @c load function and the server
43 /// simply calls this function to create the instance of this backend when
44 /// instructed to do so via configuration. Similar mechanism exists in
45 /// DHCP @c HostMgr.
46 ///
47 /// Unlike @c HostMgr, the CBMs do not directly expose API to fetch and
48 /// manipulate the data in the database. This is done via, so called,
49 /// Configuration Backends Pools. See @c BaseConfigBackendPool for
50 /// details. The @c BaseConfigBackendMgr is provided with the pool type
51 /// via class template parameter. Respective CBM implementations
52 /// use their own pools, which provide APIs appropriate for those
53 /// implementations.
54 ///
55 /// @tparam ConfgBackendPoolType Type of the configuration backend pool
56 /// to be used by the manager. It must derive from @c BaseConfigBackendPool
57 /// template class.
58 template<typename ConfigBackendPoolType>
59 class BaseConfigBackendMgr {
60 public:
61 
62     /// @brief Pointer to the configuration backend pool.
63     typedef boost::shared_ptr<ConfigBackendPoolType> ConfigBackendPoolPtr;
64 
65     /// @brief Type of the backend factory function.
66     ///
67     /// Factory function returns a pointer to the instance of the configuration
68     /// backend created.
69     typedef std::function<typename ConfigBackendPoolType::ConfigBackendTypePtr
70                           (const db::DatabaseConnection::ParameterMap&)> Factory;
71 
72     /// @brief Constructor.
BaseConfigBackendMgr()73     BaseConfigBackendMgr()
74         : factories_(), pool_(new ConfigBackendPoolType()) {
75     }
76 
77     /// @brief Registers new backend factory function for a given backend type.
78     ///
79     /// The typical usage of this function is to make the CBM aware of a
80     /// configuration backend implementation. This implementation may exist
81     /// in a hooks library. In such case, this function should be called from
82     /// the @c load function in this library. When the backend is registered,
83     /// the server will use it when required by the configuration, i.e. a
84     /// user includes configuration backend of that type in the
85     /// "config-databases" list.
86     ///
87     /// If the backend of the given type has already been registered, perhaps
88     /// by another hooks library, the CBM will refuse to register another
89     /// backend of the same type.
90     ///
91     /// @param db_type Backend type, e.g. "mysql".
92     /// @param factory Pointer to the backend factory function.
93     ///
94     /// @return true if the backend has been successfully registered, false
95     /// if another backend of this type already exists.
registerBackendFactory(const std::string & db_type,const Factory & factory)96     bool registerBackendFactory(const std::string& db_type,
97                                 const Factory& factory) {
98         // Check if this backend has been already registered.
99         if (factories_.count(db_type)) {
100             return (false);
101         }
102 
103         // Register the new backend.
104         factories_.insert(std::make_pair(db_type, factory));
105         return (true);
106     }
107 
108     /// @brief Unregisters the backend factory function for a given backend type.
109     ///
110     /// This function is used to remove the factory function and all backend instances
111     /// for a given backend type.  Typically, it would be called when unloading the
112     /// a config backend hook library, and thus called by the library's @c unload
113     /// function.
114     ///
115     /// @param db_type Backend type, e.g. "mysql".
116     ///
117     /// @return false if no factory for the given type was unregistered, true
118     /// if the factory was removed.
unregisterBackendFactory(const std::string & db_type)119     bool unregisterBackendFactory(const std::string& db_type) {
120         // Look for it.
121         auto index = factories_.find(db_type);
122 
123         // If it's there remove it
124         if (index != factories_.end()) {
125             factories_.erase(index);
126             pool_->delAllBackends(db_type);
127             return (true);
128 
129         }
130 
131         return (false);
132     }
133 
134     /// @brief Create an instance of a configuration backend.
135     ///
136     /// This method uses provided @c dbaccess string representing database
137     /// connection information to create an instance of the database
138     /// backend. If the specified backend type is not supported, i.e. there
139     /// is no relevant factory function registered, an exception is thrown.
140     ///
141     /// @param dbaccess Database access string being a collection of
142     /// key=value pairs.
143     ///
144     /// @throw InvalidParameter if access string lacks database type value.
145     /// @throw db::InvalidType if the type of the database backend is not
146     /// supported.
147     /// @throw Unexpected if the backend factory function returned NULL.
addBackend(const std::string & dbaccess)148     void addBackend(const std::string& dbaccess) {
149         // Parse the access string into a map of parameters.
150         db::DatabaseConnection::ParameterMap parameters =
151             db::DatabaseConnection::parse(dbaccess);
152 
153         // Get the database type to locate a factory function.
154         db::DatabaseConnection::ParameterMap::iterator it = parameters.find("type");
155         if (it == parameters.end()) {
156             isc_throw(InvalidParameter, "Config backend specification lacks the "
157                       "'type' keyword");
158         }
159 
160         std::string db_type = it->second;
161         auto index = factories_.find(db_type);
162 
163         // No match?
164         if (index == factories_.end()) {
165             isc_throw(db::InvalidType, "The type of the configuration backend: '" <<
166                       db_type << "' is not supported");
167         }
168 
169         // Call the factory and push the pointer on sources.
170         auto backend = index->second(parameters);
171         if (!backend) {
172             isc_throw(Unexpected, "Config database " << db_type <<
173                       " factory returned NULL");
174         }
175 
176         // Backend instance created successfully.
177         pool_->addBackend(backend);
178     }
179 
180     /// @brief Removes all backends from the pool.
delAllBackends()181     void delAllBackends() {
182         pool_->delAllBackends();
183     }
184 
185     /// @brief Delete a config backend manager.
186     ///
187     /// Delete the first instance of a config database manager which matches
188     /// specific parameters.
189     /// This should have the effect of closing the database connection.
190     ///
191     /// @param db_type Backend to remove.
192     /// @param dbaccess Database access string being a collection of
193     /// key=value pairs.
194     /// @param if_unusable Flag which indicates if the config backend should be
195     /// deleted only if it is unusable.
196     /// @return false when not removed because it is not found or because it is
197     /// still usable (if_unusable is true), true otherwise.
delBackend(const std::string & db_type,const std::string & dbaccess,bool if_unusable)198     bool delBackend(const std::string& db_type, const std::string& dbaccess,
199                     bool if_unusable) {
200         return (pool_->del(db_type, dbaccess, if_unusable));
201     }
202 
203     /// @brief Returns underlying config backend pool.
getPool()204     ConfigBackendPoolPtr getPool() const {
205         return (pool_);
206     }
207 
208 protected:
209 
210     /// @brief A map holding registered backend factory functions.
211     std::map<std::string, Factory> factories_;
212 
213     /// @brief Pointer to the configuration backends pool.
214     ConfigBackendPoolPtr pool_;
215 };
216 
217 } // end of namespace isc::cb
218 } // end of namespace isc
219 
220 #endif // BASE_CONFIG_BACKEND_MGR_H
221