summaryrefslogtreecommitdiffstats
path: root/src/lib/process/cb_ctl_base.h
blob: 7baf8dd725ac3d1efaadd7ecb3e74f018a3ac3a0 (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
371
372
373
// Copyright (C) 2019-2020 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 CB_CTL_BASE_H
#define CB_CTL_BASE_H

#include <database/audit_entry.h>
#include <database/backend_selector.h>
#include <database/server_selector.h>
#include <process/config_base.h>
#include <process/config_ctl_info.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <process/d_log.h>

namespace isc {
namespace process {


/// @brief Base class for implementing server specific mechanisms to control
/// the use of the Configuration Backends.
///
/// Every Kea server using Config Backend as a configuration repository
/// fetches configuration available for this server during startup and then
/// periodically while it is running. In both cases, the server has to
/// take into account that there is some existing configuration that the
/// server already knows, into which the configuration from the database
/// has to be merged.
///
/// When the server starts up, the existing configuration is the one that
/// the server reads from the configuration file. Usually, the configuration
/// fetched from the file will be disjointed with the configuration in the
/// database, e.g. all subnets should be specified either in the configuration
/// file or a database, not in both. However, there may be other cases when
/// option definitions are held in the configuration file, but the DHCP
/// options using them are stored in the database. The typical configuration
/// sequence upon the server startup will be to build the staging
/// configuration from the data stored in the configuration file and then
/// build another partial configuration from the data fetched from the
/// database. Finally, both configurations should be merged and committed
/// if they are deemed sane.
///
/// When the server is already running it may use "audit" (a.k.a. journal)
/// to periodically check if there are any pending configuration updates.
/// If changes are present, it will be fetched and used to create a new
/// configuration object (derived from the @c ConfigBase) holding this
/// partial configuration. This configuration has to be subsequently
/// merged into the current configuration that the server is using.
///
/// Note the difference between these two use cases is that in the first
/// case the fetched configuration is fetched into the staging configuration
/// and then committed, and in the second case it has to be directly merged
/// into the running configuration.
///
/// This class contains some common logic to facilitate both scenarios which
/// will be used by all server types. It also contains some pure virtual
/// methods to be implemented by specific servers. The common logic includes
/// the following operations:
/// - use the "config-control" specification to connect to the specified
///   databases via the configuration backends,
/// - fetch the audit trail to detect configuration updates,
/// - store the timestamp and id of the most recent audit entry fetched
///   from the database, so as next time it can fetch only the later updates.
///
/// The server specific part to be implemented in derived classes must
/// correctly interpret the audit entries and make appropriate API calls
/// to fetch the indicated configuration changes. It should also merge
/// the fetched configuration into the staging or current configuration.
/// Note that this class doesn't recognize whether it is a staging or
/// current configuration it is merging into. It simply uses the instance
/// provided by the caller.
///
/// @tparam ConfigBackendMgrType Type of the Config Backend Manager used
/// by the server implementing this class. For example, for the DHCPv4
/// server it will be @c ConfigBackendDHCPv4Mgr.
template<typename ConfigBackendMgrType>
class CBControlBase {
public:

    /// @brief Fetch mode used in invocations to @c databaseConfigFetch.
    ///
    /// One of the values indicates that the entire configuration should
    /// be fetched. The other one indicates that only configuration updates
    /// should be fetched.
    enum class FetchMode {
        FETCH_ALL,
        FETCH_UPDATE
    };

    /// @brief Constructor.
    ///
    /// Sets the time of the last fetched audit entry to Jan 1st, 2000,
    /// with id 0.
    CBControlBase()
        : last_audit_revision_time_(getInitialAuditRevisionTime()),
          last_audit_revision_id_(0) {
    }

    /// @brief Virtual destructor.
    ///
    /// It is always needed when there are virtual methods.
    virtual ~CBControlBase() {
        databaseConfigDisconnect();
    }

    /// @brief Resets the state of this object.
    ///
    /// Disconnects the configuration backends resets the recorded last
    /// audit revision time and id.
    void reset() {
        databaseConfigDisconnect();
        last_audit_revision_time_ = getInitialAuditRevisionTime();
        last_audit_revision_id_ = 0;
    }

    /// @brief (Re)connects to the specified configuration backends.
    ///
    /// This method disconnects from any existing configuration backends
    /// and then connects to those listed in the @c ConfigControlInfo
    /// structure within the @c srv_cfg argument. This method is called
    /// when the server starts up. It is not called when it merely
    /// fetches configuration updates.
    ///
    /// @param srv_cfg Pointer to the staging server configuration.
    ///
    /// @return true if the server found at least one backend to connect to,
    /// false if there are no backends available.
    bool databaseConfigConnect(const ConfigPtr& srv_cfg) {
        // We need to get rid of any existing backends.  These would be any
        // opened by previous configuration cycle.
        databaseConfigDisconnect();

        // Fetch the config-control info.
        ConstConfigControlInfoPtr config_ctl = srv_cfg->getConfigControlInfo();
        if (!config_ctl || config_ctl->getConfigDatabases().empty()) {
            // No config dbs, nothing to do.
            return (false);
        }

        // Iterate over the configured DBs and instantiate them.
        for (auto db : config_ctl->getConfigDatabases()) {
            LOG_INFO(dctl_logger, DCTL_OPEN_CONFIG_DB)
                .arg(db.redactedAccessString());
            getMgr().addBackend(db.getAccessString());
        }

        // Let the caller know we have opened DBs.
        return (true);
    }

    /// @brief Disconnects from the configuration backends.
    void databaseConfigDisconnect() {
        getMgr().delAllBackends();
    }

    /// @brief Fetches the entire or partial configuration from the database.
    ///
    /// This method is called by the starting up server to fetch and merge
    /// the entire configuration from the database or to fetch configuration
    /// updates periodically, e.g. as a result of triggering an interval
    /// timer callback.
    ///
    /// @param srv_cfg pointer to the staging configuration that should
    /// hold the config backends list and other partial configuration read
    /// from the file in case the method is called upon the server's start
    /// up. It is a pointer to the current server configuration if the
    /// method is called to fetch configuration updates.
    /// @param fetch_mode value indicating if the method is called upon the
    /// server start up or it is called to fetch configuration updates.
    virtual void databaseConfigFetch(const ConfigPtr& srv_cfg,
                                     const FetchMode& fetch_mode = FetchMode::FETCH_ALL) {
        // If the server starts up we need to connect to the database(s).
        // If there are no databases available simply do nothing.
        if ((fetch_mode == FetchMode::FETCH_ALL) && !databaseConfigConnect(srv_cfg)) {
            // There are no CB databases so we're done
            return;
        }

        LOG_INFO(dctl_logger, DCTL_CONFIG_FETCH);

        // For now we find data based on first backend that has it.
        db::BackendSelector backend_selector(db::BackendSelector::Type::UNSPEC);

        // Use the server_tag if set, otherwise use ALL.
        std::string server_tag = srv_cfg->getServerTag();
        db::ServerSelector server_selector =
            (server_tag.empty()? db::ServerSelector::ALL() : db::ServerSelector::ONE(server_tag));

        // This collection will hold the audit entries since the last update if
        // we're running this method to fetch the configuration updates.
        db::AuditEntryCollection audit_entries;

        // If we're fetching updates we need to retrieve audit entries to see
        // which objects have to be updated. If we're performing full reconfiguration
        // we also need audit entries to set the last_audit_revision_time_ to the
        // time of the most recent audit entry.

        /// @todo We need a separate API call for the latter case to only
        /// fetch the last audit entry rather than all of them.

        // Save the timestamp indicating last audit revision time.
        auto lb_modification_time = last_audit_revision_time_;
        // Save the identifier indicating last audit revision id.
        auto lb_modification_id = last_audit_revision_id_;

        audit_entries = getMgr().getPool()->getRecentAuditEntries(backend_selector,
                                                                  server_selector,
                                                                  lb_modification_time,
                                                                  lb_modification_id);
        // Store the last audit revision time. It should be set to the most recent
        // audit entry fetched. If returned audit is empty we don't update.
        updateLastAuditRevisionTimeId(audit_entries);

        // If this is full reconfiguration we don't need the audit entries anymore.
        // Let's remove them and proceed as if they don't exist.
        if (fetch_mode == FetchMode::FETCH_ALL) {
            audit_entries.clear();
        }

        // If we fetch the entire config or we're updating the config and there are
        // audit entries indicating that there are some pending updates, let's
        // execute the server specific function that fetches and merges the data
        // into the given configuration.
        if ((fetch_mode == FetchMode::FETCH_ALL) || !audit_entries.empty()) {
            try {
                databaseConfigApply(backend_selector, server_selector,
                                    lb_modification_time, audit_entries);
            } catch (...) {
                // Revert last audit revision time and id so as we can retry
                // from the last successful attempt.
                /// @todo Consider reverting to the initial value to reload
                /// the entire configuration if the update failed.
                last_audit_revision_time_ = lb_modification_time;
                last_audit_revision_id_ = lb_modification_id;
                throw;
            }
        }
    }

protected:

    /// @brief Returns audit entries for new or updated configuration
    /// elements of specific type to be fetched from the database.
    ///
    /// This is convenience method invoked from the implementations of the
    /// @c databaseConfigApply function. This method is invoked when the
    /// server should fetch the updates to the existing configuration.
    /// The collection of audit entries contains audit entries indicating
    /// the updates to be fetched. This method returns audit entries for
    /// updated configuration elements of the specific type the
    /// @c databaseConfigApply should fetch. The returned collection od
    /// audit entries contains CREATE or UPDATE entries of the specific type.
    ///
    /// @param audit_entries collection od audit entries to filter.
    /// @param object_type object type to filter with.
    /// @return audit entries collection with CREATE or UPDATE entries
    /// of the specific type be fetched from the database.
    db::AuditEntryCollection
    fetchConfigElement(const db::AuditEntryCollection& audit_entries,
                       const std::string& object_type) const {
        db::AuditEntryCollection result;
        const auto& index = audit_entries.get<db::AuditEntryObjectTypeTag>();
        auto range = index.equal_range(object_type);
        for (auto it = range.first; it != range.second; ++it) {
            if ((*it)->getModificationType() != db::AuditEntry::ModificationType::DELETE) {
                result.insert(*it);
            }
        }

        return (result);
    }

    /// @brief Server specific method to fetch and apply back end
    /// configuration into the local configuration.
    ///
    /// This pure virtual method must be implemented in the derived classes
    /// to provide server specific implementation of fetching and applying
    /// the configuration. The implementations should perform the following
    /// sequence of operations:
    /// - Check if any audit entries exist. If none exist, assume that this
    ///   is the case of full server (re)configuration, otherwise assume
    ///   that configuration update is being performed.
    /// - Select audit entries which indicate deletion of any configuration
    ///   elements. For each such audit entry delete the given object from
    ///   the local configuration.
    /// - If the server is performing full reconfiguration, fetch the entire
    ///   configuration for the server. If the server is merely updating
    ///   the server configuration, fetch only those objects for which
    ///   (create/update) audit entries exist.
    /// - Merge the fetched configuration into the local server's
    ///   configuration.
    ///
    /// @param backend_selector Backend selector.
    /// @param server_selector Server selector.
    /// @param lb_modification_time Lower bound modification time for the
    /// configuration elements to be fetched.
    /// @param audit_entries Audit entries fetched from the database since
    /// the last configuration update. This collection is empty if there
    /// were no updates.
    virtual void databaseConfigApply(const db::BackendSelector& backend_selector,
                                     const db::ServerSelector& server_selector,
                                     const boost::posix_time::ptime& lb_modification_time,
                                     const db::AuditEntryCollection& audit_entries) = 0;

    /// @brief Returns the instance of the Config Backend Manager used by
    /// this object.
    ///
    /// @return Reference to the CB Manager instance.
    ConfigBackendMgrType& getMgr() const {
        return (ConfigBackendMgrType::instance());
    }

    /// @brief Convenience method returning initial timestamp to set the
    /// @c last_audit_revision_time_ to.
    ///
    /// @return Returns 2000-Jan-01 00:00:00 in local time.
    static boost::posix_time::ptime getInitialAuditRevisionTime() {
        static boost::posix_time::ptime
            initial_time(boost::gregorian::date(2000, boost::gregorian::Jan, 1));
        return (initial_time);
    }

    /// @brief Updates timestamp of the most recent audit entry fetched from
    /// the database.
    ///
    /// If the collection of audit entries is empty, this method simply
    /// returns without updating the timestamp.
    ///
    /// @param audit_entries reference to the collection of the fetched audit entries.
    void updateLastAuditRevisionTimeId(const db::AuditEntryCollection& audit_entries) {
        // Do nothing if there are no audit entries. It is the case if
        // there were no updates to the configuration.
        if (audit_entries.empty()) {
            return;
        }

        // Get the audit entries sorted by modification time and id,
	// and pick the latest entry.
        const auto& index = audit_entries.get<db::AuditEntryModificationTimeIdTag>();
        last_audit_revision_time_ = (*index.rbegin())->getModificationTime();
        last_audit_revision_id_ = (*index.rbegin())->getRevisionId();
    }

    /// @brief Stores the most recent audit revision timestamp.
    boost::posix_time::ptime last_audit_revision_time_;

    /// @brief Stores the most recent audit revision identifier.
    ///
    /// The identifier is used when two (or more) audit entries have
    /// the same timestamp. It is not used by itself because timestamps
    /// are more user friendly. Unfortunately old versions of MySQL do not
    /// support millisecond timestamps.
    uint64_t last_audit_revision_id_;
};

/// @brief Checks if an object is in a collection od audit entries.
///
/// @param audit_entries collection od audit entries to search for.
/// @param object_id object identifier.
inline bool
hasObjectId(const db::AuditEntryCollection& audit_entries,
            const uint64_t& object_id) {
    const auto& object_id_idx = audit_entries.get<db::AuditEntryObjectIdTag>();
    return (object_id_idx.count(object_id) > 0);
}

} // end of namespace isc::process
} // end of namespace isc

#endif /* CB_CTL_BASE_H */