BIND 10 trac2269, updated. ff837bb1fc512920556a796c40cacc20407b165c [2269] Tests for dhcp6 config parser implemented.

BIND 10 source code commits bind10-changes at lists.isc.org
Fri Sep 28 18:41:36 UTC 2012


The branch, trac2269 has been updated
       via  ff837bb1fc512920556a796c40cacc20407b165c (commit)
       via  7e7171deec8d2a31147a9d5700f4315e2ea3e87b (commit)
       via  4a6553ba36f68f141e7420b070ad6d621f9a8465 (commit)
      from  e2a552fcaa6fcc1f0d39defd990bd5a35a4c73a3 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ff837bb1fc512920556a796c40cacc20407b165c
Author: Tomek Mrugalski <tomasz at isc.org>
Date:   Fri Sep 28 20:40:45 2012 +0200

    [2269] Tests for dhcp6 config parser implemented.

commit 7e7171deec8d2a31147a9d5700f4315e2ea3e87b
Author: Tomek Mrugalski <tomasz at isc.org>
Date:   Fri Sep 28 20:39:13 2012 +0200

    [2269] dhcp6 config parser now returns status codes and does not throw.

commit 4a6553ba36f68f141e7420b070ad6d621f9a8465
Author: Tomek Mrugalski <tomasz at isc.org>
Date:   Thu Sep 27 19:33:20 2012 +0200

    [2269] DHCPv6 startup phase is now handled properly by the config parser.

-----------------------------------------------------------------------

Summary of changes:
 src/bin/dhcp6/config_parser.cc                |  141 ++++++++++++----
 src/bin/dhcp6/config_parser.h                 |    7 +-
 src/bin/dhcp6/ctrl_dhcp6_srv.cc               |   27 +--
 src/bin/dhcp6/dhcp6_messages.mes              |   15 ++
 src/bin/dhcp6/dhcp6_srv.cc                    |    7 +
 src/bin/dhcp6/tests/Makefile.am               |    1 +
 src/bin/dhcp6/tests/config_parser_unittest.cc |  225 +++++++++++++++++++++++++
 src/lib/dhcp/cfgmgr.h                         |   15 ++
 src/lib/dhcp/tests/cfgmgr_unittest.cc         |    7 +
 9 files changed, 399 insertions(+), 46 deletions(-)
 create mode 100644 src/bin/dhcp6/tests/config_parser_unittest.cc

-----------------------------------------------------------------------
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 377c3d4..efe8d84 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -21,13 +21,16 @@
 #include <boost/scoped_ptr.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/algorithm/string.hpp>
-#include <cc/data.h>
 #include <asiolink/io_address.h>
-#include <dhcp6/config_parser.h>
+#include <cc/data.h>
+#include <config/ccsession.h>
+#include <log/logger_support.h>
 #include <dhcp/triplet.h>
 #include <dhcp/pool.h>
 #include <dhcp/subnet.h>
 #include <dhcp/cfgmgr.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp6/dhcp6_log.h>
 
 using namespace std;
 using namespace isc::data;
@@ -40,9 +43,7 @@ typedef boost::shared_ptr<Dhcp6ConfigParser> ParserPtr;
 typedef pair<string, ConstElementPtr> ConfigPair;
 typedef std::vector<ParserPtr> ParserCollection;
 typedef Dhcp6ConfigParser* ParserFactory(const std::string& config_id);
-
 typedef std::map<std::string, ParserFactory*> FactoryMap;
-
 typedef std::map<string, uint32_t> Uint32Storage;
 /// @brief That is a map with global parameters that will be used as defaults
 Uint32Storage uint32_defaults;
@@ -51,44 +52,87 @@ typedef std::map<string, string> StringStorage;
 StringStorage string_defaults;
 
 typedef std::vector<Pool6Ptr> PoolStorage;
-PoolStorage pool_defaults;
 
 /// @brief a dummy configuration parser
 ///
 /// It is a debugging parser. It does not configure anything,
 /// will accept any configuration and will just print it out
-/// on commit.
+/// on commit. Useful for debugging existing configurations and
+/// adding new ones.
 class DummyParser : public Dhcp6ConfigParser {
 public:
+
+    /// @brief Constructor
+    ///
+    /// See \ref Dhcp6ConfigParser class for details.
+    ///
+    /// @param param_name name of the parsed parameter
     DummyParser(const std::string& param_name)
         :param_name_(param_name) {
     }
+
+    /// @brief builds parameter value
+    ///
+    /// See \ref Dhcp6ConfigParser class for details.
     virtual void build(ConstElementPtr new_config) {
+        std::cout << "Build for token: [" << param_name_ << "] = ["
+                  << value_->str() << "]" << std::endl;
         value_ = new_config;
     }
+
+    /// @brief pretends to apply the configuration
+    ///
+    /// This is a method required by base class. It pretends to apply the
+    /// configuration, but in fact it only prints the parameter out.
+    ///
+    /// See \ref Dhcp6ConfigParser class for details.
     virtual void commit() {
-        // debug message. The whole DummyParser class is used only for parser
+        // Debug message. The whole DummyParser class is used only for parser
         // debugging, and is not used in production code. It is very convenient
-        // to keep it around. Please do not turn this cout into logger calls
+        // to keep it around. Please do not turn this cout into logger calls.
         std::cout << "Commit for token: [" << param_name_ << "] = ["
                   << value_->str() << "]" << std::endl;
     }
 
+    /// @brief factory that constructs DummyParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
     static Dhcp6ConfigParser* Factory(const std::string& param_name) {
         return (new DummyParser(param_name));
     }
 
 protected:
+    /// name of the parsed parameter
     std::string param_name_;
+
+    /// pointer to the actual value of the parameter
     ConstElementPtr value_;
 };
 
+/// @brief Configuration parser for uint32 types
+///
+/// This class is a generic parser that is able to handle any uint32 integer
+/// type. By default it stores the value in external global container
+/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, \ref Dhcp6ConfigParser.
+
 class Uint32Parser : public Dhcp6ConfigParser {
 public:
+
+    /// @brief constructor for Uint32Parser
+    /// @param param_name name of the parameter that is going to be parsed
     Uint32Parser(const std::string& param_name)
         :storage_(&uint32_defaults), param_name_(param_name) {
     }
 
+    /// @brief builds parameter value
+    ///
+    /// Parses configuration entry and stored it in storage. See
+    /// \ref setStorage() for details.
+    ///
+    /// @param value pointer to the content of parsed values
     virtual void build(ConstElementPtr value) {
         try {
             value_ = boost::lexical_cast<uint32_t>(value->str());
@@ -96,14 +140,23 @@ public:
             isc_throw(BadValue, "Failed to parse value " << value->str()
                       << " as unsigned 32-bit integer.");
         }
-        cout << "### storing " << param_name_ << "=" << value_ <<
-          " in " << storage_ << endl;
         storage_->insert(pair<string, uint32_t>(param_name_, value_));
     }
 
+    /// @brief does nothing
+    ///
+    /// This method is required for all parser. The value itself
+    /// is not commited anywhere. Higher level parsers are expected to
+    /// use values stored in the storage, e.g. renew-timer for a given
+    /// subnet is stored in subnet-specific storage. It is not commited
+    /// here, but is rather used by \ref Subnet6Parser when constructing
+    /// the subnet.
     virtual void commit() {
     }
 
+    /// @brief factory that constructs DummyParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
     static Dhcp6ConfigParser* Factory(const std::string& param_name) {
         return (new Uint32Parser(param_name));
     }
@@ -158,7 +211,6 @@ public:
     virtual void build(ConstElementPtr value) {
         BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
             interfaces_.push_back(iface->str());
-            cout << "#### Configured to listen on interface " << iface->str() << endl;
         }
     }
 
@@ -201,6 +253,7 @@ public:
             boost::erase_all(txt, " ");
             boost::erase_all(txt, "\t");
 
+            // Is this prefix/len notation?
             size_t pos = txt.find("/");
             if (pos != string::npos) {
                 IOAddress addr("::");
@@ -220,22 +273,18 @@ public:
                               "definition: " << text_pool->stringValue());
                 }
 
-                cout << "#### Creating Pool6(TYPE_IA, " <<  addr.toText() << "/"
-                     << (int)len << ")" << endl;
-                // using prefix/len notation
                 Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
                 pools_->push_back(pool);
                 continue;
             }
 
+            // Is this min-max notation?
             pos = txt.find("-");
             if (pos != string::npos) {
+                // using min-max notation
                 IOAddress min(txt.substr(0,pos-1));
                 IOAddress max(txt.substr(pos+1));
 
-                cout << "#### Creating Pool6(TYPE_IA, " << min.toText() << ","
-                     << max.toText() << ")" << endl;
-
                 Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
 
                 pools_->push_back(pool);
@@ -270,7 +319,6 @@ public:
 
     void build(ConstElementPtr subnet) {
 
-        cout << "#### Subnet6ConfigParser::build(): parsing: [" << subnet->str() << "]" << endl;
         BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
 
             ParserPtr parser(createSubnet6ConfigParser(param.first));
@@ -305,8 +353,6 @@ public:
     void commit() {
 
         StringStorage::const_iterator it = string_values_.find("subnet");
-        cout << "#### Subnet6ConfigParser::commit() string_values_.size()="
-             << string_values_.size() << endl;
         if (it == string_values_.end()) {
             isc_throw(Dhcp6ConfigError,
                       "Mandatory subnet definition in subnet missing");
@@ -328,13 +374,19 @@ public:
         Triplet<uint32_t> pref = getParam("preferred-lifetime");
         Triplet<uint32_t> valid = getParam("valid-lifetime");
 
-        cout << "#### Adding subnet " << addr.toText() << "/" << (int)len
-             << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
-             << pref << ", valid=" << valid << endl;
+        /// @todo: Convert this to logger once the parser is working reliably
+        stringstream tmp;
+        tmp << addr.toText() << "/" << (int)len
+            << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
+            << pref << ", valid=" << valid;
 
-        Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+        LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
 
+        Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
 
+        for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
+            subnet->addPool6(*it);
+        }
 
         CfgMgr::instance().addSubnet6(subnet);
     }
@@ -421,6 +473,12 @@ public:
     }
 
     void commit() {
+        // @todo: Implement more subtle reconfiguration than toss
+        // the old one and replace with the new one.
+
+        // remove old subnets
+        CfgMgr::instance().deleteSubnets6();
+
         BOOST_FOREACH(ParserPtr subnet, subnets_) {
             subnet->commit();
         }
@@ -471,13 +529,18 @@ Dhcp6ConfigParser* createGlobalDhcp6ConfigParser(const std::string& config_id) {
     return (f->second(config_id));
 }
 
-void
-configureDhcp6Server(Dhcpv6Srv& server, ConstElementPtr config_set) {
+ConstElementPtr
+configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
     if (!config_set) {
         isc_throw(Dhcp6ConfigError,
                   "Null pointer is passed to configuration parser");
     }
 
+    /// @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());
+
     ParserCollection parsers;
     try {
         BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
@@ -486,21 +549,35 @@ configureDhcp6Server(Dhcpv6Srv& server, ConstElementPtr config_set) {
             parser->build(config_pair.second);
             parsers.push_back(parser);
         }
-    } catch (const Dhcp6ConfigError& ex) {
-        throw;                  // simply rethrowing it
     } catch (const isc::Exception& ex) {
-        isc_throw(Dhcp6ConfigError, "Server configuration failed: " <<
-                  ex.what());
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                 string("Configuration parsing failed:") + ex.what());
+        return (answer);
+    } catch (...) {
+        // for things like bad_cast in boost::lexical_cast
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                 string("Configuration parsing failed"));
     }
 
     try {
         BOOST_FOREACH(ParserPtr parser, parsers) {
             parser->commit();
         }
+    }
+    catch (const isc::Exception& ex) {
+        ConstElementPtr answer = isc::config::createAnswer(2,
+                                 string("Configuration commit failed:") + ex.what());
+        return (answer);
     } catch (...) {
-        isc_throw(Dhcp6ConfigError, "Unrecoverable error: "
-                  "a configuration parser threw in commit");
+        // for things like bad_cast in boost::lexical_cast
+        ConstElementPtr answer = isc::config::createAnswer(2,
+                                 string("Configuration commit failed"));
     }
+
+    LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
+
+    ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+    return (answer);
 }
 
 }; // end of isc::dhcp namespace
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 497a89c..5c941d5 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -128,8 +128,9 @@ public:
 ///
 /// \param server The \c Dhcpv6Srv object to be configured.
 /// \param config_set A JSON style configuration to apply to \c server.
-void configureDhcp6Server(Dhcpv6Srv& server,
-                         isc::data::ConstElementPtr config_set);
+isc::data::ConstElementPtr
+configureDhcp6Server(Dhcpv6Srv& server,
+                     isc::data::ConstElementPtr config_set);
 
 
 /// Create a new \c Dhcp6ConfigParser object for a given configuration
@@ -161,7 +162,7 @@ void configureDhcp6Server(Dhcpv6Srv& server,
 /// is to be created.
 /// \return A pointer to an \c Dhcp6ConfigParser object.
 Dhcp6ConfigParser* createDhcp6ConfigParser(Dhcpv6Srv& server,
-                                         const std::string& config_id);
+                                           const std::string& config_id);
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 6e2f0d8..4f402dd 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -49,14 +49,13 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
               .arg(new_config->str());
 
-    /// @todo: commented out as server is not able to understand partial
-    /// configuration (only the diff and not the whole configuration)
-    /* if (server_) {
-        configureDhcp6Server(*server_, new_config);
-        } */
-
-    ConstElementPtr answer = isc::config::createAnswer(0,
-                             "Thank you for sending config.");
+    if (server_) {
+        return (configureDhcp6Server(*server_, new_config));
+    }
+
+    // that should never happen as we install config_handler after we
+    ConstElementPtr answer = isc::config::createAnswer(1,
+           "Configuration rejected, server is during startup/shutdown phase.");
     return (answer);
 }
 
@@ -94,7 +93,7 @@ void ControlledDhcpv6Srv::sessionReader(void) {
 }
 
 void ControlledDhcpv6Srv::establishSession() {
-    
+
     string specfile;
     if (getenv("B10_FROM_BUILD")) {
         specfile = string(getenv("B10_FROM_BUILD")) +
@@ -104,15 +103,21 @@ void ControlledDhcpv6Srv::establishSession() {
     }
 
     /// @todo: Check if session is not established already. Throw, if it is.
-    
+
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTING)
               .arg(specfile);
     cc_session_ = new Session(io_service_.get_io_service());
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
-                                          dhcp6ConfigHandler,
+                                          NULL,
                                           dhcp6CommandHandler, false);
     config_session_->start();
 
+    // We initially create ModuleCCSession() without configHandler, as
+    // the session module is too eager to send partial configuration.
+    // We want to get the full configuration, so we explicitly call
+    // getFullConfig() and then pass it to our configHandler.
+    config_session_->setConfigHandler(dhcp6ConfigHandler);
+
     try {
         configureDhcp6Server(*this, config_session_->getFullConfig());
     } catch (const Dhcp6ConfigError& ex) {
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index dcfb2ca..56f4b1c 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -105,3 +105,18 @@ This critical error message indicates that the initial DHCPv6
 configuration has failed. The server will continue to run, but
 administrator's intervention is required. The server's configuration
 must be fixed before it can become usable.
+
+% DHCP6_CONFIG_START DHCPv6 server is processing the following configuration: %1
+This is a debug message that is issued every time the server receives
+configuration. That happens during every start up and also when server
+configuration change is committed by the administrator.
+
+% DHCP6_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+This is an informational message that the configuration has extended
+and specified new subnet is now supported.
+
+% DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1
+This is an informational message that announces successful processing
+of a new configuration. That may happen as a result of one of two
+events: server startup or commit of configuration change, done by the
+administrator. Additional information may be provided.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 7c21941..f9457d5 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -42,6 +42,13 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
 const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
+    if (port == 0) {
+        // used for testing purposes. Some tests, e.g. configuration parser,
+        // require Dhcpv6Srv object, but they don't really need it to do
+        // anything. This speed up and simplifies the tests.
+        return;
+    }
+
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
 
     // First call to instance() will create IfaceMgr (it's a singleton)
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 30d2c38..1479733 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -45,6 +45,7 @@ TESTS += dhcp6_unittests
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
new file mode 100644
index 0000000..8c38762
--- /dev/null
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -0,0 +1,225 @@
+// Copyright (C) 2012 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 <config.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/config_parser.h>
+#include <config/ccsession.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+class Dhcp6ParserTest : public ::testing::Test {
+public:
+    Dhcp6ParserTest()
+    :rcode_(-1) {
+        // open port 0 means to not do anything at all
+        srv_ = new Dhcpv6Srv(0);
+    }
+
+    ~Dhcp6ParserTest() {
+        delete srv_;
+    };
+
+    Dhcpv6Srv * srv_;
+
+    int rcode_;
+    ConstElementPtr comment_;
+};
+
+
+TEST_F(Dhcp6ParserTest, version) {
+
+    ConstElementPtr x;
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{\"version\": 0}")));
+
+    // returned value must be 0 (configuration accepted)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+}
+
+TEST_F(Dhcp6ParserTest, bogus_command) {
+
+    ConstElementPtr x;
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{\"bogus\": 5}")));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(1, rcode_);
+}
+
+TEST_F(Dhcp6ParserTest, empty_subnet) {
+
+    ConstElementPtr x;
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{ \"interface\": [ \"all\" ],"
+                                      "\"preferred-lifetime\": 3000,"
+                                      "\"rebind-timer\": 2000, "
+                                      "\"renew-timer\": 1000, "
+                                      "\"subnet6\": [  ], "
+                                      "\"valid-lifetime\": 4000 }")));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+}
+
+TEST_F(Dhcp6ParserTest, subnet_global_defaults) {
+
+    ConstElementPtr x;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1000, subnet->getT1());
+    EXPECT_EQ(2000, subnet->getT2());
+    EXPECT_EQ(3000, subnet->getPreferred());
+    EXPECT_EQ(4000, subnet->getValid());
+}
+
+//
+TEST_F(Dhcp6ParserTest, subnet_local) {
+
+    ConstElementPtr x;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"renew-timer\": 1, "
+        "    \"rebind-timer\": 2, "
+        "    \"preferred-lifetime\": 3,"
+        "    \"valid-lifetime\": 4,"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1, subnet->getT1());
+    EXPECT_EQ(2, subnet->getT2());
+    EXPECT_EQ(3, subnet->getPreferred());
+    EXPECT_EQ(4, subnet->getValid());
+}
+
+TEST_F(Dhcp6ParserTest, pool_out_of_subnet) {
+
+    ConstElementPtr x;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"4001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+
+    // returned value must be 2 (values error)
+    // as the pool does not belong to that subnet
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(2, rcode_);
+
+}
+
+TEST_F(Dhcp6ParserTest, subnet_prefix_len) {
+
+    ConstElementPtr x;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1000, subnet->getT1());
+    EXPECT_EQ(2000, subnet->getT2());
+    EXPECT_EQ(3000, subnet->getPreferred());
+    EXPECT_EQ(4000, subnet->getValid());
+}
+
+};
diff --git a/src/lib/dhcp/cfgmgr.h b/src/lib/dhcp/cfgmgr.h
index 5b73f2b..f57ef99 100644
--- a/src/lib/dhcp/cfgmgr.h
+++ b/src/lib/dhcp/cfgmgr.h
@@ -98,6 +98,21 @@ public:
     /// to remove subnets. The only case where subnet6 removal would be
     /// needed is a dynamic server reconfiguration - a use case that is not
     /// planned to be supported any time soon.
+
+    /// @brief removes all subnets
+    ///
+    /// This method removes all existing subnets. It is used during
+    /// reconfiguration - old configuration is wiped and new definitions
+    /// are used to recreate subnets.
+    ///
+    /// @todo Implement more intelligent approach. Note that comparison
+    /// between old and new configuration is tricky. For example: is
+    /// 2000::/64 and 2000::/48 the same subnet or is it something
+    /// completely new?
+    void deleteSubnets6() {
+        subnets6_.clear();
+    }
+
 protected:
 
     /// @brief Protected constructor.
diff --git a/src/lib/dhcp/tests/cfgmgr_unittest.cc b/src/lib/dhcp/tests/cfgmgr_unittest.cc
index 7f287ea..bdd9a37 100644
--- a/src/lib/dhcp/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcp/tests/cfgmgr_unittest.cc
@@ -266,6 +266,9 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
     Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
     EXPECT_THROW(subnet->addPool6(pool3), BadValue);
 
+
+    Pool6Ptr pool4(new Pool6(Pool6::TYPE_IA, IOAddress("4001:db8:1::"), 80));
+    EXPECT_THROW(subnet->addPool6(pool4), BadValue);
 }
 
 // This test verifies if the configuration manager is able to hold and return
@@ -294,6 +297,10 @@ TEST(CfgMgrTest, subnet6) {
     EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
     EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
 
+    cfg_mgr.deleteSubnets6();
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
 }
 
 } // end of anonymous namespace



More information about the bind10-changes mailing list