From: Francis Dupont Date: Tue, 25 Sep 2018 17:11:14 +0000 (+0200) Subject: [65-libyang-pd-pool] Brought pd-pool code from kea-yang X-Git-Tag: 153-netconf-agent_base~1^2~3^2^2^2^2^2~1^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=26d8cb065b8a2ba1c121a6b79b97019bc2548091;p=thirdparty%2Fkea.git [65-libyang-pd-pool] Brought pd-pool code from kea-yang --- diff --git a/src/lib/yang/Makefile.am b/src/lib/yang/Makefile.am index 4761a7127d..699375538c 100644 --- a/src/lib/yang/Makefile.am +++ b/src/lib/yang/Makefile.am @@ -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. diff --git a/src/lib/yang/tests/Makefile.am b/src/lib/yang/tests/Makefile.am index 5e593f1d19..6831bceb45 100644 --- a/src/lib/yang/tests/Makefile.am +++ b/src/lib/yang/tests/Makefile.am @@ -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 index 0000000000..7869bc06d9 --- /dev/null +++ b/src/lib/yang/tests/translator_pd_pool_unittests.cc @@ -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 + +#include +#include + +#include +#include + +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 { +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 index 0000000000..c700fc4e22 --- /dev/null +++ b/src/lib/yang/translator_pd_pool.cc @@ -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 +#include +#include +#include + +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(length); + result->set("prefix-len", Element::create(static_cast(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(xlength); + result->set("excluded-prefix-len", + Element::create(static_cast(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 index 0000000000..8a0674ef9a --- /dev/null +++ b/src/lib/yang/translator_pd_pool.h @@ -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 +#include + +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-len": , +// "delegated-len": , +// "excluded-prefix": , +// "excluded-prefix-len": , +// "option-data": [ ], +// "client-class": "", +// "require-client-classes": [ ], +// "user-context": { }, +// "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