From: Francis Dupont Date: Tue, 25 Sep 2018 17:08:19 +0000 (+0200) Subject: [65-libyang-pool] Brought pool code from kea-yang X-Git-Tag: 153-netconf-control-socket_base~2^2~4^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f53c587d5717d6762c5a4471a0441a7675e1c80d;p=thirdparty%2Fkea.git [65-libyang-pool] Brought pool code from kea-yang --- diff --git a/src/lib/yang/Makefile.am b/src/lib/yang/Makefile.am index 4761a7127d..44bdcfd5ae 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_pool.cc translator_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_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..18f855b078 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_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_pool_unittests.cc b/src/lib/yang/tests/translator_pool_unittests.cc new file mode 100644 index 0000000000..29b4c7ef75 --- /dev/null +++ b/src/lib/yang/tests/translator_pool_unittests.cc @@ -0,0 +1,314 @@ +// 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 pool_list[] = "pool list"; + +/// @brief Test fixture class for @ref TranslatorPools. +class TranslatorPoolsTest : + public GenericTranslatorTest { +public: + + /// Constructor. + TranslatorPoolsTest() { } + + /// Destructor (does nothing). + virtual ~TranslatorPoolsTest() { } +}; + +// This test verifies that an empty pool list can be properly +// translated from YANG to JSON using IETF model. +TEST_F(TranslatorPoolsTest, getEmptyIetf) { + useModel("ietf-dhcpv6-server"); + + // Get the pool list and checks it is empty. + const string& xpath = "/ietf-dhcpv6-server:server/server-config/" + "network-ranges/network-range[network-range-id='111']/address-pools"; + ConstElementPtr pools; + EXPECT_NO_THROW(pools = t_obj_->getPools(xpath)); + ASSERT_TRUE(pools); + ASSERT_EQ(Element::list, pools->getType()); + EXPECT_EQ(0, pools->size()); +} + +// This test verifies that an empty pool list can be properly +// translated from YANG to JSON using Kea ad hoc model. +TEST_F(TranslatorPoolsTest, getEmptyKea) { + useModel("kea-dhcp6-server"); + + // Get the pool list and checks it is empty. + const string& xpath = + "/kea-dhcp6-server:config/subnet6/subnet6[id='111']/pools"; + ConstElementPtr pools; + EXPECT_NO_THROW(pools = t_obj_->getPools(xpath)); + ASSERT_TRUE(pools); + ASSERT_EQ(Element::list, pools->getType()); + EXPECT_EQ(0, pools->size()); +} + +// This test verifies that one pool can be properly +// translated from YANG to JSON using IETF model. +TEST_F(TranslatorPoolsTest, 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 pool 2001:db8::1:0/112 #222. + const string& xpath = subnet + "/address-pools"; + const string& prefix = xpath + "/address-pool[pool-id='222']/pool-prefix"; + S_Val s_val(new Val("2001:db8::1:0/112")); + EXPECT_NO_THROW(sess_->set_item(prefix.c_str(), s_val)); + + // Get the pool. + ConstElementPtr pool; + EXPECT_NO_THROW(pool = t_obj_->getPool(xpath + "/address-pool[pool-id='222']")); + ASSERT_TRUE(pool); + EXPECT_EQ("{ \"pool\": \"2001:db8::1:0/112\" }", pool->str()); + + // Get the pool list and checks the pool is in it. + ConstElementPtr pools; + EXPECT_NO_THROW(pools = t_obj_->getPools(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 one pool can be properly +// translated from YANG to JSON using Kea ad hoc model. +TEST_F(TranslatorPoolsTest, 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 pool 2001:db8::1:0/112. + const string& xpath = subnet + "/pools"; + const string& prefix = "2001:db8::1:0/112"; + string start_addr; + string end_addr; + ASSERT_NO_THROW(TranslatorPool::getAddresses(prefix, + start_addr, end_addr)); + EXPECT_EQ("2001:db8::1:0", start_addr); + EXPECT_EQ("2001:db8::1:ffff", end_addr); + ostringstream spool; + spool << xpath + "/pool[start-address='" << start_addr + << "'][end-address='" << end_addr << "']"; + const string& x_prefix = spool.str() + "/prefix"; + S_Val s_prefix(new Val("2001:db8::1:0/112", SR_STRING_T)); + EXPECT_NO_THROW(sess_->set_item(x_prefix.c_str(), s_prefix)); + + // Get the pool. + ConstElementPtr pool; + EXPECT_NO_THROW(pool = t_obj_->getPool(spool.str())); + ASSERT_TRUE(pool); + ElementPtr expected = Element::createMap(); + expected->set("pool", Element::create(string("2001:db8::1:0/112"))); + EXPECT_TRUE(expected->equals(*pool)); + + // Get the pool list and checks the pool is in it. + ConstElementPtr pools; + EXPECT_NO_THROW(pools = t_obj_->getPools(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 pool list can be properly +// translated from JSON to YANG using IETF model. +TEST_F(TranslatorPoolsTest, 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 + "/address-pools"; + ConstElementPtr pools = Element::createList(); + EXPECT_NO_THROW(t_obj_->setPools(xpath, pools)); + + // Get it back. + pools.reset(); + EXPECT_NO_THROW(pools = t_obj_->getPools(xpath)); + ASSERT_TRUE(pools); + ASSERT_EQ(Element::list, pools->getType()); + EXPECT_EQ(0, pools->size()); +} + +// This test verifies that an empty pool list can be properly +// translated from JSON to YANG using Kea ad hoc model. +TEST_F(TranslatorPoolsTest, 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 + "/pools"; + ConstElementPtr pools = Element::createList(); + EXPECT_NO_THROW(t_obj_->setPools(xpath, pools)); + + // Get it back. + pools.reset(); + EXPECT_NO_THROW(pools = t_obj_->getPools(xpath)); + ASSERT_TRUE(pools); + ASSERT_EQ(Element::list, pools->getType()); + EXPECT_EQ(0, pools->size()); +} + +// This test verifies that one pool can be properly +// translated from JSON to YANG using IETF model. +TEST_F(TranslatorPoolsTest, 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 + "/address-pools"; + ElementPtr pools = Element::createList(); + ElementPtr pool = Element::createMap(); + pool->set("pool", Element::create(string("2001:db8::1:0/112"))); + pools->add(pool); + EXPECT_NO_THROW(t_obj_->setPools(xpath, pools)); + + // Get it back. + pools.reset(); + EXPECT_NO_THROW(pools = t_obj_->getPools(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" + " -- address-pools (container)\n" + " |\n" + " -- address-pool (list instance)\n" + " |\n" + " -- pool-id = 0\n" + " |\n" + " -- pool-prefix = 2001:db8::1:0/112\n" + " |\n" + " -- start-address = 2001:db8::1:0\n" + " |\n" + " -- end-address = 2001:db8::1:ffff\n" + " |\n" + " -- max-address-count = disabled\n"; + EXPECT_EQ(expected, tree->to_string(100)); +} + +// This test verifies that one pool can be properly +// translated from JSON to YANG using Kea ad hoc model. +TEST_F(TranslatorPoolsTest, 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 + "/pools"; + ElementPtr pools = Element::createList(); + ElementPtr pool = Element::createMap(); + pool->set("pool", + Element::create(string("2001:db8::1 - 2001:db8::100"))); + pools->add(pool); + EXPECT_NO_THROW(t_obj_->setPools(xpath, pools)); + + // Get it back. + pools.reset(); + EXPECT_NO_THROW(pools = t_obj_->getPools(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" + " -- pools (container)\n" + " |\n" + " -- pool (list instance)\n" + " |\n" + " -- start-address = 2001:db8::1\n" + " |\n" + " -- end-address = 2001:db8::100\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_pool.cc b/src/lib/yang/translator_pool.cc new file mode 100644 index 0000000000..4d55f53b4b --- /dev/null +++ b/src/lib/yang/translator_pool.cc @@ -0,0 +1,349 @@ +// 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 +#include + +using namespace std; +using namespace isc::data; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace isc { +namespace yang { + +TranslatorPool::TranslatorPool(S_Session session, const string& model) + : TranslatorBasic(session), + TranslatorOptionData(session, model), + TranslatorOptionDataList(session, model), + model_(model) { +} + +TranslatorPool::~TranslatorPool() { +} + +ElementPtr +TranslatorPool::getPool(const string& xpath) { + try { + if (model_ == "ietf-dhcpv6-server") { + return (getPoolIetf6(xpath)); + } else if ((model_ == "kea-dhcp4-server") || + (model_ == "kea-dhcp6-server")) { + return (getPoolKea(xpath)); + } + } catch (const sysrepo_exception& ex) { + isc_throw(SysrepoError, + "sysrepo error getting pool at '" << xpath + << "': " << ex.what()); + } + isc_throw(NotImplemented, + "getPool not implemented for the model: " << model_); +} + +ElementPtr +TranslatorPool::getPoolIetf6(const string& xpath) { + ElementPtr result = Element::createMap(); + // Skip pool-id which exists but is not used. + ConstElementPtr pool = getItem(xpath + "/pool-prefix"); + if (!pool) { + isc_throw(BadValue, "getPoolIetf6 requires pool prefix"); + } + result->set("pool", pool); + // Ignore start-address - end-address as prefix form is mandatory? + ConstElementPtr guard = getItem(xpath + "/client-class"); + if (guard) { + result->set("client-class", guard); + } + 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 max-addr-count + // @todo: option-data + /// no require-client-classes nor user-context. + // Skip rapid-commit. + return (result); +} + +ElementPtr +TranslatorPool::getPoolKea(const string& xpath) { + ElementPtr result = Element::createMap(); + ConstElementPtr prefix = getItem(xpath + "/prefix"); + if (prefix) { + result->set("pool", prefix); + } else { + ConstElementPtr start_addr = getItem(xpath + "/start-address"); + ConstElementPtr end_addr = getItem(xpath + "/end-address"); + if (!start_addr || !end_addr) { + isc_throw(BadValue, "getPoolKea requires either prefix or " + "both start and end addresses"); + } + ostringstream range; + range << start_addr->stringValue() << " - " + << end_addr->stringValue(); + result->set("pool", Element::create(range.str())); + } + 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 +TranslatorPool::setPool(const string& xpath, ConstElementPtr elem) { + try { + if (model_ == "ietf-dhcpv6-server") { + setPoolIetf6(xpath, elem); + } else if ((model_ == "kea-dhcp4-server") || + (model_ == "kea-dhcp6-server")) { + setPoolKea(xpath, elem); + } else { + isc_throw(NotImplemented, + "setPool not implemented for the model: " << model_); + } + } catch (const sysrepo_exception& ex) { + isc_throw(SysrepoError, + "sysrepo error setting pool '" << elem->str() + << "' at '" << xpath << "': " << ex.what()); + } +} + +void +TranslatorPool::setPoolIetf6(const string& xpath, ConstElementPtr elem) { + ConstElementPtr pool = elem->get("pool"); + if (!pool) { + isc_throw(BadValue, "setPoolIetf6 requires pool: " << elem->str()); + } + string prefix = pool->stringValue(); + if (prefix.find("/") == string::npos) { + isc_throw(BadValue, + "setPoolIetf only supports pools in prefix (vs range) " + "format and was called with '" << prefix << "'"); + } + setItem(xpath + "/pool-prefix", pool, SR_STRING_T); + string addr = prefix.substr(0, prefix.find_first_of(" /")); + uint8_t plen = boost::lexical_cast + (prefix.substr(prefix.find_last_of(" /") + 1, string::npos)); + const IOAddress& base(addr); + setItem(xpath + "/start-address", + Element::create(firstAddrInPrefix(base, plen).toText()), + SR_STRING_T); + setItem(xpath + "/end-address", + Element::create(lastAddrInPrefix(base, plen).toText()), + SR_STRING_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); + } + // skip max-addr-count + // @todo option-data + // Set max address count to disabled. + setItem(xpath + "/max-address-count", + Element::create(string("disabled")), + SR_ENUM_T); +} + +void +TranslatorPool::setPoolKea(const string& xpath, ConstElementPtr elem) { + ConstElementPtr pool = elem->get("pool"); + if (!pool) { + isc_throw(BadValue, "setPoolKea requires pool: " << elem->str()); + } + bool created = false; + string prefix = pool->stringValue(); + string start_addr; + string end_addr; + getAddresses(prefix, start_addr, end_addr); + if (prefix.find("/") != string::npos) { + setItem(xpath + "/prefix", pool, SR_STRING_T); + created = true; + } + // Skip start-address and end-address as are the keys. + 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); + } +} + +void +TranslatorPool::getAddresses(const string& prefix, + string& start_address, string& end_address) { + size_t slash = prefix.find("/"); + if (slash != string::npos) { + string addr = prefix.substr(0, prefix.find_first_of(" /")); + uint8_t plen = boost::lexical_cast + (prefix.substr(prefix.find_last_of(" /") + 1, string::npos)); + start_address = firstAddrInPrefix(IOAddress(addr), plen).toText(); + end_address = lastAddrInPrefix(IOAddress(addr), plen).toText(); + return; + } + size_t dash = prefix.find("-"); + if (dash == string::npos) { + isc_throw(BadValue, + "getAddresses called with invalid prefix: " << prefix); + } + start_address = prefix.substr(0, prefix.find_first_of(" -")); + end_address = prefix.substr(prefix.find_last_of(" -") + 1, string::npos); +} + +TranslatorPools::TranslatorPools(S_Session session, const string& model) + : TranslatorBasic(session), + TranslatorOptionData(session, model), + TranslatorOptionDataList(session, model), + TranslatorPool(session, model), + model_(model) { +} + +TranslatorPools::~TranslatorPools() { +} + +ElementPtr +TranslatorPools::getPools(const string& xpath) { + try { + ElementPtr result = Element::createList(); + S_Iter_Value iter = getIter(xpath + "/*"); + if (!iter) { + // Can't happen. + isc_throw(Unexpected, "getPools can't get iterator: " << xpath); + } + for (;;) { + const string& pool = getNext(iter); + if (pool.empty()) { + break; + } + result->add(getPool(pool)); + } + return (result); + } catch (const sysrepo_exception& ex) { + isc_throw(SysrepoError, + "sysrepo error getting pools at '" << xpath + << "': " << ex.what()); + } +} + +void +TranslatorPools::setPools(const string& xpath, ConstElementPtr elem) { + try { + if (model_ == "ietf-dhcpv6-server") { + setPoolsbyId(xpath, elem); + } else if ((model_ == "kea-dhcp4-server") || + (model_ == "kea-dhcp6-server")) { + setPoolsbyAddresses(xpath, elem); + } else { + isc_throw(NotImplemented, + "setPools 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 +TranslatorPools::setPoolsbyId(const string& xpath, ConstElementPtr elem) { + for (size_t i = 0; i < elem->size(); ++i) { + ConstElementPtr pool = elem->get(i); + ostringstream prefix; + prefix << xpath << "/address-pool[pool-id='" << i << "']"; + setPool(prefix.str(), pool); + } +} + +void +TranslatorPools::setPoolsbyAddresses(const string& xpath, + ConstElementPtr elem) { + for (size_t i = 0; i < elem->size(); ++i) { + ConstElementPtr pool = elem->get(i); + if (!pool->contains("pool")) { + isc_throw(BadValue, "setPoolsbyAddresses: missing required pool: " + << pool->str()); + } + string pref = pool->get("pool")->stringValue(); + string start_addr; + string end_addr; + getAddresses(pref, start_addr, end_addr); + ostringstream prefix; + prefix << xpath << "/pool[start-address='" << start_addr + << "'][end-address='" << end_addr << "']"; + setPool(prefix.str(), pool); + } +} + +}; // end of namespace isc::yang +}; // end of namespace isc diff --git a/src/lib/yang/translator_pool.h b/src/lib/yang/translator_pool.h new file mode 100644 index 0000000000..21061201e3 --- /dev/null +++ b/src/lib/yang/translator_pool.h @@ -0,0 +1,248 @@ +// 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_POOL_H +#define ISC_TRANSLATOR_POOL_H 1 + +#include +#include + +namespace isc { +namespace yang { + +// Address pool translation between YANG and JSON +// +// JSON syntax for both kea-dhcp4 and kea-dhcp6 is: +// @code +// { +// "pool": "", +// "option-data": [ ], +// "client-class": "", +// "require-client-classes": [ ], +// "user-context": { }, +// "comment": "" +// } +// @endcode +// +// YANG syntax is for ietf-dhcpv6-server is with pool-id as the key: +// @code +// +--rw pool-id uint32 +// +--rw pool-prefix inet:ipv6-prefix +// +--rw start-address inet:ipv6-address-no-zone +// +--rw end-address inet:ipv6-address-no-zone +// +--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-address-count threshold +// +--rw option-set-id +// /server/server-config/option-sets/option-set/option-set-id +// @endcode +// +// YANG syntax for kea-dhcp[46] is with start-address and end-address +// as the keys: +// @code +// +--rw prefix? inet:ipv[46]-prefix +// +--rw start-address inet:ipv[46]-address +// +--rw end-address inet:ipv[46]-address +// +--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 for the IETF model: +// @code +// [ +// { +// "pool": "2001:db8::/112" +// } +// ] +// @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']/address-pools (container) +// /ietf-dhcpv6-server:server/server-config/network-ranges/ +// network-range[network-range-id='111']/address-pools/ +// address-pool[pool-id='0'] (list instance) +// /ietf-dhcpv6-server:server/server-config/network-ranges/ +// network-range[network-range-id='111']/address-pools/ +// address-pool[pool-id='0']/pool-id = 0 +// /ietf-dhcpv6-server:server/server-config/network-ranges/ +// network-range[network-range-id='111']/address-pools/ +// address-pool[pool-id='0']/pool-prefix = 2001:db8::1:0/112 +// /ietf-dhcpv6-server:server/server-config/network-ranges/ +// network-range[network-range-id='111']/address-pools/ +// address-pool[pool-id='0']/start-address = 2001:db8::1:0 +// /ietf-dhcpv6-server:server/server-config/network-ranges/ +// network-range[network-range-id='111']/address-pools/ +// address-pool[pool-id='0']/end-address = 2001:db8::1:ffff +// /ietf-dhcpv6-server:server/server-config/network-ranges/ +// network-range[network-range-id='111']/address-pools/ +// address-pool[pool-id='0']/max-address-count = disabled +// @endcode +// +// An example in JSON and YANG formats for the Kea model: +// @code +// [ +// { +// "pool": "2001:db8::1 - 2001:db8::100" +// } +// ] +// @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']/pools (container) +// /kea-dhcp6-server:config/subnet6/subnet6[id='111']/pools/ +// pool[start-address='2001:db8::1'][end-address='2001:db8::100'] +// (list instance) +// /kea-dhcp6-server:config/subnet6/subnet6[id='111']/pools/ +// pool[start-address='2001:db8::1'][end-address='2001:db8::100']/ +// start-address = 2001:db8::1 +// /kea-dhcp6-server:config/subnet6/subnet6[id='111']/pools/ +// pool[start-address='2001:db8::1'][end-address='2001:db8::100']/ +// end-address = 2001:db8::100 +// @endcode + +// @brief A translator class for converting a pool between YANG and JSON. +// +// Currently supports on kea-dhcp[46]-server and partially ietf-dhcpv6-server. +class TranslatorPool : virtual public TranslatorOptionDataList { +public: + + /// @brief Constructor. + /// + /// @param session Sysrepo session. + /// @param model Model name. + TranslatorPool(S_Session session, const std::string& model); + + /// @brief Destructor. + virtual ~TranslatorPool(); + + /// @brief Get and translate a pool from YANG to JSON. + /// + /// @param xpath The xpath of the pool. + /// @return JSON representation of the pool. + /// @throw SysrepoError when sysrepo raises an error. + isc::data::ElementPtr getPool(const std::string& xpath); + + /// @brief Translate and set (address) pool from JSON to YANG. + /// + /// @param xpath The xpath of the pool. + /// @param elem The JSON element. + void setPool(const std::string& xpath, isc::data::ConstElementPtr elem); + + /// @brief Get start and end addresses from prefix. + /// + /// @param prefix The prefix string. + /// @param start_address The reference to the start_address. + /// @param end_address The reference to the end_address. + /// @throw BadValue when the prefix is not correctly formed. + static void getAddresses(const std::string& prefix, + std::string& start_address, + std::string& end_address); + +protected: + /// @brief getPool for ietf-dhcpv6-server. + /// + /// @param xpath The xpath of the pool. + /// @return JSON representation of the pool. + /// @throw BadValue on pool without prefix. + isc::data::ElementPtr getPoolIetf6(const std::string& xpath); + + /// @brief setPool for ietf-dhcpv6-server. + /// + /// @param xpath The xpath of the pool. + /// @param elem The JSON element. + /// @throw BadValue on pool without prefix and with a range which is + /// not a prefix. + void setPoolIetf6(const std::string& xpath, + isc::data::ConstElementPtr elem); + + /// @brief getPool for kea-dhcp[46]. + /// + /// @param xpath The xpath of the pool. + /// @return JSON representation of the pool. + /// @throw BadValue on a pool without prefix and start or end address. + isc::data::ElementPtr getPoolKea(const std::string& xpath); + + /// @brief setPool for kea-dhcp[46]. + /// + /// @param xpath The xpath of the pool. + /// @param elem The JSON element. + /// @throw BadValue on a pool without a well formed prefix. + void setPoolKea(const std::string& xpath, isc::data::ConstElementPtr elem); + + /// @brief The model. + std::string model_; +}; + +// @brief A translator class for converting pools between YANG and JSON. +// +// Currently supports on kea-dhcp[46]-server and partially ietf-dhcpv6-server. +class TranslatorPools : virtual public TranslatorPool { +public: + + /// @brief Constructor. + /// + /// @param session Sysrepo session. + /// @param model Model name. + TranslatorPools(S_Session session, const std::string& model); + + /// @brief Destructor. + virtual ~TranslatorPools(); + + /// @brief Get and translate pools from YANG to JSON. + /// + /// @param xpath The xpath of the pool list. + /// @throw SysrepoError when sysrepo raises an error. + isc::data::ElementPtr getPools(const std::string& xpath); + + /// @brief Translate and set (address) pools from JSON to YANG. + /// + /// @param xpath The xpath of the pool list. + /// @param elem The JSON element. + void setPools(const std::string& xpath, isc::data::ConstElementPtr elem); + +protected: + /// @brief setPools using pool-id. + /// + /// @param xpath The xpath of the pool list. + /// @param elem The JSON element. + void setPoolsbyId(const std::string& xpath, + isc::data::ConstElementPtr elem); + + /// @brief setPools using address pair. + /// + /// @param xpath The xpath of the pool list. + /// @param elem The JSON element. + /// @throw BadValue on a pool without a prefix. + void setPoolsbyAddresses(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_POOL_H