From: Francis Dupont Date: Fri, 28 Sep 2018 13:03:30 +0000 (+0200) Subject: [65-libyang-class] Added client class(es) translators from kea-yang X-Git-Tag: 65-libyang-shared-network-translator_base~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=674447927e603dc31ee388a1a6c28daeab64eb46;p=thirdparty%2Fkea.git [65-libyang-class] Added client class(es) translators from kea-yang --- diff --git a/src/lib/yang/Makefile.am b/src/lib/yang/Makefile.am index bfba24e36a..646b93f128 100644 --- a/src/lib/yang/Makefile.am +++ b/src/lib/yang/Makefile.am @@ -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 \ diff --git a/src/lib/yang/tests/Makefile.am b/src/lib/yang/tests/Makefile.am index 320987ef98..e28bd0dbb9 100644 --- a/src/lib/yang/tests/Makefile.am +++ b/src/lib/yang/tests/Makefile.am @@ -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 diff --git a/src/lib/yang/tests/translator_class_unittests.cc b/src/lib/yang/tests/translator_class_unittests.cc new file mode 100644 index 0000000000..3b2a218331 --- /dev/null +++ b/src/lib/yang/tests/translator_class_unittests.cc @@ -0,0 +1,146 @@ +// 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 client_classes[] = "client classes"; + +/// @brief Test fixture class for @ref TranslatorClasses. +class TranslatorClassesTest : + public GenericTranslatorTest { +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 diff --git a/src/lib/yang/translator_class.cc b/src/lib/yang/translator_class.cc new file mode 100644 index 0000000000..265d8e1fd6 --- /dev/null +++ b/src/lib/yang/translator_class.cc @@ -0,0 +1,245 @@ +// 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 + +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 diff --git a/src/lib/yang/translator_class.h b/src/lib/yang/translator_class.h new file mode 100644 index 0000000000..23087edcfd --- /dev/null +++ b/src/lib/yang/translator_class.h @@ -0,0 +1,177 @@ +// 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 +#include +#include + +namespace isc { +namespace yang { + +/// Client class translation between YANG and JSON +/// +/// JSON syntax for all Kea servers with client class is: +/// @code +/// { +/// "name": , +/// "test": , +/// "only-if-required": , +/// "option-data":