Implement YangSchema subclass
While we do not necessarily want to use `-isystem` for libyang, we
definitely need to use it for Boost itself. We are only using SPirit X3
directly, but that one still brings in a great deal of other supporting
libraries, including variant and a metaprogramming library.
In our CI environment, these are installed into the same prefix. Boost
itself is OK as we're including it via cmake imported target which uses
-isystem behind the scene. However, if we then add libyang via a regular
target_include_directories, the same path gets pushed into the include
paths, this time without -isystem, but rather as a regular -I. This in
turn starts affecting Boost, and we're screwed.
Fix this by ensuring that we consider libyang to be a system library,
too. We won't get annoyed by some extra warnings and therefore we won't
be able to improve that library as much as the CI would force us
otherwise :).
Change-Id: I7ec9b28501a46f8b2219f4920cbf5c1e4df59d7e
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5928c0e..4e09b90 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -63,6 +63,14 @@
)
target_link_libraries(schemas PUBLIC Boost::boost)
+add_library(yangschema STATIC
+ src/yang_schema.cpp
+ )
+target_link_libraries(yangschema ${LIBYANG_LIBRARIES})
+# Ensure that this doesn't override Boost's -isystem -- see the log for details.
+target_include_directories(yangschema SYSTEM PRIVATE ${LIBYANG_INCLUDEDIR})
+link_directories(${LIBYANG_LIBRARY_DIRS})
+
add_library(parser STATIC
src/parser.cpp
src/ast_commands.cpp
@@ -76,7 +84,7 @@
add_executable(netconf-cli
src/main.cpp
)
-target_link_libraries(netconf-cli docopt parser)
+target_link_libraries(netconf-cli yangschema docopt parser)
add_dependencies(netconf-cli target-NETCONF_CLI_VERSION)
target_include_directories(netconf-cli PRIVATE ${PROJECT_BINARY_DIR})
@@ -118,6 +126,8 @@
cli_test(ls)
cli_test(presence_containers)
cli_test(leaf_editing)
+ cli_test(yang)
+ target_link_libraries(test_yang yangschema)
endif()
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
new file mode 100644
index 0000000..8cccf23
--- /dev/null
+++ b/src/yang_schema.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include <libyang/Libyang.hpp>
+#include <libyang/Tree_Schema.hpp>
+#include <string_view>
+#include "utils.hpp"
+#include "yang_schema.hpp"
+
+class YangLoadError : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+ ~YangLoadError() override = default;
+};
+
+class UnsupportedYangTypeException : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+ ~UnsupportedYangTypeException() override = default;
+};
+
+class InvalidSchemaQueryException : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+ ~InvalidSchemaQueryException() override = default;
+};
+
+std::string pathToYangAbsSchemPath(const path_& path)
+{
+ std::string res = "/";
+ std::string currentModule;
+
+ for (const auto& it : path.m_nodes) {
+ const auto name = nodeToSchemaString(it);
+
+ if (it.m_suffix.type() == typeid(module_)) {
+ currentModule = name;
+ continue;
+ } else {
+ res += currentModule + ":";
+ res += name + "/";
+ }
+ }
+
+ return res;
+}
+
+YangSchema::YangSchema()
+{
+ m_context = std::make_shared<Context>();
+}
+
+YangSchema::~YangSchema() = default;
+
+void YangSchema::addSchemaString(const char* schema)
+{
+ if (!m_context->parse_module_mem(schema, LYS_IN_YANG)) {
+ throw YangLoadError("Couldn't load schema");
+ }
+}
+
+void YangSchema::addSchemaDirectory(const char* directoryName)
+{
+ if (m_context->set_searchdir(directoryName)) {
+ throw YangLoadError("Couldn't add schema search directory");
+ }
+}
+
+void YangSchema::addSchemaFile(const char* filename)
+{
+ if (!m_context->parse_module_path(filename, LYS_IN_YANG)) {
+ throw YangLoadError("Couldn't load schema");
+ }
+}
+
+bool YangSchema::isModule(const path_&, const std::string& name) const
+{
+ const auto set = modules();
+ return set.find(name) != set.end();
+}
+
+bool YangSchema::isContainer(const path_& location, const ModuleNodePair& node) const
+{
+ const auto schemaNode = getSchemaNode(location, node);
+ return schemaNode && schemaNode->nodetype() == LYS_CONTAINER;
+}
+
+bool YangSchema::isLeaf(const path_& location, const ModuleNodePair& node) const
+{
+ const auto schemaNode = getSchemaNode(location, node);
+ return schemaNode && schemaNode->nodetype() == LYS_LEAF;
+}
+
+bool YangSchema::isList(const path_& location, const ModuleNodePair& node) const
+{
+ const auto schemaNode = getSchemaNode(location, node);
+ return schemaNode && schemaNode->nodetype() == LYS_LIST;
+}
+
+bool YangSchema::isPresenceContainer(const path_& location, const ModuleNodePair& node) const
+{
+ if (!isContainer(location, node))
+ return false;
+ return Schema_Node_Container(getSchemaNode(location, node)).presence();
+}
+
+bool YangSchema::leafEnumHasValue(const path_& location, const ModuleNodePair& node, const std::string& value) const
+{
+ if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::Enum)
+ return false;
+
+ Schema_Node_Leaf leaf(getSchemaNode(location, node));
+ const auto enm = std::unique_ptr<std::vector<S_Type_Enum>>(leaf.type()->info()->enums()->enm());
+
+ return std::any_of(enm->begin(), enm->end(), [=](const auto& x) { return x->name() == value; });
+}
+
+bool YangSchema::listHasKey(const path_& location, const ModuleNodePair& node, const std::string& key) const
+{
+ if (!isList(location, node))
+ return false;
+ const auto keys = listKeys(location, node);
+ return keys.find(key) != keys.end();
+}
+
+bool YangSchema::nodeExists(const std::string& location, const std::string& node) const
+{
+ const auto absPath = location + "/" + node;
+ const auto set = m_context->find_path(absPath.c_str());
+ return set->number() == 1;
+}
+
+S_Set YangSchema::getNodeSet(const path_& location, const ModuleNodePair& node) const
+{
+ std::string absPath = location.m_nodes.empty() ? "" : "/";
+ absPath += pathToAbsoluteSchemaString(location) + "/" + fullNodeName(location, node);
+ return m_context->find_path(absPath.c_str());
+}
+
+std::shared_ptr<Schema_Node> YangSchema::getSchemaNode(const path_& location, const ModuleNodePair& node) const
+{
+ const auto set = getNodeSet(location, node);
+ if (!set)
+ return nullptr;
+ const auto schemaSet = std::unique_ptr<std::vector<std::shared_ptr<Schema_Node>>>(set->schema());
+ if (set->number() != 1)
+ return nullptr;
+ return (*(schemaSet->begin()));
+}
+
+const std::set<std::string> YangSchema::listKeys(const path_& location, const ModuleNodePair& node) const
+{
+ std::set<std::string> keys;
+ if (!isList(location, node))
+ return keys;
+ Schema_Node_List list(getSchemaNode(location, node));
+ const auto keysVec = std::unique_ptr<std::vector<S_Schema_Node_Leaf>>(list.keys());
+
+ std::transform(keysVec->begin(), keysVec->end(), std::inserter(keys, keys.begin()),
+ [] (const auto& it) {return it->name();});
+ return keys;
+}
+
+yang::LeafDataTypes YangSchema::leafType(const path_& location, const ModuleNodePair& node) const
+{
+ using namespace std::string_literals;
+ if (!isLeaf(location, node))
+ throw InvalidSchemaQueryException(fullNodeName(location, node) + " is not a leaf");
+
+ Schema_Node_Leaf leaf(getSchemaNode(location, node));
+ switch (leaf.type()->base()) {
+ case LY_TYPE_STRING:
+ return yang::LeafDataTypes::String;
+ case LY_TYPE_DEC64:
+ return yang::LeafDataTypes::Decimal;
+ case LY_TYPE_BOOL:
+ return yang::LeafDataTypes::Bool;
+ case LY_TYPE_INT32:
+ return yang::LeafDataTypes::Int;
+ case LY_TYPE_UINT32:
+ return yang::LeafDataTypes::Uint;
+ case LY_TYPE_ENUM:
+ return yang::LeafDataTypes::Enum;
+ default:
+ throw UnsupportedYangTypeException("the type of "s + fullNodeName(location, node) + " is not supported");
+ }
+}
+
+std::set<std::string> YangSchema::modules() const
+{
+ const auto modules = std::unique_ptr<std::vector<S_Module>>(m_context->get_module_iter());
+
+ std::set<std::string> res;
+ std::transform(modules->begin(), modules->end(),
+ std::inserter(res, res.end()),
+ [] (const auto module) { return module->name(); });
+ return res;
+}
+
+std::set<std::string> YangSchema::childNodes(const path_& path) const
+{
+ using namespace std::string_view_literals;
+ std::set<std::string> res;
+ if (path.m_nodes.empty()) {
+ const auto nodeVec = std::unique_ptr<std::vector<S_Schema_Node>>(m_context->data_instantiables(0));
+ for (const auto it : *nodeVec) {
+ if (it->module()->name() == "ietf-yang-library"sv)
+ continue;
+ res.insert(std::string(it->module()->name()) + ":" + it->name());
+ }
+ } else {
+ const auto absolutePath = "/" + pathToAbsoluteSchemaString(path);
+ const auto set = m_context->find_path(absolutePath.c_str());
+ const auto schemaSet = std::unique_ptr<std::vector<std::shared_ptr<Schema_Node>>>(set->schema());
+ for (auto it = (*(schemaSet->begin()))->child(); it; it = it->next()) {
+ res.insert(std::string(it->module()->name()) + ":" + it->name());
+ }
+ }
+ return res;
+}
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
new file mode 100644
index 0000000..e30c701
--- /dev/null
+++ b/src/yang_schema.hpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#pragma once
+
+#include <set>
+#include <stdexcept>
+#include <unordered_map>
+#include "ast_path.hpp"
+#include "schema.hpp"
+
+class Context;
+class Set;
+class Schema_Node;
+
+/*! \class YangSchema
+ * \brief A schema class, which uses libyang for queries.
+ * */
+class YangSchema : public Schema {
+public:
+ YangSchema();
+ ~YangSchema() override;
+
+ bool isContainer(const path_& location, const ModuleNodePair& node) const override;
+ bool isLeaf(const path_& location, const ModuleNodePair& node) const override;
+ bool isModule(const path_& location, const std::string& name) const override;
+ bool isList(const path_& location, const ModuleNodePair& node) const override;
+ bool isPresenceContainer(const path_& location, const ModuleNodePair& node) const override;
+ bool leafEnumHasValue(const path_& location, const ModuleNodePair& node, const std::string& value) const override;
+ bool listHasKey(const path_& location, const ModuleNodePair& node, const std::string& key) const override;
+ bool nodeExists(const std::string& location, const std::string& node) const override;
+ const std::set<std::string> listKeys(const path_& location, const ModuleNodePair& node) const override;
+ yang::LeafDataTypes leafType(const path_& location, const ModuleNodePair& node) const override;
+ std::set<std::string> childNodes(const path_& path) const override;
+
+ /** @short Adds a new module passed as a YANG string. */
+ void addSchemaString(const char* schema);
+
+ /** @short Adds a new module from a file. */
+ void addSchemaFile(const char* filename);
+
+ /** @short Adds a new directory for schema lookup. */
+ void addSchemaDirectory(const char* directoryName);
+
+private:
+ std::set<std::string> modules() const;
+ bool nodeExists(const path_& location, const ModuleNodePair& node) const;
+
+ /** @short Returns a set of nodes, that match the location and name criteria. */
+ std::shared_ptr<Set> getNodeSet(const path_& location, const ModuleNodePair& node) const;
+
+ /** @short Returns a single Schema_Node if the criteria matches only one, otherwise nullptr. */
+ std::shared_ptr<Schema_Node> getSchemaNode(const path_& location, const ModuleNodePair& node) const;
+ std::shared_ptr<Context> m_context;
+};
diff --git a/tests/yang.cpp b/tests/yang.cpp
new file mode 100644
index 0000000..09bef03
--- /dev/null
+++ b/tests/yang.cpp
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include "trompeloeil_catch.h"
+#include "yang_schema.hpp"
+
+const char* schema = R"(
+module example-schema {
+ namespace "http://example.com/example-sports";
+ prefix coze;
+
+ container a {
+ container a2 {
+ container a3 {
+ presence true;
+ }
+ }
+
+ leaf leafa {
+ type string;
+ }
+ }
+
+ container b {
+ container b2 {
+ presence true;
+ container b3 {
+ }
+ }
+ }
+
+ leaf leafString {
+ type string;
+ }
+
+ leaf leafDecimal {
+ type decimal64 {
+ fraction-digits 5;
+ }
+ }
+
+ leaf leafBool {
+ type boolean;
+ }
+
+ leaf leafInt {
+ type int32;
+ }
+
+ leaf leafUint {
+ type uint32;
+ }
+
+ leaf leafEnum {
+ type enumeration {
+ enum lol;
+ enum data;
+ enum coze;
+ }
+ }
+
+ list _list {
+ key number;
+
+ leaf number {
+ type int32;
+ }
+
+ container contInList {
+ presence true;
+ }
+ }
+
+ list twoKeyList {
+ key "name number";
+
+ leaf number {
+ type int32;
+ }
+
+ leaf name {
+ type string;
+ }
+ }
+})";
+
+TEST_CASE("yangschema")
+{
+ YangSchema ys;
+ ys.addSchemaString(schema);
+ path_ path;
+ ModuleNodePair node;
+
+ SECTION("positive")
+ {
+ SECTION("isContainer")
+ {
+ SECTION("example-schema:a")
+ {
+ node.first = "example-schema";
+ node.second = "a";
+ }
+
+ SECTION("example-schema:a/a2")
+ {
+ path.m_nodes.push_back(node_(module_{"example-schema"}, container_("a")));
+ node.second = "a2";
+ }
+
+ REQUIRE(ys.isContainer(path, node));
+ }
+ SECTION("isLeaf")
+ {
+ SECTION("example-schema:leafString")
+ {
+ node.first = "example-schema";
+ node.second = "leafString";
+ }
+
+ SECTION("example-schema:a/leafa")
+ {
+ path.m_nodes.push_back(node_(module_{"example-schema"}, container_("a")));
+ node.first = "example-schema";
+ node.second = "leafa";
+ }
+
+ REQUIRE(ys.isLeaf(path, node));
+ }
+ SECTION("isModule")
+ {
+ REQUIRE(ys.isModule(path, "example-schema"));
+ }
+ SECTION("isList")
+ {
+ SECTION("example-schema:_list")
+ {
+ node.first = "example-schema";
+ node.second = "_list";
+ }
+
+ SECTION("example-schema:twoKeyList")
+ {
+ node.first = "example-schema";
+ node.second = "twoKeyList";
+ }
+
+ REQUIRE(ys.isList(path, node));
+ }
+ SECTION("isPresenceContainer")
+ {
+ SECTION("example-schema:a/a2/a3")
+ {
+ path.m_nodes.push_back(node_(module_{"example-schema"}, container_("a")));
+ path.m_nodes.push_back(node_(module_{"example-schema"}, container_("a2")));
+ node.second = "a3";
+ }
+
+ REQUIRE(ys.isPresenceContainer(path, node));
+ }
+ SECTION("leafEnumHasValue")
+ {
+ node.first = "example-schema";
+ node.second = "leafEnum";
+ std::string value;
+
+ SECTION("lol")
+ value = "lol";
+
+ SECTION("data")
+ value = "data";
+
+ SECTION("coze")
+ value = "coze";
+
+ REQUIRE(ys.leafEnumHasValue(path, node, value));
+ }
+ SECTION("listHasKey")
+ {
+ std::string key;
+
+ SECTION("_list")
+ {
+ node.first = "example-schema";
+ node.second = "_list";
+ SECTION("number")
+ key = "number";
+ }
+
+ SECTION("twoKeyList")
+ {
+ node.first = "example-schema";
+ node.second = "twoKeyList";
+ SECTION("number")
+ key = "number";
+ SECTION("name")
+ key = "name";
+ }
+
+ REQUIRE(ys.listHasKey(path, node, key));
+ }
+ SECTION("listKeys")
+ {
+ std::set<std::string> set;
+
+ SECTION("_list")
+ {
+ set = {"number"};
+ node.first = "example-schema";
+ node.second = "_list";
+ }
+
+ SECTION("twoKeyList")
+ {
+ set = {"number", "name"};
+ node.first = "example-schema";
+ node.second = "twoKeyList";
+ }
+
+ REQUIRE(ys.listKeys(path, node) == set);
+ }
+ SECTION("leafType")
+ {
+ yang::LeafDataTypes type;
+
+ SECTION("leafString")
+ {
+ node.first = "example-schema";
+ node.second = "leafString";
+ type = yang::LeafDataTypes::String;
+ }
+
+ SECTION("leafDecimal")
+ {
+ node.first = "example-schema";
+ node.second = "leafDecimal";
+ type = yang::LeafDataTypes::Decimal;
+ }
+
+ SECTION("leafBool")
+ {
+ node.first = "example-schema";
+ node.second = "leafBool";
+ type = yang::LeafDataTypes::Bool;
+ }
+
+ SECTION("leafInt")
+ {
+ node.first = "example-schema";
+ node.second = "leafInt";
+ type = yang::LeafDataTypes::Int;
+ }
+
+ SECTION("leafUint")
+ {
+ node.first = "example-schema";
+ node.second = "leafUint";
+ type = yang::LeafDataTypes::Uint;
+ }
+
+ SECTION("leafEnum")
+ {
+ node.first = "example-schema";
+ node.second = "leafEnum";
+ type = yang::LeafDataTypes::Enum;
+ }
+
+ REQUIRE(ys.leafType(path, node) == type);
+ }
+ SECTION("childNodes")
+ {
+ std::set<std::string> set;
+
+ SECTION("<root>")
+ {
+ set = {"example-schema:a", "example-schema:b", "example-schema:leafString",
+ "example-schema:leafDecimal", "example-schema:leafBool", "example-schema:leafInt",
+ "example-schema:leafUint", "example-schema:leafEnum", "example-schema:_list", "example-schema:twoKeyList"};
+ }
+
+ SECTION("a")
+ {
+ path.m_nodes.push_back(node_(module_{"example-schema"}, container_("a")));
+ set = {"example-schema:a2", "example-schema:leafa"};
+ }
+
+ REQUIRE(ys.childNodes(path) == set);
+ }
+ }
+
+ SECTION("negative")
+ {
+ SECTION("nonexistent nodes")
+ {
+ SECTION("example-schema:coze")
+ {
+ node.first = "example-schema";
+ node.second = "coze";
+ }
+
+ SECTION("example-schema:a/nevim")
+ {
+ path.m_nodes.push_back(node_(module_{"example-schema"}, container_("a")));
+ node.second = "nevim";
+ }
+
+ SECTION("modul:a/nevim")
+ {
+ path.m_nodes.push_back(node_(module_{"modul"}, container_("a")));
+ node.second = "nevim";
+ }
+
+ REQUIRE(!ys.isPresenceContainer(path, node));
+ REQUIRE(!ys.isList(path, node));
+ REQUIRE(!ys.isLeaf(path, node));
+ REQUIRE(!ys.isContainer(path, node));
+ }
+
+ SECTION("\"is\" methods return false for existing nodes for different nodetypes")
+ {
+ SECTION("example-schema:a")
+ {
+ node.first = "example-schema";
+ node.second = "a";
+ }
+
+ SECTION("example-schema:a/a2")
+ {
+ path.m_nodes.push_back(node_(module_{"example-schema"}, container_("a")));
+ node.second = "a2";
+ }
+
+ REQUIRE(!ys.isPresenceContainer(path, node));
+ REQUIRE(!ys.isList(path, node));
+ REQUIRE(!ys.isLeaf(path, node));
+ }
+
+ SECTION("nodetype-specific methods called with different nodetypes")
+ {
+ path.m_nodes.push_back(node_(module_{"example-schema"}, container_("a")));
+ node.second = "a2";
+
+ REQUIRE(!ys.leafEnumHasValue(path, node, "haha"));
+ REQUIRE(!ys.listHasKey(path, node, "chacha"));
+ }
+
+ SECTION("nonexistent module")
+ {
+ REQUIRE(!ys.isModule(path, "notAModule"));
+ }
+ }
+}