Browse Source

[master] Merge branch 'trac3477'

Marcin Siodelski 10 years ago
parent
commit
af2f91d258

+ 5 - 0
src/bin/d2/d2_cfg_mgr.cc

@@ -196,6 +196,11 @@ D2CfgMgr::getD2Params() {
     return (getD2CfgContext()->getD2Params());
 }
 
+std::string
+D2CfgMgr::getConfigSummary(const uint32_t) {
+    return (getD2Params()->getConfigSummary());
+}
+
 void
 D2CfgMgr::buildParams(isc::data::ConstElementPtr params_config) {
     // Base class build creates parses and invokes build on each parser.

+ 8 - 0
src/bin/d2/d2_cfg_mgr.h

@@ -238,6 +238,14 @@ public:
     /// @return reference to const D2ParamsPtr
     const D2ParamsPtr& getD2Params();
 
+    /// @brief Returns configuration summary in the textual format.
+    ///
+    /// @param selection Bitfield which describes the parts of the configuration
+    /// to be returned. This parameter is ignored for the D2.
+    ///
+    /// @return Summary of the configuration in the textual format.
+    virtual std::string getConfigSummary(const uint32_t selection);
+
 protected:
     /// @brief Performs the parsing of the given "params" element.
     ///

+ 9 - 0
src/bin/d2/d2_config.cc

@@ -22,6 +22,7 @@
 #include <boost/lexical_cast.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 
+#include <sstream>
 #include <string>
 
 namespace isc {
@@ -88,6 +89,14 @@ D2Params::validateContents() {
     }
 }
 
+std::string
+D2Params::getConfigSummary() const {
+    std::ostringstream s;
+    s << "listening on " << getIpAddress() << ", port " << getPort()
+      << ", using " << ncrProtocolToString(ncr_protocol_);
+    return (s.str());
+}
+
 bool
 D2Params::operator == (const D2Params& other) const {
     return ((ip_address_ == other.ip_address_) &&

+ 9 - 0
src/bin/d2/d2_config.h

@@ -211,6 +211,15 @@ public:
         return(ncr_format_);
     }
 
+    /// @brief Return summary of the configuration used by D2.
+    ///
+    /// The returned summary of the configuration is meant to be appended to
+    /// the log message informing about the successful completion of the
+    /// D2 configuration.
+    ///
+    /// @return Configuration summary in the textual format.
+    std::string getConfigSummary() const;
+
     /// @brief Compares two D2Paramss for equality
     bool operator == (const D2Params& other) const;
 

+ 2 - 2
src/bin/d2/d_cfg_mgr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -246,7 +246,7 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
         }
 
         // Everything was fine. Configuration set processed successfully.
-        LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg("");
+        LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
         answer = isc::config::createAnswer(0, "Configuration committed.");
 
     } catch (const std::exception& ex) {

+ 12 - 0
src/bin/d2/d_cfg_mgr.h

@@ -308,6 +308,18 @@ public:
         return (context_);
     }
 
+    /// @brief Returns configuration summary in the textual format.
+    ///
+    /// This method returns the brief text describing the current configuration.
+    /// It may be used for logging purposes, e.g. whn the new configuration is
+    /// committed to notify a user about the changes in configuration.
+    ///
+    /// @param selection Bitfield which describes the parts of the configuration
+    /// to be returned.
+    ///
+    /// @return Summary of the configuration in the textual format.
+    virtual std::string getConfigSummary(const uint32_t selection) = 0;
+
 protected:
     /// @brief Parses a set of scalar configuration elements into global
     /// parameters

+ 4 - 0
src/bin/d2/tests/d2_cfg_mgr_unittests.cc

@@ -376,6 +376,10 @@ TEST_F(D2CfgMgrTest, validParamsEntry) {
     // Verify that the global scalars have the proper values.
     EXPECT_EQ(isc::asiolink::IOAddress("3001::5"),
               d2_params_->getIpAddress());
+
+    // Verify the configuration summary.
+    EXPECT_EQ("listening on 3001::5, port 777",
+              d2_params_->getConfigSummary());
 }
 
 /// @brief Tests default values for D2Params.

+ 5 - 0
src/bin/d2/tests/d_cfg_mgr_unittests.cc

@@ -57,6 +57,11 @@ public:
                        const isc::data::Element::Position& /* pos */) {
         return (isc::dhcp::ParserPtr());
     }
+
+    /// @brief Returns summary of configuration in the textual format.
+    virtual std::string getConfigSummary(const uint32_t) {
+        return ("");
+    }
 };
 
 /// @brief Test fixture class for testing DCfgMgrBase class.

+ 14 - 0
src/bin/d2/tests/d_test_stubs.h

@@ -180,6 +180,13 @@ public:
     virtual isc::data::ConstElementPtr command(const std::string& command,
                                                isc::data::ConstElementPtr args);
 
+    /// @brief Returns configuration summary in the textual format.
+    ///
+    /// @return Always an empty string.
+    virtual std::string getConfigSummary(const uint32_t) {
+        return ("");
+    }
+
     // @brief Destructor
     virtual ~DStubProcess();
 };
@@ -701,6 +708,13 @@ public:
                        const isc::data::Element::Position& pos
                        = isc::data::Element::Position());
 
+    /// @brief Returns a summary of the configuration in the textual format.
+    ///
+    /// @return Always an empty string.
+    virtual std::string getConfigSummary(const uint32_t) {
+        return ("");
+    }
+
     /// @brief A list for remembering the element ids in the order they were
     /// parsed.
     ElementIdList parsed_order_;

+ 3 - 4
src/bin/dhcp4/json_config_parser.cc

@@ -496,9 +496,6 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
         return (answer);
     }
 
-    /// @todo: Append most essential info here (like "2 new subnets configured")
-    string config_details;
-
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
               DHCP4_CONFIG_START).arg(config_set->str());
 
@@ -657,7 +654,9 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
         return (answer);
     }
 
-    LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
+    LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE)
+        .arg(CfgMgr::instance().getConfiguration()->
+             getConfigSummary(Configuration::CFGSEL_ALL4));
 
     // Everything was fine. Configuration is successful.
     answer = isc::config::createAnswer(0, "Configuration committed.");

+ 3 - 4
src/bin/dhcp6/json_config_parser.cc

@@ -698,9 +698,6 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
         return (answer);
     }
 
-    /// @todo: Append most essential info here (like "2 new subnets configured")
-    string config_details;
-
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND,
               DHCP6_CONFIG_START).arg(config_set->str());
 
@@ -858,7 +855,9 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
         return (answer);
     }
 
-    LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
+    LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE)
+        .arg(CfgMgr::instance().getConfiguration()->
+             getConfigSummary(Configuration::CFGSEL_ALL6));
 
     // Everything was fine. Configuration is successful.
     answer = isc::config::createAnswer(0, "Configuration committed.");

+ 1 - 1
src/lib/dhcpsrv/Makefile.am

@@ -60,7 +60,7 @@ libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
 libkea_dhcpsrv_la_SOURCES += logging.cc logging.h
-libkea_dhcpsrv_la_SOURCES += configuration.h
+libkea_dhcpsrv_la_SOURCES += configuration.h configuration.cc
 libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
 
 if HAVE_MYSQL

+ 2 - 2
src/lib/dhcpsrv/cfgmgr.h

@@ -250,12 +250,12 @@ public:
     /// completely new?
     void deleteSubnets6();
 
-    /// @brief returns const reference to all subnets6
+    /// @brief Returns pointer to the collection of all IPv4 subnets.
     ///
     /// This is used in a hook (subnet4_select), where the hook is able
     /// to choose a different subnet. Server code has to offer a list
     /// of possible choices (i.e. all subnets).
-    /// @return a pointer to const Subnet6 collection
+    /// @return a pointer to const Subnet4 collection
     const Subnet4Collection* getSubnets4() const {
         return (&subnets4_);
     }

+ 64 - 0
src/lib/dhcpsrv/configuration.cc

@@ -0,0 +1,64 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/configuration.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+std::string
+Configuration::getConfigSummary(const uint32_t selection) const {
+    std::ostringstream s;
+    size_t subnets_num;
+    if ((selection & CFGSEL_SUBNET4) == CFGSEL_SUBNET4) {
+        subnets_num = CfgMgr::instance().getSubnets4()->size();
+        if (subnets_num > 0) {
+            s << "added IPv4 subnets: " << subnets_num;
+        } else {
+            s << "no IPv4 subnets!";
+        }
+        s << "; ";
+    }
+
+    if ((selection & CFGSEL_SUBNET6) == CFGSEL_SUBNET6) {
+        subnets_num = CfgMgr::instance().getSubnets6()->size();
+        if (subnets_num > 0) {
+            s << "added IPv6 subnets: " << subnets_num;
+        } else {
+            s << "no IPv6 subnets!";
+        }
+        s << "; ";
+    }
+
+    if ((selection & CFGSEL_DDNS) == CFGSEL_DDNS) {
+        bool ddns_enabled = CfgMgr::instance().ddnsEnabled();
+        s << "DDNS: " << (ddns_enabled ? "enabled" : "disabled") << "; ";
+    }
+
+    if (s.tellp() == 0) {
+        s << "no config details available";
+    }
+
+    std::string summary = s.str();
+    size_t last_separator_pos = summary.find_last_of(";");
+    if (last_separator_pos == summary.length() - 2) {
+        summary.erase(last_separator_pos);
+    }
+    return (summary);
+}
+
+}
+}

+ 50 - 3
src/lib/dhcpsrv/configuration.h

@@ -23,6 +23,8 @@
 namespace isc {
 namespace dhcp {
 
+class CfgMgr;
+
 /// @brief Defines single logging destination
 ///
 /// This structure is used to keep log4cplus configuration parameters.
@@ -33,7 +35,7 @@ struct LoggingDestination {
     /// Values accepted are: stdout, stderr, syslog, syslog:name.
     /// Any other destination will be considered a file name.
     std::string output_;
-    
+
     /// @brief Maximum number of log files in rotation
     int maxver_;
 
@@ -53,10 +55,10 @@ struct LoggingDestination {
 ///                    "maxver": 8,
 ///                    "maxsize": 204800
 ///                }
-///            ], 
+///            ],
 ///            "severity": "WARN",
 ///            "debuglevel": 99
-///        }, 
+///        },
 struct LoggingInfo {
 
     /// @brief logging name
@@ -82,8 +84,53 @@ typedef std::vector<isc::dhcp::LoggingInfo> LoggingInfoStorage;
 /// @todo Migrate all other configuration parameters from cfgmgr.h here
 struct Configuration {
 
+    /// @name Constants for selection of parameters returned by @c getConfigSummary
+    ///
+    //@{
+    /// Nothing selected
+    static const uint32_t CFGSEL_NONE    = 0x00000000;
+    /// Number of IPv4 subnets
+    static const uint32_t CFGSEL_SUBNET4 = 0x00000001;
+    /// Number of IPv6 subnets
+    static const uint32_t CFGSEL_SUBNET6 = 0x00000002;
+    /// Number of enabled ifaces
+    static const uint32_t CFGSEL_IFACE4  = 0x00000004;
+    /// Number of v6 ifaces
+    static const uint32_t CFGSEL_IFACE6  = 0x00000008;
+    /// DDNS enabled/disabled
+    static const uint32_t CFGSEL_DDNS    = 0x00000010;
+    /// Number of all subnets
+    static const uint32_t CFGSEL_SUBNET  = 0x00000003;
+    /// IPv4 related config
+    static const uint32_t CFGSEL_ALL4    = 0x00000015;
+    /// IPv6 related config
+    static const uint32_t CFGSEL_ALL6    = 0x0000001A;
+    /// Whole config
+    static const uint32_t CFGSEL_ALL     = 0xFFFFFFFF;
+    //@}
+
     /// @brief logging specific information
     LoggingInfoStorage logging_info_;
+
+    /// @brief Returns summary of the configuration in the textual format.
+    ///
+    /// This method returns the brief text describing the current configuration.
+    /// It may be used for logging purposes, e.g. when the new configuration is
+    /// committed to notify a user about the changes in configuration.
+    ///
+    /// @todo Currently this method uses @c CfgMgr accessors to get the
+    /// configuration parameters. Once these parameters are migrated from the
+    /// @c CfgMgr this method will have to be modified accordingly.
+    ///
+    /// @todo Implement reporting a summary of interfaces being used for
+    /// receiving and sending DHCP messages. This will be implemented with
+    /// ticket #3512.
+    ///
+    /// @param selection Is a bitfield which describes the parts of the
+    /// configuration to be returned.
+    ///
+    /// @return Summary of the configuration in the textual format.
+    std::string getConfigSummary(const uint32_t selection) const;
 };
 
 /// @brief pointer to the configuration

+ 206 - 14
src/lib/dhcpsrv/tests/configuration_unittest.cc

@@ -14,9 +14,12 @@
 
 #include <config.h>
 
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/configuration.h>
+#include <dhcpsrv/subnet.h>
 #include <gtest/gtest.h>
 
+using namespace isc::asiolink;
 using namespace isc::dhcp;
 
 // Those are the tests for Configuration storage. Right now they are minimal,
@@ -25,18 +28,138 @@ using namespace isc::dhcp;
 
 namespace {
 
-// Check that by default there are no logging entries
-TEST(ConfigurationTest, basic) {
-    Configuration x;
+/// @brief Number of IPv4 and IPv6 subnets to be created for a test.
+const int TEST_SUBNETS_NUM = 3;
+
+/// @brief Test fixture class for testing configuration data storage.
+class ConfigurationTest : public ::testing::Test {
+public:
+    /// @brief Constructor.
+    ///
+    /// Creates IPv4 and IPv6 subnets for unit test. The number of subnets
+    /// is @c TEST_SUBNETS_NUM for IPv4 and IPv6 each.
+    ConfigurationTest() {
+        // Remove any subnets dangling from previous unit tests.
+        clearSubnets();
+        // Create IPv4 subnets.
+        for (int i = 0; i < TEST_SUBNETS_NUM; ++i) {
+            // Default triplet carried undefined value.
+            Triplet<uint32_t> def_triplet;
+            // Create a collection of subnets: 192.0.X.0/24 where X is
+            // 0, 1, 2 etc.
+            Subnet4Ptr subnet(new Subnet4(IOAddress(0xC0000000 | (i << 2)),
+                                          24, def_triplet, def_triplet,
+                                          4000));
+            test_subnets4_.push_back(subnet);
+        }
+        // Create IPv6 subnets.
+        for (int i = 0; i < TEST_SUBNETS_NUM; ++i) {
+            // This is a base prefix. All other prefixes will be created by
+            // modifying this one.
+            IOAddress prefix("2001:db8:1::0");
+            std::vector<uint8_t> prefix_bytes = prefix.toBytes();
+            // Modify 5th byte of the prefix, so 2001:db8:1::0 becomes
+            // 2001:db8:2::0 etc.
+            ++prefix_bytes[5];
+            prefix = IOAddress::fromBytes(prefix.getFamily(), &prefix_bytes[0]);
+            Subnet6Ptr subnet(new Subnet6(prefix, 64, 1000, 2000, 3000, 4000));
+            test_subnets6_.push_back(subnet);
+        }
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Removes any dangling configuration.
+    virtual ~ConfigurationTest() {
+        clearSubnets();
+    }
+
+    /// @brief Convenience function which adds IPv4 subnet to the configuration.
+    ///
+    /// @param index Index of the subnet in the @c test_subnets4_ collection
+    /// which should be added to the configuration. The configuration is stored
+    /// in the @ conf_ member. This value must be lower than
+    /// @c TEST_SUBNETS_NUM.
+    ///
+    /// @todo Until the subnets configuration is migrated from the @c CfgMgr to
+    /// the @c Configuration object, this function adds the subnet to the
+    /// @c CfgMgr. Once, the subnet configuration is held in the
+    /// @c Configuration this function must be modified to store the subnets in
+    /// the @c conf_ object.
+    void addSubnet4(const unsigned int index);
+
+    /// @brief Convenience function which adds IPv6 subnet to the configuration.
+    ///
+    /// @param index Index of the subnet in the @c test_subnets6_ collection
+    /// which should be added to the configuration. The configuration is stored
+    /// in the @ conf_ member. This value must be lower than
+    /// @c TEST_SUBNETS_NUM.
+    ///
+    /// @todo Until the subnets configuration is migrated from the @c CfgMgr to
+    /// the @c Configuration object, this function adds the subnet to the
+    /// @c CfgMgr. Once, the subnet configuration is held in the
+    /// @c Configuration this function must be modified to store the subnets in
+    /// @c conf_ object.
+    void addSubnet6(const unsigned int index);
+
+    /// @brief Removes all subnets from the configuration.
+    ///
+    /// @todo Modify this function once the subnet configuration is migrated
+    /// from @c CfgMgr to @c Configuration.
+    void clearSubnets();
+
+    /// @brief Enable/disable DDNS.
+    ///
+    /// @param enable A boolean value indicating if the DDNS should be
+    /// enabled (true) or disabled (false).
+    void enableDDNS(const bool enable);
+
+    /// @brief Stores configuration.
+    Configuration conf_;
+    /// @brief A collection of IPv4 subnets used by unit tests.
+    Subnet4Collection test_subnets4_;
+    /// @brief A collection of IPv6 subnets used by unit tests.
+    Subnet6Collection test_subnets6_;
+
+};
+
+void
+ConfigurationTest::addSubnet4(const unsigned int index) {
+    if (index >= TEST_SUBNETS_NUM) {
+        FAIL() << "Subnet index " << index << "out of range (0.."
+               << TEST_SUBNETS_NUM << "): " << "unable to add IPv4 subnet";
+    }
+    CfgMgr::instance().addSubnet4(test_subnets4_[index]);
+}
 
-    EXPECT_TRUE(x.logging_info_.empty());
+void
+ConfigurationTest::addSubnet6(const unsigned int index) {
+    if (index >= TEST_SUBNETS_NUM) {
+        FAIL() << "Subnet index " << index << "out of range (0.."
+               << TEST_SUBNETS_NUM << "): " << "unable to add IPv6 subnet";
+    }
+    CfgMgr::instance().addSubnet6(test_subnets6_[index]);
 }
 
-// Check that Configuration can store logging information.
-TEST(ConfigurationTest, loggingInfo) {
+void
+ConfigurationTest::clearSubnets() {
+    CfgMgr::instance().deleteSubnets4();
+    CfgMgr::instance().deleteSubnets6();
+}
 
-    Configuration x;
+void
+ConfigurationTest::enableDDNS(const bool enable) {
+    // D2 configuration should always be non-NULL.
+    CfgMgr::instance().getD2ClientConfig()->enableUpdates(enable);
+}
+
+// Check that by default there are no logging entries
+TEST_F(ConfigurationTest, basic) {
+    EXPECT_TRUE(conf_.logging_info_.empty());
+}
 
+// Check that Configuration can store logging information.
+TEST_F(ConfigurationTest, loggingInfo) {
     LoggingInfo log1;
     log1.name_ = "foo";
     log1.severity_ = isc::log::WARN;
@@ -49,15 +172,84 @@ TEST(ConfigurationTest, loggingInfo) {
 
     log1.destinations_.push_back(dest);
 
-    x.logging_info_.push_back(log1);
+    conf_.logging_info_.push_back(log1);
+
+    EXPECT_EQ("foo", conf_.logging_info_[0].name_);
+    EXPECT_EQ(isc::log::WARN, conf_.logging_info_[0].severity_);
+    EXPECT_EQ(77, conf_.logging_info_[0].debuglevel_);
+
+    EXPECT_EQ("some-logfile.txt", conf_.logging_info_[0].destinations_[0].output_);
+    EXPECT_EQ(5, conf_.logging_info_[0].destinations_[0].maxver_);
+    EXPECT_EQ(2097152, conf_.logging_info_[0].destinations_[0].maxsize_);
+}
 
-    EXPECT_EQ("foo", x.logging_info_[0].name_);
-    EXPECT_EQ(isc::log::WARN, x.logging_info_[0].severity_);
-    EXPECT_EQ(77, x.logging_info_[0].debuglevel_);
+// Check that the configuration summary including information about the status
+// of DDNS is returned.
+TEST_F(ConfigurationTest, summaryDDNS) {
+    EXPECT_EQ("DDNS: disabled",
+              conf_.getConfigSummary(Configuration::CFGSEL_DDNS));
+
+    enableDDNS(true);
+    EXPECT_EQ("DDNS: enabled",
+              conf_.getConfigSummary(Configuration::CFGSEL_DDNS));
+
+    enableDDNS(false);
+    EXPECT_EQ("no IPv4 subnets!; no IPv6 subnets!; DDNS: disabled",
+              conf_.getConfigSummary(Configuration::CFGSEL_ALL));
+}
 
-    EXPECT_EQ("some-logfile.txt", x.logging_info_[0].destinations_[0].output_);
-    EXPECT_EQ(5, x.logging_info_[0].destinations_[0].maxver_);
-    EXPECT_EQ(2097152, x.logging_info_[0].destinations_[0].maxsize_);
+// Check that the configuration summary including information about added
+// subnets is returned.
+TEST_F(ConfigurationTest, summarySubnets) {
+    EXPECT_EQ("no config details available",
+              conf_.getConfigSummary(Configuration::CFGSEL_NONE));
+
+    // Initially, there are no subnets added but it should be explicitly
+    // reported when we query for information about the subnets.
+    EXPECT_EQ("no IPv4 subnets!; no IPv6 subnets!",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+
+    // If we just want information about IPv4 subnets, there should be no
+    // mention of IPv6 subnets, even though there are none added.
+    EXPECT_EQ("no IPv4 subnets!",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET4));
+
+    // If we just want information about IPv6 subnets, there should be no
+    // mention of IPv4 subnets, even though there are none added.
+    EXPECT_EQ("no IPv6 subnets!",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET6));
+
+    // Add IPv4 subnet and make sure it is reported.
+    addSubnet4(0);
+    EXPECT_EQ("added IPv4 subnets: 1",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET4));
+    EXPECT_EQ("added IPv4 subnets: 1; no IPv6 subnets!",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+
+    // Add IPv6 subnet and make sure it is reported.
+    addSubnet6(0);
+    EXPECT_EQ("added IPv6 subnets: 1",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET6));
+    EXPECT_EQ("added IPv4 subnets: 1; added IPv6 subnets: 1",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+
+    // Add one more subnet and make sure the bumped value is only
+    // for IPv4, but not for IPv6.
+    addSubnet4(1);
+    EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 1",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+    EXPECT_EQ("added IPv4 subnets: 2",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET4));
+
+    addSubnet6(1);
+    EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 2",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
+
+    // Remove all subnets and make sure that there are no reported subnets
+    // back again.
+    clearSubnets();
+    EXPECT_EQ("no IPv4 subnets!; no IPv6 subnets!",
+              conf_.getConfigSummary(Configuration::CFGSEL_SUBNET));
 }
 
 } // end of anonymous namespace