]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[65-libyang-class] Added client class(es) translators from kea-yang
authorFrancis Dupont <fdupont@isc.org>
Fri, 28 Sep 2018 13:03:30 +0000 (15:03 +0200)
committerFrancis Dupont <fdupont@isc.org>
Mon, 22 Oct 2018 11:22:21 +0000 (07:22 -0400)
src/lib/yang/Makefile.am
src/lib/yang/tests/Makefile.am
src/lib/yang/tests/translator_class_unittests.cc [new file with mode: 0644]
src/lib/yang/translator_class.cc [new file with mode: 0644]
src/lib/yang/translator_class.h [new file with mode: 0644]

index bfba24e36af2622bed79096a3c843bfac8337cdb..646b93f12880918f9d527a7ae0bf93d9819bbb90 100644 (file)
@@ -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 \
index 320987ef982fbd61009571110d3b79cabeb2c489..e28bd0dbb93328bbd924e640f52ae869bcd6a70c 100644 (file)
@@ -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 (file)
index 0000000..3b2a218
--- /dev/null
@@ -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 <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
diff --git a/src/lib/yang/translator_class.cc b/src/lib/yang/translator_class.cc
new file mode 100644 (file)
index 0000000..265d8e1
--- /dev/null
@@ -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 <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
diff --git a/src/lib/yang/translator_class.h b/src/lib/yang/translator_class.h
new file mode 100644 (file)
index 0000000..23087ed
--- /dev/null
@@ -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 <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