diff --git a/src/lib/yang/Makefile.am b/src/lib/yang/Makefile.am index b1b591e679c5ef3fd48e69cbb4a9b5c4ab4c9c5c..5dfc05e5cf251c65d7c886b957060b3e5216525d 100644 --- a/src/lib/yang/Makefile.am +++ b/src/lib/yang/Makefile.am @@ -8,6 +8,8 @@ lib_LTLIBRARIES = libkea-yang.la libkea_yang_la_SOURCES = adaptor.cc adaptor.h 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_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la libkea_yang_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la diff --git a/src/lib/yang/tests/Makefile.am b/src/lib/yang/tests/Makefile.am index 8d3b9b8e069b73549103b743b78c7494fe33b6ae..64d690265cbd50af649713659bcab5f4834d86b0 100644 --- a/src/lib/yang/tests/Makefile.am +++ b/src/lib/yang/tests/Makefile.am @@ -19,6 +19,7 @@ if HAVE_GTEST TESTS += run_unittests run_unittests_SOURCES = adaptor_unittests.cc run_unittests_SOURCES += translator_unittests.cc +run_unittests_SOURCES += translator_option_data_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_option_data_unittests.cc b/src/lib/yang/tests/translator_option_data_unittests.cc new file mode 100644 index 0000000000000000000000000000000000000000..1de5dc6b1ac73f0f440b8d99c468ee48d5dfe624 --- /dev/null +++ b/src/lib/yang/tests/translator_option_data_unittests.cc @@ -0,0 +1,157 @@ +// 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; + +namespace { + +// Test get empty option data list. +TEST(TranslatorOptionDataListTest, getEmpty) { + // Get a translator option data list object to play with. + S_Connection conn(new Connection("translator option data list unittests")); + S_Session sess(new Session(conn, SR_DS_CANDIDATE)); + boost::scoped_ptr todl_obj; + + // Use the ad hoc model. + const string& model = "kea-dhcp4"; + EXPECT_NO_THROW(todl_obj.reset(new TranslatorOptionDataList(sess, model))); + + // Get the option data list and checks it is empty. + const string& xpath = "/kea-dhcp4:config/option-data-list"; + ConstElementPtr options; + EXPECT_NO_THROW(options = todl_obj->getOptionDataList(xpath)); + ASSERT_TRUE(options); + ASSERT_EQ(Element::list, options->getType()); + EXPECT_EQ(0, options->size()); +} + +// Test get one option data. +TEST(TranslatorOptionDataListTest, get) { + // Get a translator option data list object to play with. + S_Connection conn(new Connection("translator option data list unittests")); + S_Session sess(new Session(conn, SR_DS_CANDIDATE)); + boost::scoped_ptr todl_obj; + + // Use the ad hoc model. + const string& model = "kea-dhcp6"; + EXPECT_NO_THROW(todl_obj.reset(new TranslatorOptionDataList(sess, model))); + + // Create the option code 100. + const string& xpath = "/kea-dhcp6:config/option-data-list"; + const string& xoption = xpath + "/option-data[code='100'][space='dns']"; + const string& xformat = xoption + "/csv-format"; + const string& xdata = xoption + "/data"; + const string& xsend = xoption + "/always-send"; + S_Val s_false(new Val(false)); + ASSERT_NO_THROW(sess->set_item(xformat.c_str(), s_false)); + S_Val s_data(new Val("12121212")); + ASSERT_NO_THROW(sess->set_item(xdata.c_str(), s_data)); + ASSERT_NO_THROW(sess->set_item(xsend.c_str(), s_false)); + + // Get the option data. + ConstElementPtr option; + EXPECT_NO_THROW(option = todl_obj->getOptionData(xoption)); + ASSERT_TRUE(option); + EXPECT_EQ("{ \"always-send\": false, \"code\": 100, \"csv-format\": false, \"data\": \"12121212\", \"space\": \"dns\" }", + option->str()); + + // Get the option data list. + ConstElementPtr options; + EXPECT_NO_THROW(options = todl_obj->getOptionDataList(xpath)); + ASSERT_TRUE(options); + ASSERT_EQ(Element::list, options->getType()); + EXPECT_EQ(1, options->size()); + EXPECT_TRUE(option->equals(*options->get(0))); +} + +// Test set empty option data list. +TEST(TranslatorOptionDataListTest, setEmpty) { + // Get a translator option data list object to play with. + S_Connection conn(new Connection("translator option data list unittests")); + S_Session sess(new Session(conn, SR_DS_CANDIDATE)); + boost::scoped_ptr todl_obj; + + // Use the ad hoc model. + const string& model = "kea-dhcp4"; + EXPECT_NO_THROW(todl_obj.reset(new TranslatorOptionDataList(sess, model))); + + // Set empty list. + const string& xpath = "/kea-dhcp4:config/option-data-list"; + ConstElementPtr options = Element::createList(); + EXPECT_NO_THROW(todl_obj->setOptionDataList(xpath, options)); + + // Get it back. + options.reset(); + EXPECT_NO_THROW(options = todl_obj->getOptionDataList(xpath)); + ASSERT_TRUE(options); + EXPECT_EQ(0, options->size()); +} + +// Test set an option data. +TEST(TranslatorOptionDataListTest, set) { + // Get a translator option data list object to play with. + S_Connection conn(new Connection("translator option data list unittests")); + S_Session sess(new Session(conn, SR_DS_CANDIDATE)); + boost::scoped_ptr todl_obj; + + // Use the ad hoc model. + const string& model = "kea-dhcp6"; + EXPECT_NO_THROW(todl_obj.reset(new TranslatorOptionDataList(sess, model))); + + // Set one option data. + const string& xpath = "/kea-dhcp6:config/option-data-list"; + ElementPtr options = Element::createList(); + ElementPtr option = Element::createMap(); + option->set("code", Element::create(100)); + option->set("space", Element::create(string("dns"))); + option->set("csv-format", Element::create(false)); + option->set("data", Element::create(string("12121212"))); + option->set("always-send", Element::create(false)); + options->add(option); + EXPECT_NO_THROW(todl_obj->setOptionDataList(xpath, options)); + + // Get it back. + ConstElementPtr got; + EXPECT_NO_THROW(got = todl_obj->getOptionDataList(xpath)); + ASSERT_TRUE(got); + ASSERT_EQ(1, got->size()); + EXPECT_TRUE(option->equals(*got->get(0))); + + // Check the tree representation. + S_Tree tree; + EXPECT_NO_THROW(tree = sess->get_subtree("/kea-dhcp6:config")); + ASSERT_TRUE(tree); + string expected = + "kea-dhcp6:config (container)\n" + " |\n" + " -- option-data-list (container)\n" + " |\n" + " -- option-data (list instance)\n" + " |\n" + " -- code = 100\n" + " |\n" + " -- space = dns\n" + " |\n" + " -- data = 12121212\n" + " |\n" + " -- csv-format = false\n" + " |\n" + " -- always-send = false\n"; + EXPECT_EQ(expected, tree->to_string(100)); +} + +}; // end of anonymous namespace diff --git a/src/lib/yang/translator_option_data.cc b/src/lib/yang/translator_option_data.cc new file mode 100644 index 0000000000000000000000000000000000000000..304bd37d848e5cf9a51783a0c018df556edb96e4 --- /dev/null +++ b/src/lib/yang/translator_option_data.cc @@ -0,0 +1,197 @@ +// 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 { + +TranslatorOptionData::TranslatorOptionData(S_Session session, + const string& model) + : TranslatorBasic(session), model_(model) { +} + +TranslatorOptionData::~TranslatorOptionData() { +} + +ElementPtr +TranslatorOptionData::getOptionData(const string& xpath) { + try { + if ((model_ == "kea-dhcp4") || (model_ == "kea-dhcp6")) { + return (getOptionDataKea(xpath)); + } + } catch (const sysrepo_exception& ex) { + isc_throw(SysrepoError, + "sysrepo error getting option data at '" << xpath + << "': " << ex.what()); + } + isc_throw(NotImplemented, + "getOptionData not implemented for the model: " << model_); +} + +ElementPtr +TranslatorOptionData::getOptionDataKea(const string& xpath) { + ConstElementPtr code = getItem(xpath + "/code"); + ConstElementPtr space = getItem(xpath + "/space"); + if (!code || !space) { + return (ElementPtr()); + } + ElementPtr result = Element::createMap(); + result->set("code", code); + result->set("space", space); + ConstElementPtr name = getItem(xpath + "/name"); + if (name) { + result->set("name", name); + } + ConstElementPtr data = getItem(xpath + "/data"); + if (data) { + result->set("data", data); + } + ConstElementPtr format = getItem(xpath + "/csv-format"); + if (format) { + result->set("csv-format", format); + } + ConstElementPtr send = getItem(xpath + "/always-send"); + if (send) { + result->set("always-send", send); + } + ConstElementPtr context = getItem(xpath + "/user-context"); + if (context) { + result->set("user-context", Element::fromJSON(context->stringValue())); + } + return (result); +} + +void +TranslatorOptionData::setOptionData(const string& xpath, + ConstElementPtr elem) { + try { + if ((model_ == "kea-dhcp4") || (model_ == "kea-dhcp6")) { + setOptionDataKea(xpath, elem); + } else { + isc_throw(NotImplemented, + "setOptionData not implemented for the model: " + << model_); + } + } catch (const sysrepo_exception& ex) { + isc_throw(SysrepoError, + "sysrepo error setting option data '" << elem->str() + << "' at '" << xpath << "': " << ex.what()); + } +} + +void +TranslatorOptionData::setOptionDataKea(const string& xpath, + ConstElementPtr elem) { + ConstElementPtr name = elem->get("name"); + if (name) { + setItem(xpath + "/name", name, SR_STRING_T); + } + ConstElementPtr data = elem->get("data"); + if (data) { + setItem(xpath + "/data", data, SR_STRING_T); + } + ConstElementPtr format = elem->get("csv-format"); + if (format) { + setItem(xpath + "/csv-format", format, SR_BOOL_T); + } + ConstElementPtr send = elem->get("always-send"); + if (send) { + setItem(xpath + "/always-send", send, SR_BOOL_T); + } + ConstElementPtr context = Adaptor::getContext(elem); + if (context) { + setItem(xpath + "/user-context", Element::create(context->str()), + SR_STRING_T); + } +} + +TranslatorOptionDataList::TranslatorOptionDataList(S_Session session, + const string& model) + : TranslatorBasic(session), TranslatorOptionData(session, model), + model_(model) { +} + +TranslatorOptionDataList::~TranslatorOptionDataList() { +} + +ConstElementPtr +TranslatorOptionDataList::getOptionDataList(const string& xpath) { + try { + if ((model_ == "kea-dhcp4") || (model_ == "kea-dhcp6")) { + return (getOptionDataListKea(xpath)); + } + } catch (const sysrepo_exception& ex) { + isc_throw(SysrepoError, + "sysrepo error getting option data list at '" << xpath + << "': " << ex.what()); + } + isc_throw(NotImplemented, + "getOptionDataList not implemented for the model: " << model_); +} + +ConstElementPtr +TranslatorOptionDataList::getOptionDataListKea(const string& xpath) { + ElementPtr result = Element::createList(); + S_Iter_Value iter = getIter(xpath + "/*"); + if (!iter) { + return (ConstElementPtr()); + } + for (;;) { + const string& option = getNext(iter); + if (option.empty()) { + break; + } + result->add(getOptionData(option)); + } + return (result); +} + +void +TranslatorOptionDataList::setOptionDataList(const string& xpath, + ConstElementPtr elem) { + try { + if ((model_ == "kea-dhcp4") || (model_ == "kea-dhcp6")) { + setOptionDataListKea(xpath, elem); + } else { + isc_throw(NotImplemented, + "setOptionDataList not implemented for the model: " + << model_); + } + } catch (const sysrepo_exception& ex) { + isc_throw(SysrepoError, + "sysrepo error setting option data list '" << elem->str() + << "' at '" << xpath << "': " << ex.what()); + } +} + +void +TranslatorOptionDataList::setOptionDataListKea(const string& xpath, + ConstElementPtr elem) { + for (size_t i = 0; i < elem->size(); ++i) { + ConstElementPtr option = elem->get(i); + if (!option->contains("code")) { + isc_throw(BadValue, "option data without code: " << option->str()); + } + unsigned code = static_cast(option->get("code")->intValue()); + if (!option->contains("space")) { + isc_throw(BadValue,"option data without space: " <str()); + } + string space = option->get("space")->stringValue(); + ostringstream keys; + keys << xpath << "/option-data[code='" << code + << "'][space='" << space << "']"; + setOptionData(keys.str(), option); + } +} + +}; // end of namespace isc::yang +}; // end of namespace isc diff --git a/src/lib/yang/translator_option_data.h b/src/lib/yang/translator_option_data.h new file mode 100644 index 0000000000000000000000000000000000000000..3600a80611e7a55dec8ea50a7e19913d7eba2d3c --- /dev/null +++ b/src/lib/yang/translator_option_data.h @@ -0,0 +1,136 @@ +// 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_OPTION_DATA_H +#define ISC_TRANSLATOR_OPTION_DATA_H 1 + +#include +#include + +namespace isc { +namespace yang { + +// @brief Between Yang and JSON translator class for the option data. +class TranslatorOptionData : virtual public TranslatorBasic { +public: + + /// @brief Constructor. + /// + /// @param session Sysrepo session. + /// @param model Model name. + TranslatorOptionData(S_Session session, const std::string& model); + + /// @brief Destructor. + virtual ~TranslatorOptionData(); + + /// @brief Get and translate an option data from Yang to JSON. + /// + /// JSON syntax for Kea DHCP with command channel is: + /// @code + /// { + /// "code": , + /// "name": , + /// "space": , + /// "csv-format": , + /// "data": , + /// "always-send": , + /// "user-context": { }, + /// "comment": "" + /// } + /// @endcode + /// + /// @param xpath The xpath of the option data. + /// @return JSON representation of the option data. + /// @throw SysrepoError when sysrepo raises an error. + isc::data::ElementPtr getOptionData(const std::string& xpath); + + /// @brief Translate and set option data from JSON to Yang. + /// + /// @param xpath The xpath of the option data. + /// @param elem The JSON element. + void setOptionData(const std::string& xpath, + isc::data::ConstElementPtr elem); + +protected: + /// @brief getOptionData JSON for kea-dhcp[46]. + /// + /// @param xpath The xpath of the option data. + /// @return JSON representation of the option data. + /// @throw SysrepoError when sysrepo raises an error. + isc::data::ElementPtr getOptionDataKea(const std::string& xpath); + + /// @brief setOptionData for kea-dhcp[46]. + /// + /// Yang syntax for kea-dhcp[46] with code and space as keys is: + /// @code + /// +--rw name? string + /// +--rw data? string + /// +--rw code uint8 / uint16 + /// +--rw space string + /// +--rw csv-format? string + /// +--rw always-send? boolean + /// +--rw user-context? string + /// @endcode + /// + /// @param xpath The xpath of the option data. + /// @param elem The JSON element. + void setOptionDataKea(const std::string& xpath, + isc::data::ConstElementPtr elem); + + /// @brief The model. + std::string model_; +}; + +// @brief Between Yang and JSON translator class for option data list. +class TranslatorOptionDataList : virtual public TranslatorOptionData { +public: + + /// @brief Constructor. + /// + /// @param session Sysrepo session. + /// @param model Model name. + TranslatorOptionDataList(S_Session session, const std::string& model); + + /// @brief Destructor. + virtual ~TranslatorOptionDataList(); + + /// @brief Get and translate option data list from Yang to JSON. + /// + /// @param xpath The xpath of the option data list. + /// @throw SysrepoError when sysrepo raises an error. + isc::data::ConstElementPtr getOptionDataList(const std::string& xpath); + + /// @brief Translate and set option data list from JSON to Yang. + /// + /// @param xpath The xpath of the option data list. + /// @param elem The JSON element. + void setOptionDataList(const std::string& xpath, + isc::data::ConstElementPtr elem); + +protected: + /// @brief getOptionDataList for kea-dhcp[46]. + /// + /// @param xpath The xpath of the option data list. + /// @throw SysrepoError when sysrepo raises an error. + isc::data::ConstElementPtr getOptionDataListKea(const std::string& xpath); + + /// @brief setOptionDataList for kea-dhcp[46]. + /// + /// Yang syntax is a option-data list keyed by code and space. + /// + /// @param xpath The xpath of the option data list. + /// @param elem The JSON element. + void setOptionDataListKea(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_OPTION_DATA_H