Commit 67444792 authored by Francis Dupont's avatar Francis Dupont

[65-libyang-class] Added client class(es) translators from kea-yang

parent 1d34b296
......@@ -20,12 +20,12 @@ libkea_yang_la_SOURCES += translator_option_data.cc
libkea_yang_la_SOURCES += translator_option_data.h
libkea_yang_la_SOURCES += translator_option_def.cc
libkea_yang_la_SOURCES += translator_option_def.h
libkea_yang_la_SOURCES += translator_class.cc translator_class.h
libkea_yang_la_SOURCES += translator_pool.cc translator_pool.h
libkea_yang_la_SOURCES += translator_pd_pool.cc translator_pd_pool.h
libkea_yang_la_SOURCES += translator_host.cc translator_host.h
libkea_yang_la_SOURCES += yang_models.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
libkea_yang_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
......@@ -46,6 +46,7 @@ libkea_yang_include_HEADERS = \
adaptor_subnet.h \
sysrepo_error.h \
translator.h \
translator_class.h \
translator_control_socket.h \
translator_database.h \
translator_host.h \
......
......@@ -29,6 +29,7 @@ run_unittests_SOURCES += translator_database_unittests.cc
run_unittests_SOURCES += translator_logger_unittests.cc
run_unittests_SOURCES += translator_option_data_unittests.cc
run_unittests_SOURCES += translator_option_def_unittests.cc
run_unittests_SOURCES += translator_class_unittests.cc
run_unittests_SOURCES += translator_pool_unittests.cc
run_unittests_SOURCES += translator_pd_pool_unittests.cc
run_unittests_SOURCES += translator_host_unittests.cc
......
// 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_class.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 client_classes[] = "client classes";
/// @brief Test fixture class for @ref TranslatorClasses.
class TranslatorClassesTest :
public GenericTranslatorTest<client_classes, TranslatorClasses> {
public:
/// Constructor.
TranslatorClassesTest() { }
/// Destructor (does nothing).
virtual ~TranslatorClassesTest() { }
};
// This test verifies that an empty client class list can be properly
// translated from YANG to JSON.
TEST_F(TranslatorClassesTest, getEmpty) {
useModel("kea-dhcp4-server");
// Get the client class list and checks it is empty.
const string& xpath = "/kea-dhcp4-server:config/client-classes";
ConstElementPtr classes;
EXPECT_NO_THROW(classes = t_obj_->getClasses(xpath));
EXPECT_FALSE(classes);
}
// This test verifies that one client class can be properly translated
// from YANG to JSON.
TEST_F(TranslatorClassesTest, get) {
useModel("kea-dhcp6-server");
// Create the client class.
const string& xpath = "/kea-dhcp6-server:config/client-classes";
const string& xclass = xpath + "/client-class[name='foo']";
const string& xtest = xclass + "/test";
S_Val v_test(new Val("not member('ALL')", SR_STRING_T));
EXPECT_NO_THROW(sess_->set_item(xtest.c_str(), v_test));
// Get the client class.
ConstElementPtr cclass;
EXPECT_NO_THROW(cclass = t_obj_->getClass(xclass));
ASSERT_TRUE(cclass);
ElementPtr expected = Element::createMap();
expected->set("name", Element::create(string("foo")));
expected->set("test", Element::create(string("not member('ALL')")));
EXPECT_TRUE(expected->equals(*cclass));
// Get the client class list and checks the client class is in it.
ConstElementPtr classes;
EXPECT_NO_THROW(classes = t_obj_->getClasses(xpath));
ASSERT_TRUE(classes);
ASSERT_EQ(Element::list, classes->getType());
ASSERT_EQ(1, classes->size());
EXPECT_TRUE(cclass->equals(*classes->get(0)));
}
// This test verifies that an empty client class list can be properly
// translated from JSON to YANG.
TEST_F(TranslatorClassesTest, setEmpty) {
useModel("kea-dhcp4-server");
// Set empty list.
const string& xpath = "/kea-dhcp4-server:config/client-classes";
ConstElementPtr classes = Element::createList();
EXPECT_NO_THROW(t_obj_->setClasses(xpath, classes));
// Get it back.
classes.reset();
EXPECT_NO_THROW(classes = t_obj_->getClasses(xpath));
EXPECT_FALSE(classes);
// Check that the tree representation is empty.
S_Tree tree;
EXPECT_NO_THROW(tree = sess_->get_subtree("/kea-dhcp4-server:config"));
EXPECT_FALSE(tree);
}
// This test verifies that one client class can be properly translated
// from JSON to YANG.
TEST_F(TranslatorClassesTest, set) {
useModel("kea-dhcp6-server");
// Set one client class.
const string& xpath = "/kea-dhcp6-server:config/client-classes";
ElementPtr classes = Element::createList();
ElementPtr cclass = Element::createMap();
cclass->set("name", Element::create(string("foo")));
cclass->set("test", Element::create(string("''==''")));
cclass->set("only-if-required",Element::create(false));
classes->add(cclass);
EXPECT_NO_THROW(t_obj_->setClasses(xpath, classes));
// Get it back.
ConstElementPtr got;
EXPECT_NO_THROW(got = t_obj_->getClasses(xpath));
ASSERT_TRUE(got);
ASSERT_EQ(Element::list, got->getType());
ASSERT_EQ(1, got->size());
EXPECT_TRUE(cclass->equals(*got->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"
" -- client-classes (container)\n"
" |\n"
" -- client-class (list instance)\n"
" |\n"
" -- name = foo\n"
" |\n"
" -- test = ''==''\n"
" |\n"
" -- only-if-required = false\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 <yang/translator_class.h>
#include <yang/adaptor.h>
#include <sstream>
using namespace std;
using namespace isc::data;
namespace isc {
namespace yang {
TranslatorClass::TranslatorClass(S_Session session, const string& model)
: TranslatorBasic(session),
TranslatorOptionData(session, model),
TranslatorOptionDataList(session, model),
TranslatorOptionDef(session, model),
TranslatorOptionDefList(session, model),
model_(model) {
}
TranslatorClass::~TranslatorClass() {
}
ElementPtr
TranslatorClass::getClass(const string& xpath) {
try {
if ((model_ == "kea-dhcp4-server") ||
(model_ == "kea-dhcp6-server")) {
return (getClassKea(xpath));
}
} catch (const sysrepo_exception& ex) {
isc_throw(SysrepoError,
"sysrepo error getting client class at '" << xpath
<< "': " << ex.what());
}
isc_throw(NotImplemented,
"getClass not implemented for the model: " << model_);
}
ElementPtr
TranslatorClass::getClassKea(const string& xpath) {
ConstElementPtr name = getItem(xpath + "/name");
if (!name) {
// Can't happen as the name is the key.
isc_throw(Unexpected, "getClassKea requires name: " << xpath);
}
ElementPtr result = Element::createMap();
result->set("name", name);
ConstElementPtr test = getItem(xpath + "/test");
if (test) {
result->set("test", test);
}
ConstElementPtr required = getItem(xpath + "/only-if-required");
if (required) {
result->set("only-if-required", required);
}
ConstElementPtr options = getOptionDataList(xpath + "/option-data-list");
if (options && (options->size() > 0)) {
result->set("option-data", options);
}
if (model_ == "kea-dhcp4-server") {
ConstElementPtr defs = getOptionDefList(xpath +"/option-def-list");
if (defs && (defs->size() > 0)) {
result->set("option-def", defs);
}
ConstElementPtr next = getItem(xpath + "/next-server");
if (next) {
result->set("next-server", next);
}
ConstElementPtr hostname = getItem(xpath + "/server-hostname");
if (hostname) {
result->set("server-hostname", hostname);
}
ConstElementPtr boot = getItem(xpath + "/boot-file-name");
if (boot) {
result->set("boot-file-name", boot);
}
}
ConstElementPtr context = getItem(xpath + "/user-context");
if (context) {
result->set("user-context", Element::fromJSON(context->stringValue()));
}
return (result);
}
void
TranslatorClass::setClass(const string& xpath, ConstElementPtr elem) {
try {
if ((model_ == "kea-dhcp4-server") ||
(model_ == "kea-dhcp6-server")) {
setClassKea(xpath, elem);
} else {
isc_throw(NotImplemented,
"setClass not implemented for the model: " << model_);
}
} catch (const sysrepo_exception& ex) {
isc_throw(SysrepoError,
"sysrepo error setting client class '" << elem->str()
<< "' at '" << xpath << "': " << ex.what());
}
}
void
TranslatorClass::setClassKea(const string& xpath, ConstElementPtr elem) {
bool created = false;
// Skip key name.
ConstElementPtr test = elem->get("test");
if (test) {
setItem(xpath + "/test", test, SR_STRING_T);
created = true;
}
ConstElementPtr required = elem->get("only-if-required");
if (required) {
setItem(xpath + "/only-if-required", required, SR_BOOL_T);
created = true;
}
ConstElementPtr options = elem->get("option-data");
if (options) {
setOptionDataList(xpath + "/option-data-list", options);
created = true;
}
if (model_ == "kea-dhcp4-server") {
ConstElementPtr defs = elem->get("option-def");
if (defs) {
setOptionDefList(xpath + "/option-def-list", defs);
created = true;
}
ConstElementPtr next = elem->get("next-server");
if (next) {
setItem(xpath + "/next-server", next, SR_STRING_T);
created = true;
}
ConstElementPtr hostname = elem->get("server-hostname");
if (hostname) {
setItem(xpath + "/server-hostname", hostname, SR_STRING_T);
created = true;
}
ConstElementPtr boot = elem->get("boot-file-name");
if (boot) {
setItem(xpath + "/boot-file-name", boot, 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 key so force creation.
if (!created) {
ConstElementPtr list = Element::createList();
setItem(xpath, list, SR_LIST_T);
}
}
TranslatorClasses::TranslatorClasses(S_Session session, const string& model)
: TranslatorBasic(session),
TranslatorOptionData(session, model),
TranslatorOptionDataList(session, model),
TranslatorOptionDef(session, model),
TranslatorOptionDefList(session, model),
TranslatorClass(session, model),
model_(model) {
}
TranslatorClasses::~TranslatorClasses() {
}
ConstElementPtr
TranslatorClasses::getClasses(const string& xpath) {
try {
if ((model_ == "kea-dhcp4-server") ||
(model_ == "kea-dhcp6-server")) {
return (getClassesKea(xpath));
}
} catch (const sysrepo_exception& ex) {
isc_throw(SysrepoError,
"sysrepo error getting client classes at '" << xpath
<< "': " << ex.what());
}
isc_throw(NotImplemented,
"getClasses not implemented for the model: " << model_);
}
ElementPtr
TranslatorClasses::getClassesKea(const string& xpath) {
S_Iter_Value iter = getIter(xpath + "/*");
if (!iter) {
// Can't happen.
isc_throw(Unexpected, "getClassesKea: can't get iterator: " << xpath);
}
ElementPtr result = Element::createList();
for (;;) {
const string& cclass = getNext(iter);
if (cclass.empty()) {
break;
}
result->add(getClass(cclass));
}
if (result->size() > 0) {
return (result);
} else {
return (ElementPtr());
}
}
void
TranslatorClasses::setClasses(const string& xpath, ConstElementPtr elem) {
try {
if ((model_ == "kea-dhcp4-server") ||
(model_ == "kea-dhcp6-server")) {
setClassesKea(xpath, elem);
} else {
isc_throw(NotImplemented,
"setClasses not implemented for the model: " << model_);
}
} catch (const sysrepo_exception& ex) {
isc_throw(SysrepoError,
"sysrepo error setting client classes '" << elem->str()
<< "' at '" << xpath << "': " << ex.what());
}
}
void
TranslatorClasses::setClassesKea(const string& xpath, ConstElementPtr elem) {
for (size_t i = 0; i < elem->size(); ++i) {
ConstElementPtr cclass = elem->get(i);
if (!cclass->contains("name")) {
isc_throw(BadValue, "client class without name: " << elem->str());
}
string name = cclass->get("name")->stringValue();
ostringstream key;
key << xpath << "/client-class[name='" << name << "']";
setClass(key.str(), cclass);
}
}
}; // end of namespace isc::yang
}; // end of namespace isc
// 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_CLASS_H
#define ISC_TRANSLATOR_CLASS_H 1
#include <yang/translator_option_data.h>
#include <yang/translator_option_def.h>
#include <list>
namespace isc {
namespace yang {
/// Client class translation between YANG and JSON
///
/// JSON syntax for all Kea servers with client class is:
/// @code
/// {
/// "name": <name>,
/// "test": <test expression>,
/// "only-if-required": <only if required flag>,
/// "option-data": <option data list>,
/// (DHCPv4 only)
/// "option-def": <option definition list>,
/// "next-server": <next server addresss>,
/// "server-hostname": <server hostname>,
/// "boot-file-name": <boot filename>,
/// "user-context": { <json map> },
/// "comment": <comment>
/// }
/// @endcode
///
/// YANG syntax for kea-dhcp[46] is with the name as the list key:
/// @code
/// +--rw name string
/// +--rw test? string
/// +--rw only-if-required? boolean
/// +--rw option-data-list option-data*
/// +--rw option-def-list option-def*
/// +--rw next-server? inet:ipv4-address
/// +--rw server-hostname? string
/// +--rw boot-file-name? string
/// +--rw user-context? string
/// @endcode
///
/// An example in JSON and YANG formats:
/// @code
/// [
/// {
/// "name": "foo",
/// "test": "''==''",
/// "only-if-required": false
/// }
/// ]
/// @endcode
/// @code
/// /kea-dhcp6-server:config (container)
/// /kea-dhcp6-server:config/client-classes (container)
/// /kea-dhcp6-server:config/client-classes/
/// client-class[name='foo'] (list instance)
/// /kea-dhcp6-server:config/client-classes/
/// client-class[name='foo']/name = foo
/// /kea-dhcp6-server:config/client-classes/
/// client-class[name='foo']/test = ''==''
/// /kea-dhcp6-server:config/client-classes/
/// client-class[name='foo']/ only-if-required = false
/// @endcode
/// @brief A translator class for converting a client class between
/// YANG and JSON.
///
/// Currently supports on kea-dhcp[46]-server. Ietf-dhcpv6-server does
/// not define client classe contents.
class TranslatorClass : virtual public TranslatorOptionDataList,
virtual public TranslatorOptionDefList {
public:
/// @brief Constructor.
///
/// @param session Sysrepo session.
/// @param model Model name.
TranslatorClass(S_Session session, const std::string& model);
/// @brief Destructor.
virtual ~TranslatorClass();
/// @brief Get and translate a client class from YANG to JSON.
///
/// @param xpath The xpath of the class .
/// @return JSON representation of the class .
/// @throw SysrepoError when sysrepo raises an error.
isc::data::ElementPtr getClass(const std::string& xpath);
/// @brief Translate and set client class from JSON to YANG.
///
/// @param xpath The xpath of the client class.
/// @param elem The JSON element.
void setClass(const std::string& xpath, isc::data::ConstElementPtr elem);
protected:
/// @brief getClass JSON for kea-dhcp[46].
///
/// @param xpath The xpath of the class .
/// @return JSON representation of the class .
/// @throw SysrepoError when sysrepo raises an error.
isc::data::ElementPtr getClassKea(const std::string& xpath);
/// @brief setClass for kea-dhcp[46].
///
/// @param xpath The xpath of the client class.
/// @param elem The JSON element.
void setClassKea(const std::string& xpath,
isc::data::ConstElementPtr elem);
/// @brief The model.
std::string model_;
};
/// @brief A translator class for converting a client class list between
/// YANG and JSON.
///
/// Currently supports on kea-dhcp[46]-server. Ietf-dhcpv6-server does
/// not define client classe contents.
class TranslatorClasses : virtual public TranslatorClass {
public:
/// @brief Constructor.
///
/// @param session Sysrepo session.
/// @param model Model name.
TranslatorClasses(S_Session session, const std::string& model);
/// @brief Destructor.
virtual ~TranslatorClasses();
/// @brief Get and translate client classes from YANG to JSON.
///
/// @param xpath The xpath of classes.
/// @return JSON representation of classes.
/// @throw SysrepoError when sysrepo raises an error.
isc::data::ConstElementPtr getClasses(const std::string& xpath);
/// @brief Translate and set client classes from JSON to YANG.
///
/// @param xpath The xpath of classes.
/// @param elem The JSON element.
void setClasses(const std::string& xpath,
isc::data::ConstElementPtr elem);
protected:
/// @brief getClasses JSON for kea-dhcp[46].
///
/// @param xpath The xpath of classes.
/// @return JSON representation of classes.
/// @throw SysrepoError when sysrepo raises an error.
isc::data::ElementPtr getClassesKea(const std::string& xpath);
/// @brief setClasses for kea-dhcp[46].
///
///
/// @param xpath The xpath of classes.
/// @param elem The JSON element.
/// @throw BadValue on client class without name.
void setClassesKea(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_CLASS_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment