Migrate to libyang2

Explanation of some of the changes:
1) New libyang produces different schema paths, that don't include
   choice/case nodes. This can be seen in Firewall.cpp.
2) New sysrepo does not use <map>, so it has to be included at multiple
   places.
3) getUniqueSubtree is now just one line of code. Another commit can get
   rid of it.
4) dataFromSysrepo sometimes gives less and sometimes more data. This is
   because it now uses libyang instead of sr_val_t
   - When it gives more data it's usually just lists or empty containers,
     sr_val_t didn't give those.
   - When it gives less data it's also just empty containers. This can
     be seen with "sensor-data" in hardware_ietf-hardware.cpp.

Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/5171
Change-Id: I388536269e790b8b74ea7791c79b180adc5d80a6
Co-authored-by: Jan Kundrát <jan.kundrat@cesnet.cz>
diff --git a/src/utils/libyang.cpp b/src/utils/libyang.cpp
index a4b7479..d58822c 100644
--- a/src/utils/libyang.cpp
+++ b/src/utils/libyang.cpp
@@ -1,29 +1,30 @@
 #include <fmt/format.h>
-#include <libyang/Tree_Data.hpp>
+#include <libyang-cpp/DataNode.hpp>
+#include <libyang-cpp/Set.hpp>
 #include "utils/libyang.h"
 
 namespace velia::utils {
 
-const char* getValueAsString(const libyang::S_Data_Node& node)
+const char* getValueAsString(const libyang::DataNode& node)
 {
-    if (!node || node->schema()->nodetype() != LYS_LEAF) {
+    if (node.schema().nodeType() != libyang::NodeType::Leaf) {
         throw std::logic_error("retrieveString: invalid node");
     }
 
-    return libyang::Data_Node_Leaf_List(node).value_str();
+    return node.asTerm().valueStr().data();
 }
 
-std::optional<libyang::S_Data_Node> getUniqueSubtree(const libyang::S_Data_Node& start, const char* path)
+std::optional<libyang::DataNode> getUniqueSubtree(const libyang::DataNode& start, const char* path)
 {
-    auto set = start->find_path(path);
+    auto set = start.findXPath(path);
 
-    switch(set->number()) {
+    switch(set.size()) {
     case 0:
         return std::nullopt;
     case 1:
-        return set->data().front();
+        return set.front();
     default:
-        throw std::runtime_error(fmt::format("getUniqueSubtree({}, {}): more than one match (got {})", start->path(), path, set->number()));
+        throw std::runtime_error(fmt::format("getUniqueSubtree({}, {}): more than one match (got {})", start.path().get().get(), path, set.size()));
     }
 }
 }
diff --git a/src/utils/libyang.h b/src/utils/libyang.h
index 389c1ac..678d736 100644
--- a/src/utils/libyang.h
+++ b/src/utils/libyang.h
@@ -10,7 +10,7 @@
 #include <optional>
 
 namespace libyang {
-    class Data_Node;
+    class DataNode;
 }
 
 namespace velia::utils {
@@ -21,11 +21,12 @@
  * @param node A libyang data node. Mustn't be nullptr. Must be a leaf.
  *
  */
-const char* getValueAsString(const std::shared_ptr<libyang::Data_Node>& node);
+const char* getValueAsString(const libyang::DataNode& node);
 
-/** @brief Gets exactly one node based on `path` starting from `start`.
+/** @brief Gets exactly one node based on `path` starting from `start`. Uses findXPath, so it works even with lists with
+ * missing predicates.
  *
  * Throws if there is more than one matching node. Returns std::nullopt if no node matches.
  */
-std::optional<std::shared_ptr<libyang::Data_Node>> getUniqueSubtree(const std::shared_ptr<libyang::Data_Node>& start, const char* path);
+std::optional<libyang::DataNode> getUniqueSubtree(const libyang::DataNode& start, const char* path);
 }
diff --git a/src/utils/sysrepo.cpp b/src/utils/sysrepo.cpp
index 481c2b5..3a3b511 100644
--- a/src/utils/sysrepo.cpp
+++ b/src/utils/sysrepo.cpp
@@ -64,114 +64,98 @@
     sr_log_set_cb(spdlog_sr_log_cb);
 }
 
-void valuesToYang(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, std::shared_ptr<libyang::Data_Node>& parent)
+void valuesToYang(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session, std::optional<libyang::DataNode>& parent)
 {
     valuesToYang(mapToVector(values), removePaths, std::move(session), parent);
 }
 
-void valuesToYang(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, std::shared_ptr<libyang::Data_Node>& parent)
+void valuesToYang(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session, std::optional<libyang::DataNode>& parent)
 {
-    auto netconf = session->get_context()->get_module("ietf-netconf");
+    auto netconf = session.getContext().getModuleImplemented("ietf-netconf");
     auto log = spdlog::get("main");
 
     for (const auto& propertyName : removePaths) {
         log->trace("Processing node deletion {}", propertyName);
 
         if (!parent) {
-            parent = std::make_shared<libyang::Data_Node>(
-                session->get_context(),
-                propertyName.c_str(),
-                nullptr,
-                LYD_ANYDATA_CONSTSTRING,
-                LYD_PATH_OPT_EDIT);
+            parent = session.getContext().newPath(propertyName.c_str(), nullptr, libyang::CreationOptions::Opaque);
         } else {
-            parent->new_path(
-                session->get_context(),
-                propertyName.c_str(),
-                nullptr,
-                LYD_ANYDATA_CONSTSTRING,
-                LYD_PATH_OPT_EDIT);
+            parent->newPath(propertyName.c_str(), nullptr, libyang::CreationOptions::Opaque);
         }
 
-        auto deletion = parent->find_path(propertyName.c_str());
-        if (deletion->number() != 1) {
+        auto deletion = parent->findPath(propertyName.c_str());
+        if (!deletion) {
             throw std::logic_error {"Cannot find XPath " + propertyName + " for deletion in libyang's new_path() output"};
         }
-        deletion->data()[0]->insert_attr(netconf, "operation", "remove");
+        deletion->newMeta(*netconf, "operation", "remove");
     }
 
     for (const auto& [propertyName, value] : values) {
         log->trace("Processing node update {} -> {}", propertyName, value);
 
         if (!parent) {
-            parent = std::make_shared<libyang::Data_Node>(
-                session->get_context(),
-                propertyName.c_str(),
-                value.c_str(),
-                LYD_ANYDATA_CONSTSTRING,
-                LYD_PATH_OPT_OUTPUT);
+            parent = session.getContext().newPath(propertyName.c_str(), value.c_str(), libyang::CreationOptions::Output);
         } else {
-            parent->new_path(
-                session->get_context(),
-                propertyName.c_str(),
-                value.c_str(),
-                LYD_ANYDATA_CONSTSTRING,
-                LYD_PATH_OPT_OUTPUT);
+            parent->newPath(propertyName.c_str(), value.c_str(), libyang::CreationOptions::Output);
         }
     }
 }
 
 /** @brief Set or remove values in Sysrepo's specified datastore. It changes the datastore and after the data are applied, the original datastore is restored. */
-void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, sr_datastore_t datastore)
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session, sysrepo::Datastore datastore)
 {
-    sr_datastore_t oldDatastore = session->session_get_ds();
-    session->session_switch_ds(datastore);
+    auto oldDatastore = session.activeDatastore();
+    session.switchDatastore(datastore);
 
     valuesPush(values, removePaths, session);
 
-    session->session_switch_ds(oldDatastore);
+    session.switchDatastore(oldDatastore);
 }
 
 /** @brief Set or remove paths in Sysrepo's current datastore. */
-void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session)
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session)
 {
     if (values.empty() && removePaths.empty()) return;
 
-    libyang::S_Data_Node edit;
+    std::optional<libyang::DataNode> edit;
     valuesToYang(values, removePaths, session, edit);
 
-    session->edit_batch(edit, "merge");
-    session->apply_changes();
+    if (edit) {
+        session.editBatch(*edit, sysrepo::DefaultOperation::Merge);
+        session.applyChanges();
+    }
 }
 
 /** @brief Set or remove values in Sysrepo's specified datastore. It changes the datastore and after the data are applied, the original datastore is restored. */
-void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, sr_datastore_t datastore)
+void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, sysrepo::Session session, sysrepo::Datastore datastore)
 {
-    sr_datastore_t oldDatastore = session->session_get_ds();
-    session->session_switch_ds(datastore);
+    auto oldDatastore = session.activeDatastore();
+    session.switchDatastore(datastore);
 
     valuesPush(values, removePaths, session);
 
-    session->session_switch_ds(oldDatastore);
+    session.switchDatastore(oldDatastore);
 }
 
 /** @brief Set or remove paths in Sysrepo's current datastore. */
-void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session)
+void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, sysrepo::Session session)
 {
     if (values.empty() && removePaths.empty())
         return;
 
-    libyang::S_Data_Node edit;
+    std::optional<libyang::DataNode> edit;
     valuesToYang(values, removePaths, session, edit);
 
-    session->edit_batch(edit, "merge");
-    session->apply_changes();
+    if (edit) {
+    session.editBatch(*edit, sysrepo::DefaultOperation::Merge);
+    session.applyChanges();
+    }
 }
 
 /** @brief Checks whether a module is implemented in Sysrepo and throws if not. */
-void ensureModuleImplemented(std::shared_ptr<::sysrepo::Session> session, const std::string& module, const std::string& revision)
+void ensureModuleImplemented(::sysrepo::Session session, const std::string& module, const std::string& revision)
 {
-    if (auto mod = session->get_context()->get_module(module.c_str(), revision.c_str()); !mod || !mod->implemented()) {
+    if (auto mod = session.getContext().getModule(module.c_str(), revision.c_str()); !mod || !mod->implemented()) {
         throw std::runtime_error(module + "@" + revision + " is not implemented in sysrepo.");
     }
 }
@@ -180,4 +164,17 @@
     , m_value(std::move(value))
 {
 }
+
+void setErrors(::sysrepo::Session session, const std::string& msg)
+{
+    session.setNetconfError(sysrepo::NetconfErrorInfo{
+        .type = "application",
+        .tag = "operation-failed",
+        .appTag = {},
+        .path = {},
+        .message = msg.c_str(),
+        .infoElements = {},
+    });
+    session.setErrorMessage(msg.c_str());
+}
 }
diff --git a/src/utils/sysrepo.h b/src/utils/sysrepo.h
index 69b4df6..5f83352 100644
--- a/src/utils/sysrepo.h
+++ b/src/utils/sysrepo.h
@@ -8,6 +8,7 @@
 #pragma once
 
 #include <sysrepo-cpp/Session.hpp>
+#include <map>
 #include <string>
 
 namespace velia::utils {
@@ -19,15 +20,16 @@
     YANGPair(std::string xpath, std::string value);
 };
 
-void valuesToYang(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, std::shared_ptr<libyang::Data_Node>& parent);
-void valuesToYang(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, std::shared_ptr<libyang::Data_Node>& parent);
+void valuesToYang(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session, std::optional<libyang::DataNode>& parent);
+void valuesToYang(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session, std::optional<libyang::DataNode>& parent);
 
-void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session);
-void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session);
-void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, sr_datastore_t datastore);
-void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, std::shared_ptr<::sysrepo::Session> session, sr_datastore_t datastore);
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session);
+void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session);
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session, sysrepo::Datastore datastore);
+void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, ::sysrepo::Session session, sysrepo::Datastore datastore);
 
 void initLogsSysrepo();
-void ensureModuleImplemented(std::shared_ptr<::sysrepo::Session> session, const std::string& module, const std::string& revision);
+void ensureModuleImplemented(::sysrepo::Session session, const std::string& module, const std::string& revision);
 
+void setErrors(::sysrepo::Session session, const std::string& msg);
 }