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
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.
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)
--- /dev/null
+// 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_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 pool_list[] = "pool list";
+
+/// @brief Test fixture class for @ref TranslatorPools.
+class TranslatorPoolsTest :
+ public GenericTranslatorTest<pool_list, TranslatorPools> {
+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
--- /dev/null
+// 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 <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <yang/adaptor.h>
+#include <yang/translator_pool.h>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+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<unsigned>
+ (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<unsigned>
+ (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
--- /dev/null
+// 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 <yang/translator_option_data.h>
+#include <list>
+
+namespace isc {
+namespace yang {
+
+// Address pool translation between YANG and JSON
+//
+// JSON syntax for both kea-dhcp4 and kea-dhcp6 is:
+// @code
+// {
+// "pool": "<pool prefix or start - end addresses>",
+// "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 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