lib_LTLIBRARIES = libkea-yang.la
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
if HAVE_GTEST
TESTS += run_unittests
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)
--- /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_option_data.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+
+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<TranslatorOptionDataList> 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<TranslatorOptionDataList> 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<TranslatorOptionDataList> 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<TranslatorOptionDataList> 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
--- /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 <yang/translator_option_data.h>
+#include <yang/adaptor.h>
+#include <sstream>
+
+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<unsigned>(option->get("code")->intValue());
+ if (!option->contains("space")) {
+ isc_throw(BadValue,"option data without space: " <<option->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
--- /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_OPTION_DATA_H
+#define ISC_TRANSLATOR_OPTION_DATA_H 1
+
+#include <yang/translator.h>
+#include <list>
+
+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": <code>,
+ /// "name": <name>,
+ /// "space": <space>,
+ /// "csv-format": <csv format flag>,
+ /// "data": <value>,
+ /// "always-send": <always send flag>,
+ /// "user-context": { <json map> },
+ /// "comment": "<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