]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[65-libyang-pd-pool] Brought pd-pool code from kea-yang
authorFrancis Dupont <fdupont@isc.org>
Tue, 25 Sep 2018 17:11:14 +0000 (19:11 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 26 Sep 2018 16:20:47 +0000 (12:20 -0400)
src/lib/yang/Makefile.am
src/lib/yang/tests/Makefile.am
src/lib/yang/tests/translator_pd_pool_unittests.cc [new file with mode: 0644]
src/lib/yang/translator_pd_pool.cc [new file with mode: 0644]
src/lib/yang/translator_pd_pool.h [new file with mode: 0644]

index 4761a7127d49bbb666a321eba6f92a21981fab43..699375538c1d6488dae1bff471c9061e49951643 100644 (file)
@@ -10,6 +10,7 @@ libkea_yang_la_SOURCES += sysrepo_error.h
 libkea_yang_la_SOURCES += translator.cc translator.h
 libkea_yang_la_SOURCES += translator_option_data.cc
 libkea_yang_la_SOURCES += translator_option_data.h
+libkea_yang_la_SOURCES += translator_pd_pool.cc translator_pd_pool.h
 
 libkea_yang_la_LIBADD =  $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 libkea_yang_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
@@ -27,7 +28,8 @@ libkea_yang_include_HEADERS = \
        adaptor.h \
        sysrepo_error.h \
        translator.h \
-       translator_option_data.h
+       translator_option_data.h \
+       translator_pd_pool.h
 
 EXTRA_DIST = yang.dox
 # Distribute yang models.
index 5e593f1d191f119c3a178e5367ded58d427e43e4..6831bceb454c36fbcdb3f914eb8086ab51be8981 100644 (file)
@@ -21,6 +21,7 @@ run_unittests_SOURCES  = adaptor_unittests.cc
 run_unittests_SOURCES += sysrepo_setup.h
 run_unittests_SOURCES += translator_unittests.cc
 run_unittests_SOURCES += translator_option_data_unittests.cc
+run_unittests_SOURCES += translator_pd_pool_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
diff --git a/src/lib/yang/tests/translator_pd_pool_unittests.cc b/src/lib/yang/tests/translator_pd_pool_unittests.cc
new file mode 100644 (file)
index 0000000..7869bc0
--- /dev/null
@@ -0,0 +1,321 @@
+// Copyright (C) 2018 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 <yang/translator_pd_pool.h>
+#include <yang/tests/sysrepo_setup.h>
+
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+
+namespace {
+
+/// @brief Translator name.
+extern char const pd_pool_list[] = "pd pool list";
+
+/// @brief Test fixture class for @ref TranslatorPdPools.
+class TranslatorPdPoolsTest :
+    public GenericTranslatorTest<pd_pool_list, TranslatorPdPools> {
+public:
+
+    /// Constructor.
+    TranslatorPdPoolsTest() { }
+
+    /// Destructor (does nothing).
+    virtual ~TranslatorPdPoolsTest() { }
+};
+
+// This test verifies that an empty pd pool list can be properly
+// translated from YANG to JSON using the IETF model.
+TEST_F(TranslatorPdPoolsTest, getEmptyIetf) {
+    useModel("ietf-dhcpv6-server");
+
+    // Get the pd-pool list and checks it is empty.
+    const string& xpath =
+        "/ietf-dhcpv6-server:server/server-config/network-ranges"
+        "/network-range[network-range-id='111']/pd-pools";
+    ConstElementPtr pools;
+    EXPECT_NO_THROW(pools = t_obj_->getPdPools(xpath));
+    ASSERT_TRUE(pools);
+    ASSERT_EQ(Element::list, pools->getType());
+    EXPECT_EQ(0, pools->size());
+}
+
+// This test verifies that an empty pd pool list can be properly
+// translated from YANG to JSON using the Kea ad hoc model.
+TEST_F(TranslatorPdPoolsTest, getEmptyKea) {
+    useModel("kea-dhcp6-server");
+
+    // Get the pd-pool list and checks it is empty.
+    const string& xpath =
+        "/kea-dhcp6-server:config/subnet6/subnet6[id='111']/pd-pools";
+    ConstElementPtr pools;
+    EXPECT_NO_THROW(pools = t_obj_->getPdPools(xpath));
+    ASSERT_TRUE(pools);
+    ASSERT_EQ(Element::list, pools->getType());
+    EXPECT_EQ(0, pools->size());
+}
+
+// This test verifies that one empty pd pool can be properly
+// translated from YANG to JSON using the IETF model.
+TEST_F(TranslatorPdPoolsTest, getIetf) {
+    useModel("ietf-dhcpv6-server");
+
+    // Create the subnet 2001:db8::/48 #111.
+    const string& subnet =
+        "/ietf-dhcpv6-server:server/server-config/network-ranges"
+        "/network-range[network-range-id='111']";
+    S_Val v_subnet(new Val("2001:db8::/48", SR_STRING_T));
+    const string& subnet_subnet = subnet + "/network-prefix";
+    EXPECT_NO_THROW(sess_->set_item(subnet_subnet.c_str(), v_subnet));
+
+    // Create the pd-pool 2001:db8:0:1000::/64 #222.
+    const string& xpath = subnet + "/pd-pools/pd-pool[pool-id='222']";
+    const string& prefix = xpath + "/prefix";
+    S_Val s_prefix(new Val("2001:db8:0:1000::/56"));
+    EXPECT_NO_THROW(sess_->set_item(prefix.c_str(), s_prefix));
+    const string& length = xpath + "/prefix-length";
+    uint8_t len = 56;
+    S_Val s_length(new Val(len, SR_UINT8_T));
+    EXPECT_NO_THROW(sess_->set_item(length.c_str(), s_length));
+
+    // Get the pool.
+    ConstElementPtr pool;
+    EXPECT_NO_THROW(pool = t_obj_->getPdPool(xpath));
+    ASSERT_TRUE(pool);
+    ElementPtr expected = Element::createMap();
+    expected->set("prefix", Element::create(string("2001:db8:0:1000::")));
+    expected->set("prefix-len", Element::create(56));
+    EXPECT_TRUE(expected->equals(*pool));
+
+    // Get the pd-pool list and checks the pd-pool is in it.
+    ConstElementPtr pools;
+    EXPECT_NO_THROW(pools = t_obj_->getPdPools(subnet + "/pd-pools"));
+    ASSERT_TRUE(pools);
+    ASSERT_EQ(Element::list, pools->getType());
+    ASSERT_EQ(1, pools->size());
+    EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that one empty pd pool can be properly
+// translated from YANG to JSON using the Kea ad hoc model.
+TEST_F(TranslatorPdPoolsTest, getKea) {
+    useModel("kea-dhcp6-server");
+
+    // Create the subnet 2001:db8::/48 #111.
+    const string& subnet =
+        "/kea-dhcp6-server:config/subnet6/subnet6[id='111']";
+    S_Val v_subnet(new Val("2001:db8::/48", SR_STRING_T));
+    const string& subnet_subnet = subnet + "/subnet";
+    EXPECT_NO_THROW(sess_->set_item(subnet_subnet.c_str(), v_subnet));
+
+    // Create the pd-pool 2001:db8:0:1000::/64.
+    const string& xpath = subnet + "/pd-pools";
+    const string& prefix = "2001:db8:0:1000::/56";
+    ostringstream spool;
+    spool << xpath + "/pd-pool[prefix='" << prefix << "']";
+    const string& x_delegated = spool.str() + "/delegated-len";
+    uint8_t dl = 64;
+    S_Val s_delegated(new Val(dl, SR_UINT8_T));
+    EXPECT_NO_THROW(sess_->set_item(x_delegated.c_str(), s_delegated));
+
+    // Get the pool.
+    ConstElementPtr pool;
+    EXPECT_NO_THROW(pool = t_obj_->getPdPool(spool.str()));
+    ASSERT_TRUE(pool);
+    ElementPtr expected = Element::createMap();
+    expected->set("prefix", Element::create(string("2001:db8:0:1000::")));
+    expected->set("prefix-len", Element::create(56));
+    expected->set("delegated-len", Element::create(64));
+    EXPECT_TRUE(expected->equals(*pool));
+
+    // Get the pd-pool list and checks the pd-pool is in it.
+    ConstElementPtr pools;
+    EXPECT_NO_THROW(pools = t_obj_->getPdPools(xpath));
+    ASSERT_TRUE(pools);
+    ASSERT_EQ(Element::list, pools->getType());
+    ASSERT_EQ(1, pools->size());
+    EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that an empty pd pool list can be properly
+// translated from JSON to YANG using the IETF model.
+TEST_F(TranslatorPdPoolsTest, setEmptyIetf) {
+    useModel("ietf-dhcpv6-server");
+
+    // Create the subnet 2001:db8::/48 #111.
+    const string& subnet =
+        "/ietf-dhcpv6-server:server/server-config/network-ranges"
+        "/network-range[network-range-id='111']";
+    S_Val v_subnet(new Val("2001:db8::/48", SR_STRING_T));
+    const string& subnet_subnet = subnet + "/network-prefix";
+    EXPECT_NO_THROW(sess_->set_item(subnet_subnet.c_str(), v_subnet));
+
+    // Set empty list.
+    const string& xpath = subnet + "/pd-pools";
+    ConstElementPtr pools = Element::createList();
+    EXPECT_NO_THROW(t_obj_->setPdPools(xpath, pools));
+
+    // Get it back.
+    pools.reset();
+    EXPECT_NO_THROW(pools = t_obj_->getPdPools(xpath));
+    ASSERT_TRUE(pools);
+    ASSERT_EQ(Element::list, pools->getType());
+    EXPECT_EQ(0, pools->size());
+}
+
+// This test verifies that an empty pd pool list can be properly
+// translated from JSON to YANG using the Kea ad hoc model.
+TEST_F(TranslatorPdPoolsTest, setEmptyKea) {
+    useModel("kea-dhcp6-server");
+
+    // Create the subnet 2001:db8::/48 #111.
+    const string& subnet =
+        "/kea-dhcp6-server:config/subnet6/subnet6[id='111']";
+    S_Val v_subnet(new Val("2001:db8::/48", SR_STRING_T));
+    const string& subnet_subnet = subnet + "/subnet";
+    EXPECT_NO_THROW(sess_->set_item(subnet_subnet.c_str(), v_subnet));
+
+    // Set empty list.
+    const string& xpath = subnet + "/pd-pools";
+    ConstElementPtr pools = Element::createList();
+    EXPECT_NO_THROW(t_obj_->setPdPools(xpath, pools));
+
+    // Get it back.
+    pools.reset();
+    EXPECT_NO_THROW(pools = t_obj_->getPdPools(xpath));
+    ASSERT_TRUE(pools);
+    ASSERT_EQ(Element::list, pools->getType());
+    EXPECT_EQ(0, pools->size());
+}
+
+// This test verifies that one pd pool can be properly
+// translated from JSON to YANG using the IETF model.
+TEST_F(TranslatorPdPoolsTest, setIetf) {
+    useModel("ietf-dhcpv6-server");
+
+    // Create the subnet 2001:db8::/48 #111.
+    const string& subnet =
+        "/ietf-dhcpv6-server:server/server-config/network-ranges"
+        "/network-range[network-range-id='111']";
+    S_Val v_subnet(new Val("2001:db8::/48", SR_STRING_T));
+    const string& subnet_subnet = subnet + "/network-prefix";
+    EXPECT_NO_THROW(sess_->set_item(subnet_subnet.c_str(), v_subnet));
+
+    // Set one pool.
+    const string& xpath = subnet + "/pd-pools";
+    ElementPtr pools = Element::createList();
+    ElementPtr pool = Element::createMap();
+    pool->set("prefix", Element::create(string("2001:db8:0:1000::")));
+    pool->set("prefix-len", Element::create(56));
+    pools->add(pool);
+    EXPECT_NO_THROW(t_obj_->setPdPools(xpath, pools));
+
+    // Get it back.
+    pools.reset();
+    EXPECT_NO_THROW(pools = t_obj_->getPdPools(xpath));
+    ASSERT_TRUE(pools);
+    ASSERT_EQ(Element::list, pools->getType());
+    ASSERT_EQ(1, pools->size());
+    EXPECT_TRUE(pool->equals(*pools->get(0)));
+
+    // Check the tree representation.
+    S_Tree tree;
+    EXPECT_NO_THROW(tree = sess_->get_subtree("/ietf-dhcpv6-server:server"));
+    ASSERT_TRUE(tree);
+    string expected =
+        "ietf-dhcpv6-server:server (container)\n"
+        " |\n"
+        " -- server-config (container)\n"
+        "     |\n"
+        "     -- network-ranges (container)\n"
+        "         |\n"
+        "         -- network-range (list instance)\n"
+        "             |\n"
+        "             -- network-range-id = 111\n"
+        "             |\n"
+        "             -- network-prefix = 2001:db8::/48\n"
+        "             |\n"
+        "             -- pd-pools (container)\n"
+        "                 |\n"
+        "                 -- pd-pool (list instance)\n"
+        "                     |\n"
+        "                     -- pool-id = 0\n"
+        "                     |\n"
+        "                     -- prefix = 2001:db8:0:1000::/56\n"
+        "                     |\n"
+        "                     -- prefix-length = 56\n"
+        "                     |\n"
+        "                     -- max-pd-space-utilization = disabled\n";
+    EXPECT_EQ(expected, tree->to_string(100));
+}
+
+// This test verifies that one pd pool can be properly
+// translated from JSON to YANG using the kea ad hoc model.
+TEST_F(TranslatorPdPoolsTest, setKea) {
+    useModel("kea-dhcp6-server");
+
+    // Create the subnet 2001:db8::/48 #111.
+    const string& subnet =
+        "/kea-dhcp6-server:config/subnet6/subnet6[id='111']";
+    S_Val v_subnet(new Val("2001:db8::/48", SR_STRING_T));
+    const string& subnet_subnet = subnet + "/subnet";
+    EXPECT_NO_THROW(sess_->set_item(subnet_subnet.c_str(), v_subnet));
+
+    // Set one pool.
+    const string& xpath = subnet + "/pd-pools";
+    ElementPtr pools = Element::createList();
+    ElementPtr pool = Element::createMap();
+    pool->set("prefix", Element::create(string("2001:db8:0:1000::")));
+    pool->set("prefix-len", Element::create(56));
+    pool->set("delegated-len", Element::create(64));
+    pools->add(pool);
+    EXPECT_NO_THROW(t_obj_->setPdPools(xpath, pools));
+
+    // Get it back.
+    pools.reset();
+    EXPECT_NO_THROW(pools = t_obj_->getPdPools(xpath));
+    ASSERT_TRUE(pools);
+    ASSERT_EQ(Element::list, pools->getType());
+    ASSERT_EQ(1, pools->size());
+    EXPECT_TRUE(pool->equals(*pools->get(0)));
+
+    // Check the tree representation.
+    S_Tree tree;
+    EXPECT_NO_THROW(tree = sess_->get_subtree("/kea-dhcp6-server:config"));
+    ASSERT_TRUE(tree);
+    string expected =
+        "kea-dhcp6-server:config (container)\n"
+        " |\n"
+        " -- subnet6 (container)\n"
+        "     |\n"
+        "     -- subnet6 (list instance)\n"
+        "         |\n"
+        "         -- id = 111\n"
+        "         |\n"
+        "         -- subnet = 2001:db8::/48\n"
+        "         |\n"
+        "         -- pd-pools (container)\n"
+        "             |\n"
+        "             -- pd-pool (list instance)\n"
+        "                 |\n"
+        "                 -- prefix = 2001:db8:0:1000::/56\n"
+        "                 |\n"
+        "                 -- delegated-len = 64\n";
+    EXPECT_EQ(expected, tree->to_string(100));
+
+    // Check it validates.
+    EXPECT_NO_THROW(sess_->validate());
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/yang/translator_pd_pool.cc b/src/lib/yang/translator_pd_pool.cc
new file mode 100644 (file)
index 0000000..c700fc4
--- /dev/null
@@ -0,0 +1,365 @@
+// Copyright (C) 2018 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 <yang/adaptor.h>
+#include <yang/translator_pd_pool.h>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace yang {
+
+TranslatorPdPool::TranslatorPdPool(S_Session session, const string& model)
+    : TranslatorBasic(session),
+      TranslatorOptionData(session, model),
+      TranslatorOptionDataList(session, model),
+      model_(model) {
+}
+
+TranslatorPdPool::~TranslatorPdPool() {
+}
+
+ElementPtr
+TranslatorPdPool::getPdPool(const string& xpath) {
+    try {
+        if (model_ == "ietf-dhcpv6-server") {
+            return (getPdPoolIetf6(xpath));
+        } else if (model_ == "kea-dhcp6-server") {
+            return (getPdPoolKea(xpath));
+        }
+    } catch (const sysrepo_exception& ex) {
+        isc_throw(SysrepoError,
+                  "sysrepo error getting pd-pool at '" << xpath
+                  << "': " << ex.what());
+    }
+    isc_throw(NotImplemented,
+              "getPdPool not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorPdPool::getPdPoolIetf6(const string& xpath) {
+    ElementPtr result = Element::createMap();
+    ConstElementPtr pref = getItem(xpath + "/prefix");
+    if (!pref) {
+        isc_throw(BadValue, "getPdPoolIetf6: prefix is required");
+    }
+    const string& prefix = pref->stringValue();
+    size_t slash = prefix.find("/");
+    if (slash == string::npos) {
+        isc_throw(BadValue,
+                  "getPdPoolIetf6: no '/' in prefix '" << prefix << "'");
+    }
+    const string& address = prefix.substr(0, slash);
+    if (address.empty()) {
+        isc_throw(BadValue,
+                  "getPdPoolIetf6: malformed prefix '" << prefix << "'");
+    }
+    result->set("prefix", Element::create(address));
+    // Silly: the prefix length is specified twice...
+    ConstElementPtr preflen = getItem(xpath + "/prefix-length");
+    if (!preflen) {
+        isc_throw(BadValue, "getPdPoolIetf6: prefix length is required");
+    }
+    result->set("prefix-len", preflen);
+    ConstElementPtr valid_lifetime = getItem(xpath + "/valid-lifetime");
+    if (valid_lifetime) {
+        result->set("valid-lifetime", valid_lifetime);
+    }
+    ConstElementPtr preferred_lifetime =
+        getItem(xpath + "/preferred-lifetime");
+    if (preferred_lifetime) {
+        result->set("preferred-lifetime", preferred_lifetime);
+    }
+    ConstElementPtr renew_time = getItem(xpath + "/renew-time");
+    if (renew_time) {
+        result->set("renew-timer", renew_time);
+    }
+    ConstElementPtr rebind_time = getItem(xpath + "/rebind-time");
+    if (rebind_time) {
+        result->set("rebind-timer", rebind_time);
+    }
+    // Skip rapid-commit.
+    ConstElementPtr guard = getItem(xpath + "/client-class");
+    if (guard) {
+        result->set("client-class", guard);
+    }
+    // no require-client-classes nor user-context.
+    // Skip max-pd-space-utilization.
+    // @todo option-data.
+    return (result);
+}
+
+ElementPtr
+TranslatorPdPool::getPdPoolKea(const string& xpath) {
+    ElementPtr result = Element::createMap();
+    ConstElementPtr pref = getItem(xpath + "/prefix");
+    if (!pref) {
+        isc_throw(BadValue, "getPdPoolKea: prefix is required");
+    }
+    const string& prefix = pref->stringValue();
+    size_t slash = prefix.find("/");
+    if (slash == string::npos) {
+        isc_throw(BadValue,
+                  "getPdPoolKea: no '/' in prefix '" << prefix << "'");
+    }
+    const string& address = prefix.substr(0, slash);
+    const string& length = prefix.substr(slash + 1, string::npos);
+    if (address.empty() || length.empty()) {
+        isc_throw(BadValue,
+                  "getPdPoolKea: malformed prefix '" << prefix << "'");
+    }
+    result->set("prefix", Element::create(address));
+    try {
+        unsigned len = boost::lexical_cast<unsigned>(length);
+        result->set("prefix-len", Element::create(static_cast<int>(len)));
+    } catch (const boost::bad_lexical_cast&) {
+        isc_throw(BadValue,
+                  "getPdPoolKea: bad prefix length in '" << prefix << "'");
+    }
+
+    ConstElementPtr xpref = getItem(xpath + "/excluded-prefix");
+    if (xpref) {
+        const string& xprefix = xpref->stringValue();
+        size_t xslash = xprefix.find("/");
+        if (xslash == string::npos) {
+            isc_throw(BadValue,
+                      "getPdPoolKea: no '/' in excluded prefix '"
+                      << xprefix << "'");
+        }
+        const string& xaddress = xprefix.substr(0, xslash);
+        const string& xlength = xprefix.substr(xslash + 1, string::npos);
+        if (xaddress.empty() || xlength.empty()) {
+            isc_throw(BadValue,
+                      "getPdPoolKea: malformed excluded prefix '"
+                      << xprefix << "'");
+        }
+        result->set("excluded-prefix", Element::create(xaddress));
+        try {
+            unsigned xlen = boost::lexical_cast<unsigned>(xlength);
+            result->set("excluded-prefix-len",
+                        Element::create(static_cast<int>(xlen)));
+        } catch (const boost::bad_lexical_cast&) {
+            isc_throw(BadValue,
+                      "getPdPoolKea: bad excluded prefix length in '"
+                      << xprefix << "'");
+        }
+    }
+
+    ConstElementPtr delegated = getItem(xpath + "/delegated-len");
+    if (delegated) {
+        result->set("delegated-len", delegated);
+    }
+    ConstElementPtr options = getOptionDataList(xpath + "/option-data-list");
+    if (options && (options->size() > 0)) {
+        result->set("option-data", options);
+    }
+    ConstElementPtr guard = getItem(xpath + "/client-class");
+    if (guard) {
+        result->set("client-class", guard);
+    }
+    ConstElementPtr required = getItems(xpath + "/require-client-classes");
+    if (required && (required->size() > 0)) {
+        result->set("require-client-classes", required);
+    }
+    ConstElementPtr context = getItem(xpath + "/user-context");
+    if (context) {
+        result->set("user-context", Element::fromJSON(context->stringValue()));
+    }
+    return (result);
+}
+
+void
+TranslatorPdPool::setPdPool(const string& xpath, ConstElementPtr elem) {
+    try {
+        if (model_ == "ietf-dhcpv6-server") {
+            setPdPoolIetf6(xpath, elem);
+        } else if (model_ == "kea-dhcp6-server") {
+            setPdPoolKea(xpath, elem);
+        } else {
+            isc_throw(NotImplemented,
+                      "setPdPool not implemented for the model: " << model_);
+        }
+    } catch (const sysrepo_exception& ex) {
+        isc_throw(SysrepoError,
+                  "sysrepo error setting pd-pool '" << elem->str()
+                  << "' at '" << xpath << "': " << ex.what());
+    }
+}
+
+void
+TranslatorPdPool::setPdPoolIetf6(const string& xpath, ConstElementPtr elem) {
+    ConstElementPtr base = elem->get("prefix");
+    ConstElementPtr length = elem->get("prefix-len");
+    if (!base || !length) {
+        isc_throw(BadValue,
+                  "setPdPoolIetf6 requires prefix and prefix length: "
+                  << elem->str());
+    }
+    ostringstream prefix;
+    prefix << base->stringValue() << "/" << length->intValue();
+    setItem(xpath + "/prefix", Element::create(prefix.str()), SR_STRING_T);
+    setItem(xpath + "/prefix-length", length, SR_UINT8_T);
+    ConstElementPtr valid_lifetime = elem->get("valid-lifetime");
+    if (valid_lifetime) {
+        setItem(xpath + "/valid-lifetime", valid_lifetime, SR_UINT32_T);
+    }
+    ConstElementPtr preferred_lifetime = elem->get("preferred-lifetime");
+    if (preferred_lifetime) {
+        setItem(xpath + "/preferred-lifetime",
+                preferred_lifetime, SR_UINT32_T);
+    }
+    ConstElementPtr renew_timer = elem->get("renew-timer");
+    if (renew_timer) {
+        setItem(xpath + "/renew-time", renew_timer, SR_UINT32_T);
+    }
+    ConstElementPtr rebind_timer = elem->get("rebind-timer");
+    if (rebind_timer) {
+        setItem(xpath + "/rebind-time", rebind_timer, SR_UINT32_T);
+    }
+    // Skip rapid-commit.
+    ConstElementPtr guard = elem->get("client-class");
+    if (guard) {
+        setItem(xpath + "/client-class", guard, SR_STRING_T);
+    }
+    // Set max pd space utilization to disabled.
+    setItem(xpath + "/max-pd-space-utilization",
+            Element::create(string("disabled")),
+            SR_ENUM_T);
+    // @todo option-data.
+}
+
+void
+TranslatorPdPool::setPdPoolKea(const string& xpath, ConstElementPtr elem) {
+    // Skip prefix as it is the key.
+    bool created = false;
+    ConstElementPtr delegated = elem->get("delegated-len");
+    if (delegated) {
+        setItem(xpath + "/delegated-len", delegated, SR_UINT8_T);
+    }
+    ConstElementPtr xprefix = elem->get("excluded-prefix");
+    ConstElementPtr xlen = elem->get("excluded-prefix-len");
+    if (xprefix && xlen) {
+        ostringstream xpref;
+        xpref << xprefix->stringValue() << "/" << xlen->intValue();
+        setItem(xpath + "/excluded-prefix", Element::create(xpref.str()),
+                SR_STRING_T);
+        created = true;
+    }
+    ConstElementPtr options = elem->get("option-data");
+    if (options && (options->size() > 0)) {
+        setOptionDataList(xpath + "/option-data-list", options);
+        created = true;
+    }
+    ConstElementPtr guard = elem->get("client-class");
+    if (guard) {
+        setItem(xpath + "/client-class", guard, SR_STRING_T);
+        created = true;
+    }
+    ConstElementPtr required = elem->get("require-client-classes");
+    if (required && (required->size() > 0)) {
+        for (ConstElementPtr rclass : required->listValue()) {
+            setItem(xpath + "/require-client-classes", rclass, SR_STRING_T);
+            created = true;
+        }
+    }
+    ConstElementPtr context = Adaptor::getContext(elem);
+    if (context) {
+        setItem(xpath + "/user-context", Element::create(context->str()),
+                SR_STRING_T);
+        created = true;
+    }
+    // There is no mandatory fields outside the keys so force creation.
+    if (!created) {
+        ConstElementPtr list = Element::createList();
+        setItem(xpath, list, SR_LIST_T);
+    }
+}
+
+TranslatorPdPools::TranslatorPdPools(S_Session session, const string& model)
+    : TranslatorBasic(session),
+      TranslatorOptionData(session, model),
+      TranslatorOptionDataList(session, model),
+      TranslatorPdPool(session, model),
+      model_(model) {
+}
+
+TranslatorPdPools::~TranslatorPdPools() {
+}
+
+ElementPtr
+TranslatorPdPools::getPdPools(const string& xpath) {
+    try {
+        ElementPtr result = Element::createList();
+        S_Iter_Value iter = getIter(xpath + "/*");
+        if (!iter) {
+            // Can't happen.
+            isc_throw(Unexpected, "getPdPools: can't get iterator: " << xpath);
+        }
+        for (;;) {
+            const string& pool = getNext(iter);
+            if (pool.empty()) {
+                break;
+            }
+            result->add(getPdPool(pool));
+        }
+        return (result);
+    } catch (const sysrepo_exception& ex) {
+        isc_throw(SysrepoError,
+                  "sysrepo error getting pd-pools at '" << xpath
+                  << "': " << ex.what());
+    }
+}
+
+void
+TranslatorPdPools::setPdPools(const string& xpath, ConstElementPtr elem) {
+    try {
+        if (model_ == "ietf-dhcpv6-server") {
+            setPdPoolsId(xpath, elem);
+        } else if (model_ == "kea-dhcp6-server") {
+            setPdPoolsPrefix(xpath, elem);
+        } else {
+            isc_throw(NotImplemented,
+                      "setPdPools not implemented for the model: " << model_);
+        }
+    } catch (const sysrepo_exception& ex) {
+        isc_throw(SysrepoError,
+                  "sysrepo error setting pools '" << elem->str()
+                  << "' at '" << xpath << "': " << ex.what());
+    }
+}
+
+void
+TranslatorPdPools::setPdPoolsId(const string& xpath, ConstElementPtr elem) {
+    for (size_t i = 0; i < elem->size(); ++i) {
+        ConstElementPtr pool = elem->get(i);
+        ostringstream prefix;
+        prefix << xpath << "/pd-pool[pool-id='" << i << "']";
+        setPdPool(prefix.str(), pool);
+    }
+}
+
+void
+TranslatorPdPools::setPdPoolsPrefix(const string& xpath,
+                                    ConstElementPtr elem) {
+    for (size_t i = 0; i < elem->size(); ++i) {
+        ConstElementPtr pool = elem->get(i);
+        if (!pool->contains("prefix") || !pool->contains("prefix-len")) {
+            isc_throw(BadValue, "pd-pool requires prefix and prefix length: "
+                      << pool->str());
+        }
+        ostringstream prefix;
+        prefix << xpath << "/pd-pool[prefix='"
+               << pool->get("prefix")->stringValue() << "/"
+               << pool->get("prefix-len")->intValue() << "']";
+        setPdPool(prefix.str(), pool);
+    }
+}
+
+}; // end of namespace isc::yang
+}; // end of namespace isc
diff --git a/src/lib/yang/translator_pd_pool.h b/src/lib/yang/translator_pd_pool.h
new file mode 100644 (file)
index 0000000..8a0674e
--- /dev/null
@@ -0,0 +1,227 @@
+// Copyright (C) 2018 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 ISC_TRANSLATOR_PD_POOL_H
+#define ISC_TRANSLATOR_PD_POOL_H 1
+
+#include <yang/translator_option_data.h>
+#include <list>
+
+namespace isc {
+namespace yang {
+
+// Prefix delegation pool translation between YANG and JSON
+//
+// JSON syntax for both kea-dhcp4 and kea-dhcp6 is:
+// @code
+// {
+//     "prefix": <prefix base>,
+//     "prefix-len": <prefix length>,
+//     "delegated-len": <delegated length>,
+//     "excluded-prefix": <excluded prefix>,
+//     "excluded-prefix-len": <excluded prefix length>,
+//     "option-data": [ <list of option data> ],
+//     "client-class": "<guard class name>",
+//     "require-client-classes": [ <list of required class names> ],
+//     "user-context": { <json map> },
+//     "comment": "<comment>"
+// }
+// @endcode
+//
+// YANG syntax for ietf-dhcpv6-server is with pool-id as the key.
+// @code
+//  +--rw pool-id                     uint32
+//  +--rw prefix                      inet:ipv6-prefix
+//  +--rw prefix-length               uint8
+//  +--rw valid-lifetime              yang:timeticks
+//  +--rw renew-time                  yang:timeticks
+//  +--rw rebind-time                 yang:timeticks
+//  +--rw preferred-lifetime          yang:timeticks
+//  +--rw rapid-commit?               boolean
+//  +--rw client-class?               string
+//  +--rw max-pd-space-utilization?   threshold
+//  +--rw option-set-id?
+//     /server/server-config/option-sets/option-set/option-set-id
+// @endcode
+//
+// YANG syntax for kea-dhcp6 is with prefix as the key.
+// @code
+//  +--rw prefix?                  inet:ipv6-prefix
+//  +--rw delegated-len?           uint8
+//  +--rw excluded-prefix?         inet:ipv6-prefix
+//  +--rw option-data-list         option-data*
+//  +--rw client-class?            string
+//  +--rw require-client-classes*  string
+//  +--rw user-context?            string
+// @endcode
+//
+// An example in JSON and YANG formats:
+// @code
+// [
+//     {
+//         "prefix": "2001:db8:0:1000::",
+//         "prefix-len": 56,
+//         "delegated-len": 64
+//     }
+// ]
+// @endcode
+// @code
+//  /ietf-dhcpv6-server:server (container)
+//  /ietf-dhcpv6-server:server/server-config (container)
+//  /ietf-dhcpv6-server:server/server-config/network-ranges (container)
+//  /ietf-dhcpv6-server:server/server-config/network-ranges
+//     network-range[network-range-id='111'] (list instance)
+//  /ietf-dhcpv6-server:server/server-config/network-ranges
+//     network-range[network-range-id='111']/network-range-id = 111
+//  /ietf-dhcpv6-server:server/server-config/network-ranges
+//     network-range[network-range-id='111']/network-prefix = 2001:db8::/48
+//  /ietf-dhcpv6-server:server/server-config/network-ranges
+//     network-range[network-range-id='111']/pd-pools (container)
+//  /ietf-dhcpv6-server:server/server-config/network-ranges
+//     network-range[network-range-id='111']/pd-pools/
+//     pd-pool[pool-id='0'] (list instance)
+//  /ietf-dhcpv6-server:server/server-config/network-ranges/
+//     pd-pool[pool-id='0']/pool-id = 0
+//     network-range[network-range-id='111']/pd-pools/
+//     pd-pool[pool-id='0']/prefix = 2001:db8:0:1000::/56
+//  /ietf-dhcpv6-server:server/server-config/network-ranges
+//     network-range[network-range-id='111']/pd-pools/
+//     pd-pool[pool-id='0']/prefix-length = 56
+//  /ietf-dhcpv6-server:server/server-config/network-ranges
+//     network-range[network-range-id='111']/pd-pools/
+//     pd-pool[pool-id='0']/max-pd-space-utilization = disabled
+// @endcode
+// @code
+//  /kea-dhcp6-server:config (container)
+//  /kea-dhcp6-server:config/subnet6 (container)
+//  /kea-dhcp6-server:config/subnet6/subnet6[id='111'] (list instance)
+//  /kea-dhcp6-server:config/subnet6/subnet6[id='111']/id = 111
+//  /kea-dhcp6-server:config/subnet6/subnet6[id='111']/subnet = 2001:db8::/48
+//  /kea-dhcp6-server:config/subnet6/subnet6[id='111']/pd-pools (container)
+//  /kea-dhcp6-server:config/subnet6/subnet6[id='111']/pd-pools/
+//     pd-pool[prefix='2001:db8:0:1000::/56' (list instance)
+//  /kea-dhcp6-server:config/subnet6/subnet6[id='111']/pd-pools/
+//     pd-pool[prefix='2001:db8:0:1000::/56'/prefix = 2001:db8:0:1000::/56
+//  /kea-dhcp6-server:config/subnet6/subnet6[id='111']/pd-pools/
+//     pd-pool[prefix='2001:db8:0:1000::/56'/delegated-len = 64
+// @endcode
+
+// @brief A translator class for converting a pd-pool between
+// YANG and JSON.
+//
+// Currently supports on kea-dhcp[46]-server and partially ietf-dhcpv6-server.
+class TranslatorPdPool : virtual public TranslatorOptionDataList {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param session Sysrepo session.
+    /// @param model Model name.
+    TranslatorPdPool(S_Session session, const std::string& model);
+
+    /// @brief Destructor.
+    virtual ~TranslatorPdPool();
+
+    /// @brief Get and translate a pd-pool from YANG to JSON.
+    ///
+    /// @param xpath The xpath of the pd-pool.
+    /// @return JSON representation of the pd-pool.
+    /// @throw SysrepoError when sysrepo raises an error.
+    /// @throw BadValue on pd-pool without well formed prefix.
+    isc::data::ElementPtr getPdPool(const std::string& xpath);
+
+    /// @brief Translate and set pd-pool from JSON to YANG.
+    ///
+    /// @param xpath The xpath of the pd-pool.
+    /// @param elem The JSON element.
+    void setPdPool(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+    /// @brief getPdPool for ietf-dhcpv6-server.
+    ///
+    /// @param xpath The xpath of the pd-pool.
+    /// @return JSON representation of the pd-pool.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getPdPoolIetf6(const std::string& xpath);
+
+    /// @brief setPdPool for ietf-dhcpv6-server.
+    ///
+    /// @param xpath The xpath of the pd-pool.
+    /// @param elem The JSON element.
+    /// @throw BadValue on pd-pool without prefix or prefix length.
+    void setPdPoolIetf6(const std::string& xpath,
+                        isc::data::ConstElementPtr elem);
+
+    /// @brief getPdPool for kea-dhcp6.
+    ///
+    /// @param xpath The xpath of the pd-pool.
+    /// @return JSON representation of the pd-pool.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getPdPoolKea(const std::string& xpath);
+
+    /// @brief setPdPool for kea-dhcp6.
+    ///
+    /// @param xpath The xpath of the pd-pool.
+    /// @param elem The JSON element.
+    void setPdPoolKea(const std::string& xpath,
+                      isc::data::ConstElementPtr elem);
+
+    /// @brief The model.
+    std::string model_;
+};
+
+// @brief A translator class for converting a pd-pool list between
+// YANG and JSON.
+//
+// Currently supports on kea-dhcp[46]-server and partially ietf-dhcpv6-server.
+class TranslatorPdPools : virtual public TranslatorPdPool {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param session Sysrepo session.
+    /// @param model Model name.
+    TranslatorPdPools(S_Session session, const std::string& model);
+
+    /// @brief Destructor.
+    virtual ~TranslatorPdPools();
+
+    /// @brief Get and translate pd-pools from YANG to JSON.
+    ///
+    /// @param xpath The xpath of the pd-pool list.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getPdPools(const std::string& xpath);
+
+    /// @brief Translate and set pd-pools from JSON to YANG.
+    ///
+    /// @param xpath The xpath of the pd-pool list.
+    /// @param elem The JSON element.
+    void setPdPools(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+    /// @brief setPdPools using pool-id.
+    ///
+    /// @param xpath The xpath of the pd-pool list.
+    /// @param elem The JSON element.
+    void setPdPoolsId(const std::string& xpath,
+                      isc::data::ConstElementPtr elem);
+
+    /// @brief setPdPools using prefix.
+    ///
+    /// @param xpath The xpath of the pd-pool list.
+    /// @param elem The JSON element.
+    /// @throw BadValue on pd-pool without prefix or prefix length.
+    void setPdPoolsPrefix(const std::string& xpath,
+                          isc::data::ConstElementPtr elem);
+
+    /// @brief The model.
+    std::string model_;
+};
+
+}; // end of namespace isc::yang
+}; // end of namespace isc
+
+#endif // ISC_TRANSLATOR_PD_POOL_H