]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#103,!277] Implemented CBControlBase class.
authorMarcin Siodelski <marcin@isc.org>
Mon, 18 Mar 2019 09:58:52 +0000 (10:58 +0100)
committerMarcin Siodelski <marcin@isc.org>
Tue, 26 Mar 2019 07:08:56 +0000 (03:08 -0400)
26 files changed:
src/bin/dhcp4/tests/config_parser_unittest.cc
src/bin/dhcp6/tests/config_parser_unittest.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.cc
src/hooks/dhcp/mysql_cb/mysql_cb_dhcp4.h
src/hooks/dhcp/mysql_cb/tests/mysql_cb_dhcp4_unittest.cc
src/lib/dhcpsrv/config_backend_dhcp4.h
src/lib/dhcpsrv/config_backend_dhcp6.h
src/lib/dhcpsrv/config_backend_pool_dhcp4.cc
src/lib/dhcpsrv/config_backend_pool_dhcp4.h
src/lib/dhcpsrv/config_backend_pool_dhcp6.cc
src/lib/dhcpsrv/config_backend_pool_dhcp6.h
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/srv_config.h
src/lib/dhcpsrv/tests/srv_config_unittest.cc
src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc
src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h
src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc
src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h
src/lib/process/Makefile.am
src/lib/process/cb_ctl_base.h [new file with mode: 0644]
src/lib/process/config_base.cc
src/lib/process/config_base.h
src/lib/process/process_messages.mes
src/lib/process/tests/Makefile.am
src/lib/process/tests/cb_ctl_base_unittests.cc [new file with mode: 0644]
src/lib/process/tests/config_base_unittests.cc

index 95d238c47517aedc50d033e8f610f1e0409eaf47..b847f7879287c93b8e7f9187ef8afc3af728236f 100644 (file)
@@ -6467,7 +6467,7 @@ TEST_F(Dhcp4ParserTest, serverTag) {
 
     // Configuration with the tag should have the tag value.
     configure(config_tag, CONTROL_RESULT_SUCCESS, "");
-    EXPECT_EQ("boo", CfgMgr::instance().getStagingCfg()->getServerTag());
+    EXPECT_EQ("boo", CfgMgr::instance().getStagingCfg()->getServerTag().get());
 
     // Make sure a invalid server-tag fails to parse.
     ASSERT_THROW(parseDHCP4(bad_tag), std::exception);
index a32a1e97d698ed107f392b694ec1e06860d064bb..733ad794be63515244b409645d512915853f16c7 100644 (file)
@@ -7043,7 +7043,7 @@ TEST_F(Dhcp6ParserTest, serverTag) {
 
     // Configuration with the tag should have the tag value.
     configure(config_tag, CONTROL_RESULT_SUCCESS, "");
-    EXPECT_EQ("boo", CfgMgr::instance().getStagingCfg()->getServerTag());
+    EXPECT_EQ("boo", CfgMgr::instance().getStagingCfg()->getServerTag().get());
 
     // Make sure a invalid server-tag fails to parse.
     ASSERT_THROW(parseDHCP6(bad_tag), std::exception);
index 88966bcb1214c1ef20a8ebd5e66d54a98c71ca0c..fa2c7f7882caaa2c65107e8d348535c5b2eef2de 100644 (file)
@@ -2497,8 +2497,8 @@ getModifiedGlobalParameters4(const db::ServerSelector& server_selector,
 
 AuditEntryCollection
 MySqlConfigBackendDHCPv4::
-getRecentAuditEntries4(const db::ServerSelector& server_selector,
-                       const boost::posix_time::ptime& modification_time) const {
+getRecentAuditEntries(const db::ServerSelector& server_selector,
+                      const boost::posix_time::ptime& modification_time) const {
     AuditEntryCollection audit_entries;
     impl_->getRecentAuditEntries(MySqlConfigBackendDHCPv4Impl::GET_AUDIT_ENTRIES4_TIME,
                                  server_selector, modification_time, audit_entries);
index 3e1ffc99ad2df2ea068867d0055e0c4d600fb005..6797ca0d262af80c7f892cc207c8c9aa0b7aae35 100644 (file)
@@ -192,8 +192,8 @@ public:
     /// result set, i.e. entries later than specified time are returned.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
-    getRecentAuditEntries4(const db::ServerSelector& server_selector,
-                           const boost::posix_time::ptime& modification_time) const;
+    getRecentAuditEntries(const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const;
 
     /// @brief Creates or updates a subnet.
     ///
index f7ca3cca3b2fac777a78a250867886376bdb7204..8cb1541e1c2c35ad518278ad82782f72b50c68b0 100644 (file)
@@ -360,8 +360,8 @@ public:
                            const std::string& exp_log_message,
                            const size_t new_entries_num = 1) {
         auto audit_entries_size_save = audit_entries_.size();
-        audit_entries_ = cbptr_->getRecentAuditEntries4(ServerSelector::ALL(),
-                                                        timestamps_["two days ago"]);
+        audit_entries_ = cbptr_->getRecentAuditEntries(ServerSelector::ALL(),
+                                                       timestamps_["two days ago"]);
         ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_.size())
             << logExistingAuditEntries();
 
index 1a95a024474de74457cc51a095ee93bcad04a624..90c0505e838c3846411d4f63ad991b7c25bec954 100644 (file)
@@ -194,8 +194,8 @@ public:
     /// result set, i.e. entries later than specified time are returned.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
-    getRecentAuditEntries4(const db::ServerSelector& server_selector,
-                           const boost::posix_time::ptime& modification_time) const = 0;
+    getRecentAuditEntries(const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const = 0;
 
     /// @brief Creates or updates a subnet.
     ///
index 6f7b94f74c50e6078e83fbd6c36c13f49d5d2b95..6de59a4ec883662599fde0c2f9c6b834b6350e99 100644 (file)
@@ -194,8 +194,8 @@ public:
     /// result set, i.e. entries later than specified time are returned.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
-    getRecentAuditEntries6(const db::ServerSelector& server_selector,
-                           const boost::posix_time::ptime& modification_time) const = 0;
+    getRecentAuditEntries(const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const = 0;
 
     /// @brief Creates or updates a subnet.
     ///
index f7fea147090cba2b1cac9b50b55c0509ef9fdc0d..018bdf53b49a6947f6b5de4a70c814cd4f7be854 100644 (file)
@@ -202,12 +202,12 @@ getModifiedGlobalParameters4(const db::BackendSelector& backend_selector,
 
 AuditEntryCollection
 ConfigBackendPoolDHCPv4::
-getRecentAuditEntries4(const db::BackendSelector& backend_selector,
-                       const db::ServerSelector& server_selector,
-                       const boost::posix_time::ptime& modification_time) const {
+getRecentAuditEntries(const db::BackendSelector& backend_selector,
+                      const db::ServerSelector& server_selector,
+                      const boost::posix_time::ptime& modification_time) const {
     AuditEntryCollection audit_entries;
     getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
-        (&ConfigBackendDHCPv4::getRecentAuditEntries4, backend_selector,
+        (&ConfigBackendDHCPv4::getRecentAuditEntries, backend_selector,
          server_selector, audit_entries, modification_time);
     return (audit_entries);
 }
index 15d9b9211c130adbf94d09da6946d01e4eea9da4..302ffe7960ed37f46590e0739587acfb6260690a 100644 (file)
@@ -227,9 +227,9 @@ public:
     /// result set, i.e. entries later than specified time are returned.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
-    getRecentAuditEntries4(const db::BackendSelector& backend_selector,
-                           const db::ServerSelector& server_selector,
-                           const boost::posix_time::ptime& modification_time) const;
+    getRecentAuditEntries(const db::BackendSelector& backend_selector,
+                          const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const;
 
     /// @brief Creates or updates a subnet.
     ///
index d4a0071b2a81cd9fb13fd31d2b5c5f4fa4306517..66d8f01c627c6f465faa3e57b01b634b6567f41c 100644 (file)
@@ -202,12 +202,12 @@ getModifiedGlobalParameters6(const db::BackendSelector& backend_selector,
 
 AuditEntryCollection
 ConfigBackendPoolDHCPv6::
-getRecentAuditEntries6(const db::BackendSelector& backend_selector,
-                       const db::ServerSelector& server_selector,
-                       const boost::posix_time::ptime& modification_time) const {
+getRecentAuditEntries(const db::BackendSelector& backend_selector,
+                      const db::ServerSelector& server_selector,
+                      const boost::posix_time::ptime& modification_time) const {
     AuditEntryCollection audit_entries;
     getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
-        (&ConfigBackendDHCPv6::getRecentAuditEntries6, backend_selector,
+        (&ConfigBackendDHCPv6::getRecentAuditEntries, backend_selector,
          server_selector, audit_entries, modification_time);
     return (audit_entries);
 }
index 6031450895491a625e24feac45b20b3a4e5df980..0bb0c6672efb64d5ec21242c18a478c1f99e068a 100644 (file)
@@ -227,9 +227,9 @@ public:
     /// result set, i.e. entries later than specified time are returned.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
-    getRecentAuditEntries6(const db::BackendSelector& backend_selector,
-                           const db::ServerSelector& server_selector,
-                           const boost::posix_time::ptime& modification_time) const;
+    getRecentAuditEntries(const db::BackendSelector& backend_selector,
+                          const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const;
 
     /// @brief Creates or updates a subnet.
     ///
index c5dafca98a528031634751a20e5a4294ae9ca632..9ac59f47bb80b150b79a3f810cfc2c81a59600c5 100644 (file)
@@ -40,8 +40,7 @@ SrvConfig::SrvConfig()
       decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0),
       d2_client_config_(new D2ClientConfig()),
       configured_globals_(Element::createMap()),
-      cfg_consist_(new CfgConsistency()),
-      server_tag_("") {
+      cfg_consist_(new CfgConsistency()) {
 }
 
 SrvConfig::SrvConfig(const uint32_t sequence)
@@ -59,8 +58,7 @@ SrvConfig::SrvConfig(const uint32_t sequence)
       decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0),
       d2_client_config_(new D2ClientConfig()),
       configured_globals_(Element::createMap()),
-      cfg_consist_(new CfgConsistency()),
-      server_tag_("") {
+      cfg_consist_(new CfgConsistency()) {
 }
 
 std::string
index 7dcb361c78bb8434ab122989661ee300ecf06c18..67414d9763a315372ec9424c0da74281c0728ec6 100644 (file)
@@ -615,21 +615,6 @@ public:
         configured_globals_->set(name, value);
     }
 
-    /// @brief Sets the server's logical name
-    ///
-    /// @param server_tag a unique string name which identifies this server
-    /// from any other configured servers
-    void setServerTag(const std::string& server_tag) {
-        server_tag_ = server_tag;
-    }
-
-    /// @brief Returns the server's logical name
-    ///
-    /// @return string containing the server's tag
-    std::string getServerTag() const {
-        return (server_tag_);
-    }
-
     /// @brief Unparse a configuration object
     ///
     /// @return a pointer to unparsed configuration
@@ -791,9 +776,6 @@ private:
 
     /// @brief Pointer to the configuration consistency settings
     CfgConsistencyPtr cfg_consist_;
-
-    /// @brief Logical name of the server
-    std::string server_tag_;
 };
 
 /// @name Pointers to the @c SrvConfig object.
index f46a07a83b619acf2c31cd5bd354b0e279721961..89a3dcdd2341724ef5cdc111c5306b84fc070877 100644 (file)
@@ -295,22 +295,6 @@ TEST_F(SrvConfigTest, echoClientId) {
     EXPECT_TRUE(conf1.getEchoClientId());
 }
 
-// This test verifies that server-tag may be configured.
-TEST_F(SrvConfigTest, serverTag) {
-    SrvConfig conf;
-
-    // Check that the default is an empty string.
-    EXPECT_TRUE(conf.getServerTag().empty());
-
-    // Check that it can be modified.
-    conf.setServerTag("boo");
-    EXPECT_EQ("boo", conf.getServerTag());
-
-    // Check the other constructor has the same default
-    SrvConfig conf1(1);
-    EXPECT_EQ("boo", conf.getServerTag());
-}
-
 // This test checks if entire configuration can be copied and that the sequence
 // number is not affected.
 TEST_F(SrvConfigTest, copy) {
@@ -1039,7 +1023,7 @@ TEST_F(SrvConfigTest, mergeGlobals4) {
     EXPECT_EQ(999, cfg_to.getDhcp4o6Port());
 
     //  server-tag port should be the "from" configured value.
-    EXPECT_EQ("use_this_server", cfg_to.getServerTag());
+    EXPECT_EQ("use_this_server", cfg_to.getServerTag().get());
 
     // Next we check the explicitly "configured" globals. 
     // The list should be all of the "to" + "from", with the
index 1d959d235cbb1f8416d00d77a90b09deb6332b33..59b576f3990f4dd97c7ea697512e254c3eed760f 100644 (file)
@@ -212,8 +212,8 @@ TestConfigBackendDHCPv4::getModifiedGlobalParameters4(const db::ServerSelector&
 }
 
 AuditEntryCollection
-TestConfigBackendDHCPv4::getRecentAuditEntries4(const db::ServerSelector&,
-                                                const boost::posix_time::ptime&) const {
+TestConfigBackendDHCPv4::getRecentAuditEntries(const db::ServerSelector&,
+                                               const boost::posix_time::ptime&) const {
     return (AuditEntryCollection());
 }
 
index 28145a54c90c77cbb105f7dd8bcb671b31dbed8a..2754c1bc83ec15e79a15d3465675df5929042161 100644 (file)
@@ -215,8 +215,8 @@ public:
     /// result set, i.e. entries later than specified time are returned.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
-    getRecentAuditEntries4(const db::ServerSelector& server_selector,
-                           const boost::posix_time::ptime& modification_time) const;
+    getRecentAuditEntries(const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const;
 
     /// @brief Creates or updates a subnet.
     ///
index e5b61da1cb05d8008ed8f3c2ce0c23523fd19b65..16ed733ee8180d9f6ac66e6406dac571e65e4b75 100644 (file)
@@ -211,8 +211,8 @@ TestConfigBackendDHCPv6::getModifiedGlobalParameters6(const db::ServerSelector&
 }
 
 AuditEntryCollection
-TestConfigBackendDHCPv6::getRecentAuditEntries6(const db::ServerSelector&,
-                                                const boost::posix_time::ptime&) const {
+TestConfigBackendDHCPv6::getRecentAuditEntries(const db::ServerSelector&,
+                                               const boost::posix_time::ptime&) const {
     return (AuditEntryCollection());
 }
 
index b89ee0c96f59e2c31e9794ca943d8457258899a6..e18bba3b6ad09d379125f08ce0d3fdb60f7ef159 100644 (file)
@@ -215,8 +215,8 @@ public:
     /// result set, i.e. entries later than specified time are returned.
     /// @return Collection of audit entries.
     virtual db::AuditEntryCollection
-    getRecentAuditEntries6(const db::ServerSelector& server_selector,
-                           const boost::posix_time::ptime& modification_time) const;
+    getRecentAuditEntries(const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const;
 
     /// @brief Creates or updates a subnet.
     ///
index 9f38ff9945eaa887b8cf5a65a45c63d313f3c871..77758f81f3fd0b14778a4bcc51de4618e6b67086 100644 (file)
@@ -16,6 +16,7 @@ DISTCLEANFILES = spec_config.h.pre
 
 lib_LTLIBRARIES = libkea-process.la
 libkea_process_la_SOURCES  = config_base.cc config_base.h
+libkea_process_la_SOURCES += config_ctl_base.h
 libkea_process_la_SOURCES += config_ctl_info.cc config_ctl_info.h
 libkea_process_la_SOURCES += config_ctl_parser.cc config_ctl_parser.h
 libkea_process_la_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
@@ -83,6 +84,7 @@ endif
 libkea_process_includedir = $(pkgincludedir)/process
 libkea_process_include_HEADERS = \
        config_base.h \
+       config_ctl_base.h \
        config_ctl_info.h \
        config_ctl_parser.h \
        daemon.h \
diff --git a/src/lib/process/cb_ctl_base.h b/src/lib/process/cb_ctl_base.h
new file mode 100644 (file)
index 0000000..229b4e4
--- /dev/null
@@ -0,0 +1,282 @@
+// Copyright (C) 2019 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 read from the configuration file. Usually, the configuration
+/// fetched from the file will be disjoint 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 uses "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 faciliate 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 of the most recent audit entry fetched from the
+///   database, so as next time it can only fetch 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 Constructor.
+    ///
+    /// Sets the time of the last fetched audit entry to Jan 1st, 1970.
+    CBControlBase()
+        : last_audit_entry_time_(getInitialAuditEntryTime()) {
+    }
+
+    /// @brief Virtual destructor.
+    ///
+    /// It is always needed when there are virtual methods.
+    virtual ~CBControlBase() {
+    }
+
+    /// @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_updates_only boolean value indicating if the method is
+    /// called upon the server start up (false) or it is called to fetch
+    /// configuration updates (true).
+    void databaseConfigFetch(const ConfigPtr& srv_cfg,
+                             const bool fetch_updates_only = false) {
+        // If the server starts up we need to connect to the database(s).
+        // If there are no databases available simply do nothing.
+        if (!fetch_updates_only && !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_entry_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.
+
+        audit_entries = getMgr().getPool()->getRecentAuditEntries(backend_selector,
+                                                                  server_selector,
+                                                                  last_audit_entry_time_);
+        // Store the last audit entry time. It should be set to the most recent
+        // audit entry fetched. If returned audit is empty we don't update.
+        updateLastAuditEntryTime(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_updates_only) {
+            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_updates_only || !audit_entries.empty()) {
+            databaseConfigApply(srv_cfg, backend_selector, server_selector, audit_entries);
+        }
+    }
+
+protected:
+
+    /// @brief Server specific method to apply fetched 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.
+    ///
+    /// @pararm srv_cfg Pointer to the local server configuration.
+    /// @param backend_selector Backend selector.
+    /// @param server_selector Server selector.
+    /// @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 ConfigPtr& srv_cfg,
+                                     const db::BackendSelector& backend_selector,
+                                     const db::ServerSelector& server_selector,
+                                     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_entry_time_ to.
+    ///
+    /// @return Returns 1970-Jan-01 00:00:00 in local time.
+    static boost::posix_time::ptime getInitialAuditEntryTime() {
+        static boost::posix_time::ptime
+            initial_time(boost::gregorian::date(1970, 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 Reference to the collection of the fetched audit entries.
+    void updateLastAuditEntryTime(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 pick the
+        // latest entry.
+        const auto& index = audit_entries.get<db::AuditEntryModificationTimeTag>();
+        last_audit_entry_time_ = ((*index.rbegin())->getModificationTime());
+    }
+
+    /// @brief Stores the most recent audit entry.
+    boost::posix_time::ptime last_audit_entry_time_;
+};
+
+} // end of namespace isc::process
+} // end of namespace isc
+
+#endif /* CB_CTL_BASE_H */
index 47e4214414e885c614aaf6e5e5fb96c8ab4ab6b8..6e9ffd6e82f42bd095f419d93788ba330749e23e 100644 (file)
@@ -80,6 +80,9 @@ ConfigBase::copy(ConfigBase& other) const {
     } else {
         other.config_ctl_info_.reset();
     }
+
+    // Clone server tag.
+    other.server_tag_ = server_tag_;
 }
 
 void
@@ -97,6 +100,11 @@ ConfigBase::merge(ConfigBase& other) {
             config_ctl_info_ = other.config_ctl_info_;
         }
     }
+
+    // Merge server tag.
+    if (!other.server_tag_.unspecified()) {
+        server_tag_ = other.server_tag_.get();
+    }
 }
 
 ElementPtr
@@ -117,6 +125,11 @@ ConfigBase::toElement() const {
         result->set("Logging", logging);
     }
 
+    // server-tag
+    if (!server_tag_.unspecified()) {
+        result->set("server-tag", Element::create(server_tag_.get()));
+    }
+
     // We do NOT output ConfigControlInfo here, as it is not a
     // top level element, but rather belongs within the process
     // element.
index 966a863b2b0d65ffd26f64ca043228d5c2e6a044..e1965b317c6bfe2c5deb7f2b0ec9c1155dd12e5a 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2019 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
@@ -11,6 +11,7 @@
 #include <cc/user_context.h>
 #include <process/config_ctl_info.h>
 #include <process/logging_info.h>
+#include <util/optional.h>
 
 namespace isc {
 namespace process {
@@ -118,6 +119,21 @@ public:
         config_ctl_info_ = config_ctl_info;
     }
 
+    /// @brief Sets the server's logical name
+    ///
+    /// @param server_tag a unique string name which identifies this server
+    /// from any other configured servers
+    void setServerTag(const util::Optional<std::string>& server_tag) {
+        server_tag_ = server_tag;
+    }
+
+    /// @brief Returns the server's logical name
+    ///
+    /// @return string containing the server's tag
+    util::Optional<std::string> getServerTag() const {
+        return (server_tag_);
+    }
+
 protected:
     /// @brief Copies the current configuration to a new configuration.
     ///
@@ -135,6 +151,9 @@ private:
 
     /// @brief Configuration control information.
     process::ConfigControlInfoPtr config_ctl_info_;
+
+    /// @brief Logical name of the server
+    util::Optional<std::string> server_tag_;
 };
 
 /// @brief Non-const pointer to the @c SrvConfig.
index 637d1f5dcd54bc8dd2b45e1e6f0427ae1648d316..13593a41f2d940f615dddf9fc0b4d8800ab96c81 100644 (file)
@@ -49,6 +49,10 @@ new configuration. It is output during server startup, and when an updated
 configuration is committed by the administrator.  Additional information
 may be provided.
 
+% DCTL_CONFIG_FETCH Fetching configuration data from config backends.
+This is an informational message emitted when the Kea server about to begin
+retrieving configuration data from one or more configuration backends.
+
 % DCTL_CONFIG_FILE_LOAD_FAIL %1 reason: %2
 This fatal error message indicates that the application attempted to load its
 initial configuration from file and has failed. The service will exit.
@@ -83,6 +87,11 @@ application and will exit.
 A warning message is issued when an attempt is made to shut down the
 application when it is not running.
 
+% DCTL_OPEN_CONFIG_DB Opening configuration database: %1
+This message is printed when the Kea server is attempting to open a
+configuration database.  The database access string with password redacted
+is logged.
+
 % DCTL_PARSER_FAIL : %1
 On receipt of a new configuration, the server failed to create a parser to
 decode the contents of the named configuration element, or the creation
index 2fe844b5d40c7636e37dc347b1871b8ff6c6122d..253a9c378c620031d8713eb931a667517696aed4 100644 (file)
@@ -22,6 +22,7 @@ if HAVE_GTEST
 TESTS += libprocess_unittests
 
 libprocess_unittests_SOURCES  = d_cfg_mgr_unittests.cc
+libprocess_unittests_SOURCES += cb_ctl_base_unittests.cc
 libprocess_unittests_SOURCES += config_base_unittests.cc
 libprocess_unittests_SOURCES += config_ctl_info_unittests.cc
 libprocess_unittests_SOURCES += config_ctl_parser_unittests.cc
diff --git a/src/lib/process/tests/cb_ctl_base_unittests.cc b/src/lib/process/tests/cb_ctl_base_unittests.cc
new file mode 100644 (file)
index 0000000..9abc1a7
--- /dev/null
@@ -0,0 +1,532 @@
+// Copyright (C) 2019 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/.
+
+#include <config.h>
+
+#include <config_backend/base_config_backend_mgr.h>
+#include <config_backend/base_config_backend_pool.h>
+#include <process/cb_ctl_base.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <map>
+#include <string>
+
+using namespace isc;
+using namespace isc::cb;
+using namespace isc::db;
+using namespace isc::process;
+
+namespace {
+
+/// @brief Implementation of the config backend for testing the
+/// @c CBControlBase template class.
+///
+/// This simple class allows for adding, retrieving and clearing audit
+/// entries. The @c CBControlBase unit tests use it to control the
+/// behavior of the @c CBControlBase class under test.
+class CBControlBackend : BaseConfigBackend {
+public:
+
+    /// @brief Constructor.
+    CBControlBackend(const db::DatabaseConnection::ParameterMap&) {
+    }
+
+    /// @brief Retrieves the audit entries later than specified time.
+    ///
+    /// @param server_selector Server selector.
+    /// @param modification_time The lower bound time for which audit
+    /// entries should be returned.
+    ///
+    /// @return Collection of audit entries later than specified time.
+    virtual db::AuditEntryCollection
+    getRecentAuditEntries(const db::ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const {
+        db::AuditEntryCollection filtered_entries;
+
+        // Use the index which orders the audit entries by timestamps.
+        const auto& index = audit_entries_.get<AuditEntryModificationTimeTag>();
+
+        // Locate the first audit entry after the last one having the
+        // specified modification time.
+        auto first_entry = index.upper_bound(modification_time);
+
+        // If there are any entries found return them.
+        if (first_entry != index.end()) {
+            filtered_entries.insert(first_entry, index.end());
+        }
+
+        return (filtered_entries);
+    }
+
+    /// @brief Add audit entry to the backend.
+    ///
+    /// @param object_type Object type to be stored in the audit entry.
+    /// @param object_id Object id to be stored in the audit entry.
+    /// @param modification_time Audit entry modification time to be set.
+    void addAuditEntry(const ServerSelector&,
+                       const std::string& object_type,
+                       const uint64_t object_id,
+                       const boost::posix_time::ptime& modification_time) {
+        // Create new audit entry from the specified parameters.
+        AuditEntryPtr audit_entry(new AuditEntry(object_type,
+                                                 object_id,
+                                                 AuditEntry::ModificationType::CREATE,
+                                                 modification_time,
+                                                 "added audit entry"));
+
+        // The audit entries are held in the static variable so as they
+        // don't disappear when we diconnect from the backend. The
+        // audit entries are explicitly cleared during the unit tests
+        // setup.
+        audit_entries_.insert(audit_entry);
+    }
+
+    /// @brief Returns backend type in the textual format.
+    ///
+    /// @return Name of the storage for configurations, e.g. "mysql",
+    /// "pgsql" and so forth.
+    virtual std::string getType() const {
+        return ("memfile");
+    }
+
+    /// @brief Returns backend host
+    ///
+    /// This is used by the @c BaseConfigBackendPool to select backend
+    /// when @c BackendSelector is specified.
+    ///
+    /// @return host on which the database is located.
+    virtual std::string getHost() const {
+        return ("");
+    }
+
+    /// @brief Returns backend port number.
+    ///
+    /// This is used by the @c BaseConfigBackendPool to select backend
+    /// when @c BackendSelector is specified.
+    ///
+    /// @return Port number on which database service is available.
+    virtual uint16_t getPort() const {
+        return (0);
+    }
+
+    /// @brief Removes audit entries.
+    static void clearAuditEntries() {
+        audit_entries_.clear();
+    }
+
+private:
+
+    /// @brief Static collection of audit entries.
+    ///
+    /// Thanks to storing them in the static member they are preserved
+    /// when the unit tests "disconnect" from the backend.
+    static AuditEntryCollection audit_entries_;
+};
+
+/// @brief Pointer to the @c CBControlBackend object.
+typedef boost::shared_ptr<CBControlBackend> CBControlBackendPtr;
+
+AuditEntryCollection CBControlBackend::audit_entries_;
+
+/// @brief Implementation of the backends pool used in the
+/// @c CBControlBase template class unit tests.
+class CBControlBackendPool : public BaseConfigBackendPool<CBControlBackend> {
+public:
+
+    /// @brief Add audit entry to the backend.
+    ///
+    /// @param backend_selector Backend selector.
+    /// @param server_selector Server selector.
+    /// @param object_type Object type to be stored in the audit entry.
+    /// @param object_id Object id to be stored in the audit entry.
+    /// @param modification_time Audit entry modification time to be set.
+    void addAuditEntry(const BackendSelector& backend_selector,
+                       const ServerSelector& server_selector,
+                       const std::string& object_type,
+                       const uint64_t object_id,
+                       const boost::posix_time::ptime& modification_time) {
+        createUpdateDeleteProperty<void, const std::string&, const uint64_t,
+                                   const boost::posix_time::ptime&>
+            (&CBControlBackend::addAuditEntry, backend_selector, server_selector,
+             object_type, object_id, modification_time);
+    }
+
+    /// @brief Retrieves the audit entries later than specified time.
+    ///
+    /// @param backend_selector Backend selector.
+    /// @param server_selector Server selector.
+    /// @param modification_time The lower bound time for which audit
+    /// entries should be returned.
+    ///
+    /// @return Collection of audit entries later than specified time.
+    virtual db::AuditEntryCollection
+    getRecentAuditEntries(const BackendSelector& backend_selector,
+                          const ServerSelector& server_selector,
+                          const boost::posix_time::ptime& modification_time) const {
+        AuditEntryCollection audit_entries;
+        getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
+            (&CBControlBackend::getRecentAuditEntries, backend_selector,
+             server_selector, audit_entries, modification_time);
+        return (audit_entries);
+    }
+};
+
+/// @brief Implementation of the config backends manager used
+/// in the @c CBControlBase template class unit tests.
+class CBControlBackendMgr : public BaseConfigBackendMgr<CBControlBackendPool> {
+public:
+
+    /// @brief Constructor.
+    CBControlBackendMgr()
+        : instance_id_(0) {
+    }
+
+    /// @brief Returns instance of the @c CBControlBackendMgr.
+    ///
+    /// @return Reference to the instance of the @c CBControlBackendMgr.
+    static CBControlBackendMgr& instance() {
+        static CBControlBackendMgr mgr;
+        return (mgr);
+    }
+
+    /// @brief Returns instance id.
+    ///
+    /// This value is used in tests which verify that the @c CBControlBase::getMgr
+    /// returns the right instance of the CB manager.
+    ///
+    /// @return Instance id.
+    uint32_t getInstanceId() const {
+        return (instance_id_);
+    }
+
+    /// @brief Sets new instance id.
+    ///
+    /// @param instance_id New instance id.
+    void setInstanceId(const uint32_t instance_id) {
+        instance_id_ = instance_id;
+    }
+
+    /// @brief Instance id.
+    uint32_t instance_id_;
+};
+
+/// @brief Implementation of the @c CBControlBase class used in
+/// the unit tests.
+///
+/// It makes some of the protected methods public. It also provides
+/// means to test the behavior of the @c CBControlBase template.
+class CBControl : public CBControlBase<CBControlBackendMgr> {
+public:
+
+    using CBControlBase<CBControlBackendMgr>::getMgr;
+    using CBControlBase<CBControlBackendMgr>::getInitialAuditEntryTime;
+
+    /// @brief Constructor.
+    CBControl()
+        : CBControlBase<CBControlBackendMgr>(),
+        merges_num_(0),
+        backend_selector_(BackendSelector::Type::MYSQL),
+        server_selector_(ServerSelector::UNASSIGNED()),
+        audit_entries_num_(-1) {
+    }
+
+    /// @brief Implementation of the method called to fetch and apply
+    /// configuration from the database into the local configuration.
+    ///
+    /// This stub implementation doesn't attempt to merge any configurations
+    /// but merely records the values of the parameters called.
+    ///
+    /// @param backend_selector Backend selector.
+    /// @param server_selector Server selector.
+    /// @param audit_entries Collection of audit entries.
+    virtual void databaseConfigApply(const ConfigPtr& srv_cfg,
+                                     const BackendSelector& backend_selector,
+                                     const ServerSelector& server_selector,
+                                     const AuditEntryCollection& audit_entries) {
+        ++merges_num_;
+        backend_selector_ = backend_selector;
+        server_selector_ = server_selector;
+        audit_entries_num_ = static_cast<int>(audit_entries.size());
+    }
+
+    /// @brief Returns the number of times the @c databaseConfigApply was called.
+    size_t getMergesNum() const {
+        return (merges_num_);
+    }
+
+    /// @brief Returns backend selector used as an argument in a call to
+    /// @c databaseConfigApply.
+    const BackendSelector& getBackendSelector() const {
+        return (backend_selector_);
+    }
+
+    /// @brief Returns server selector used as an argument in a call to
+    /// @c databaseConfigApply.
+    const ServerSelector& getServerSelector() const {
+        return (server_selector_);
+    }
+
+    /// @brief Returns the number of audit entries in the collection passed
+    /// to @c databaseConfigApply
+    int getAuditEntriesNum() const {
+        return (audit_entries_num_);
+    }
+
+    /// @brief Returns the recorded time of last audit entry.
+    boost::posix_time::ptime getLastAuditEntryTime() const {
+        return (last_audit_entry_time_);
+    }
+
+    /// @brief Overwrites the last audit entry time.
+    ///
+    /// @param last_audit_entry_time New time to be set.
+    void setLastAuditEntryTime(const boost::posix_time::ptime& last_audit_entry_time) {
+        last_audit_entry_time_ = last_audit_entry_time;
+    }
+
+private:
+
+    /// @brief Recorded number of calls to @c databaseConfigApply.
+    size_t merges_num_;
+
+    /// @brief Recorded backend selector value.
+    BackendSelector backend_selector_;
+
+    /// @brief Recorded server selector value.
+    ServerSelector server_selector_;
+
+    /// @brief Recorded number of audit entries.
+    int audit_entries_num_;
+};
+
+/// @brief Out of the blue instance id used in tests.
+constexpr uint32_t TEST_INSTANCE_ID = 123;
+
+/// @brief Test fixture class for @c CBControlBase template class.
+class CBControlBaseTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    CBControlBaseTest()
+        : cb_ctl_(), mgr_(CBControlBackendMgr::instance()),
+          timestamps_() {
+        mgr_.registerBackendFactory("db1",
+            [](const DatabaseConnection::ParameterMap& params)
+                -> CBControlBackendPtr {
+            return (CBControlBackendPtr(new CBControlBackend(params)));
+        });
+        mgr_.setInstanceId(TEST_INSTANCE_ID);
+        initTimestamps();
+        CBControlBackend::clearAuditEntries();
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Removes audit entries created in the test.
+    ~CBControlBaseTest() {
+        CBControlBackend::clearAuditEntries();
+    }
+
+    /// @brief Initialize posix time values used in tests.
+    void initTimestamps() {
+        // Current time minus 1 hour to make sure it is in the past.
+        timestamps_["today"] = boost::posix_time::second_clock::local_time()
+            - boost::posix_time::hours(1);
+        // Yesterday.
+        timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
+        // Two days ago.
+        timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48);
+        // Tomorrow.
+        timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
+    }
+
+    /// @brief Creates an instance of the configuration object.
+    ///
+    /// @param db1_access Database access string to be used to connect to
+    /// the test configuration backend. It doesn't connect if the string
+    /// is empty.
+    ConfigPtr makeConfigBase(const std::string& db1_access = "") const {
+        ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+
+        if (!db1_access.empty()) {
+            config_ctl_info->addConfigDatabase(db1_access);
+        }
+
+        ConfigPtr config_base(new ConfigBase());
+
+        config_base->setConfigControlInfo(config_ctl_info);
+        return (config_base);
+    }
+
+    /// @brief Instance of the @c CBControl used in tests.
+    CBControl cb_ctl_;
+
+    /// @brief Instance of the Config Backend Manager.
+    CBControlBackendMgr& mgr_;
+
+    /// @brief Holds timestamp values used in tests.
+    std::map<std::string, boost::posix_time::ptime> timestamps_;
+};
+
+// This test verifies that the correct instance of the Config
+// Backend Manager is returned.
+TEST_F(CBControlBaseTest, getMgr) {
+    auto mgr = cb_ctl_.getMgr();
+    EXPECT_EQ(TEST_INSTANCE_ID, mgr.getInstanceId());
+}
+
+// This test verifies that true is return when the server successfully
+// connects to the backend and false if there are no backends to connect
+// to.
+TEST_F(CBControlBaseTest, connect) {
+    EXPECT_TRUE(cb_ctl_.databaseConfigConnect(makeConfigBase("type=db1")));
+    EXPECT_FALSE(cb_ctl_.databaseConfigConnect(makeConfigBase()));
+}
+
+// This test verifies the scenario when the server fetches the entire
+// configuration from the database upon startup.
+TEST_F(CBControlBaseTest, fetchAll) {
+    auto config_base = makeConfigBase("type=db1");
+
+    // Add two audit entries to the database. The server should load
+    // the entire configuration from the database regardless of the
+    // existing audit entries. However, the last audit entry timestamp
+    // should be set to the most recent audit entry in the
+    // @c CBControlBase.
+    ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
+
+    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+                                              ServerSelector::ALL(),
+                                              "sql_table_2",
+                                              1234,
+                                              timestamps_["yesterday"]);
+
+    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+                                              ServerSelector::ALL(),
+                                              "sql_table_1",
+                                              3456,
+                                              timestamps_["today"]);
+
+    // Disconnect from the database in order to check that the
+    // databaseConfigFetch reconnects.
+    ASSERT_NO_THROW(cb_ctl_.databaseConfigDisconnect());
+
+    // Verify that various indicators are set to their initial values.
+    ASSERT_EQ(0, cb_ctl_.getMergesNum());
+    ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
+    ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
+    ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
+    EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+
+    // Connect to the database and fetch the configuration.
+    ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));
+
+    // There should be one invocation of the databaseConfigApply.
+    ASSERT_EQ(1, cb_ctl_.getMergesNum());
+    // Since this is full reconfiguration the audit entry collection
+    // passed to the databaseConfigApply should be empty.
+    EXPECT_EQ(0, cb_ctl_.getAuditEntriesNum());
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
+    EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
+    // Make sure that the internal timestamp is set to the most recent
+    // audit entry, so as the server will only later fetch config
+    // updates after this timestamp.
+    EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditEntryTime());
+}
+
+// This test verifies that the configuration can be fetched for a
+// specified server tag.
+TEST_F(CBControlBaseTest, fetchFromServer) {
+    auto config_base = makeConfigBase("type=db1");
+    // Set a server tag.
+    config_base->setServerTag("a-tag");
+
+    // Verify that various indicators are set to their initial values.
+    ASSERT_EQ(0, cb_ctl_.getMergesNum());
+    ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
+    ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
+    ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
+    EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+
+    ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));
+
+    ASSERT_EQ(1, cb_ctl_.getMergesNum());
+    EXPECT_EQ(0, cb_ctl_.getAuditEntriesNum());
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
+    // An explicit server selector should have been used this time.
+    ASSERT_EQ(ServerSelector::Type::SUBSET, cb_ctl_.getServerSelector().getType());
+    EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+
+    // Make sure that the server selector used in databaseConfigFetch is
+    // correct.
+    auto tags = cb_ctl_.getServerSelector().getTags();
+    ASSERT_EQ(1, tags.size());
+    EXPECT_EQ("a-tag", *tags.begin());
+}
+
+// This test verifies that incremental configuration changes can be
+// fetched.
+TEST_F(CBControlBaseTest, fetchUpdates) {
+    auto config_base = makeConfigBase("type=db1");
+
+    // Connect to the database and store an audit entry. Do not close
+    // the database connection to simulate the case when the server
+    // uses existing connection to fetch configuration updates.
+    ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
+    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+                                              ServerSelector::ALL(),
+                                              "sql_table_1",
+                                              3456,
+                                              timestamps_["today"]);
+
+    // Verify that various indicators are set to their initial values.
+    ASSERT_EQ(0, cb_ctl_.getMergesNum());
+    ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
+    ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
+    ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
+    EXPECT_EQ(cb_ctl_.getInitialAuditEntryTime(), cb_ctl_.getLastAuditEntryTime());
+
+    ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base, true));
+
+    // There should be one invocation to databaseConfigApply recorded.
+    ASSERT_EQ(1, cb_ctl_.getMergesNum());
+    // The number of audit entries passed to this function should be 1.
+    EXPECT_EQ(1, cb_ctl_.getAuditEntriesNum());
+    EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
+    EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
+    // The last audit entry time should be set to the latest audit entry.
+    EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditEntryTime());
+}
+
+// Check that the databaseConfigApply function is not called when there
+// are no more unprocessed audit entries.
+TEST_F(CBControlBaseTest, fetchNoUpdates) {
+    auto config_base = makeConfigBase("type=db1");
+
+    // Set last audit entry time to the timestamp of the audit
+    // entry we are going to add. That means that there will be
+    // no new audit entries to fetch.
+    cb_ctl_.setLastAuditEntryTime(timestamps_["yesterday"]);
+
+    ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
+
+    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+                                              ServerSelector::ALL(),
+                                              "sql_table_1",
+                                              3456,
+                                              timestamps_["yesterday"]);
+
+    ASSERT_EQ(0, cb_ctl_.getMergesNum());
+
+    ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base, true));
+
+    // The databaseConfigApply should not be called because there are
+    // no new audit entires to process.
+    ASSERT_EQ(0, cb_ctl_.getMergesNum());
+}
+
+}
index 2c0302cc7dce6e289392712c5941cee71fa79871..cec45826825f0a0c50815a6d966388907e27a264 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2019 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
@@ -8,11 +8,13 @@
 
 #include <exceptions/exceptions.h>
 #include <process/config_base.h>
+#include <util/optional.h>
 
 #include <gtest/gtest.h>
 
 using namespace isc;
 using namespace isc::process;
+using namespace isc::util;
 
 /// @brief Derived ConfigBase class
 /// We use this derivation to test the
@@ -123,3 +125,106 @@ TEST(ConfigBase, mergeConfigControl) {
     ASSERT_NO_THROW(base1.merge(base2));
     EXPECT_TRUE(base1.equals(base2));
 }
+
+// Verifies that server-tag may be configured.
+TEST(ConfigBase, serverTag) {
+    ConfigBaseImpl conf;
+
+    // Check that the default is an unspecified and empty string.
+    EXPECT_TRUE(conf.getServerTag().unspecified());
+    EXPECT_TRUE(conf.getServerTag().empty());
+
+    // Check that it can be modified.
+    conf.setServerTag("boo");
+    EXPECT_FALSE(conf.getServerTag().unspecified());
+    EXPECT_EQ("boo", conf.getServerTag().get());
+}
+
+// Verifies that server tag can be merged to another config.
+TEST(ConfigBase, mergeServerTag) {
+    ConfigBaseImpl base1;
+    ConfigBaseImpl base2;
+
+    // Initially the server tags in both config should be
+    // unspecified.
+    EXPECT_TRUE(base1.getServerTag().unspecified());
+    EXPECT_TRUE(base2.getServerTag().unspecified());
+
+    // Merging the config with unspecified server tag should
+    // not modify the target config.
+    ASSERT_NO_THROW(base1.merge(base2));
+    EXPECT_TRUE(base1.getServerTag().unspecified());
+    EXPECT_TRUE(base2.getServerTag().unspecified());
+
+    // Set server tag for base2 and merge it.
+    base2.setServerTag(std::string("base2"));
+    ASSERT_NO_THROW(base1.merge(base2));
+
+    // The server tag should be copied into the base1. Both
+    // should now be unspecified.
+    EXPECT_FALSE(base1.getServerTag().unspecified());
+    EXPECT_FALSE(base2.getServerTag().unspecified());
+
+    // They should also hold the same value.
+    EXPECT_EQ("base2", base1.getServerTag().get());
+    EXPECT_EQ("base2", base2.getServerTag().get());
+
+    // Reset the server tag to unspecified.
+    base2.setServerTag(Optional<std::string>());
+    EXPECT_FALSE(base1.getServerTag().unspecified());
+    EXPECT_TRUE(base2.getServerTag().unspecified());
+
+    // Merging the config with unspecified server tag should
+    // result in no change in the target config.
+    ASSERT_NO_THROW(base1.merge(base2));
+    EXPECT_FALSE(base1.getServerTag().unspecified());
+    EXPECT_TRUE(base2.getServerTag().unspecified());
+
+    // The server tag should remain the same.
+    EXPECT_EQ("base2", base1.getServerTag().get());
+
+    // Set the explicit server tag in the source config.
+    base2.setServerTag("new-base2");
+
+    // Merge again.
+    ASSERT_NO_THROW(base1.merge(base2));
+
+    // The new value should be stored in the target config, so
+    // both should be specified and have the same value.
+    EXPECT_FALSE(base1.getServerTag().unspecified());
+    EXPECT_FALSE(base2.getServerTag().unspecified());
+    EXPECT_EQ("new-base2", base1.getServerTag().get());
+    EXPECT_EQ("new-base2", base2.getServerTag().get());
+}
+
+// Verifies that server tag can be copied to another config.
+TEST(ConfigBase, copyServerTag) {
+    ConfigBaseImpl base1;
+    ConfigBaseImpl base2;
+
+    // Set server tag for the base2.
+    base2.setServerTag(std::string("base2"));
+
+    // The base1 has server tag unspecified. Copying it to the
+    // base2 should result in unspecified server tag in base2.
+    ASSERT_NO_THROW(base1.copy(base2));
+    EXPECT_TRUE(base2.getServerTag().unspecified());
+
+    // Set server tag for base1 and copy it to base2.
+    base1.setServerTag(std::string("base1"));
+    ASSERT_NO_THROW(base1.copy(base2));
+
+    // The base2 should now hold the value from base1.
+    EXPECT_FALSE(base2.getServerTag().unspecified());
+    EXPECT_EQ("base1", base2.getServerTag().get());
+
+    // Set base1 value to a different value.
+    base1.setServerTag(std::string("new-base1"));
+
+    // Copy again.
+    ASSERT_NO_THROW(base1.copy(base2));
+
+    // It should override the value in the base2.
+    EXPECT_FALSE(base2.getServerTag().unspecified());
+    EXPECT_EQ("new-base1", base2.getServerTag().get());
+}