Commit f53c587d authored by Francis Dupont's avatar Francis Dupont

[65-libyang-pool] Brought pool code from kea-yang

parent 52f787c5
......@@ -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.
......
......@@ -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)
......
// 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
// 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);