Use atomic discards for the operational DS

Port away from sysrepo::Connection::discardOperationalChanges() because
sysrepo now supports discarding via the usual sr_edit_batch(), yay.

The actual changes are slightly different, some high-level leaf-list
instances for example are apparently not reported.

Change-Id: I3c4f7e5aabe65c52f557c4cd060e0a6fbc316e16
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/6243
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/sysrepo-ietf-alarms/+/6248
diff --git a/src/ietf-hardware/sysrepo/Sysrepo.cpp b/src/ietf-hardware/sysrepo/Sysrepo.cpp
index 444a714..b06a72e 100644
--- a/src/ietf-hardware/sysrepo/Sysrepo.cpp
+++ b/src/ietf-hardware/sysrepo/Sysrepo.cpp
@@ -60,11 +60,11 @@
                 }
             }
 
-            for (const auto& component : deletedComponents) {
-                conn.discardOperationalChanges(component);
-            }
+            std::vector<std::string> discards;
+            discards.reserve(deletedComponents.size());
+            std::copy(deletedComponents.begin(), deletedComponents.end(), std::back_inserter(discards));
 
-            utils::valuesPush(hwStateValues, {}, m_session, ::sysrepo::Datastore::Operational);
+            utils::valuesPush(hwStateValues, {}, discards, m_session, ::sysrepo::Datastore::Operational);
 
             prevValues = std::move(hwStateValues);
             std::this_thread::sleep_for(m_pollInterval);
diff --git a/src/system/Firmware.cpp b/src/system/Firmware.cpp
index 9c34aeb..1e6bca5 100644
--- a/src/system/Firmware.cpp
+++ b/src/system/Firmware.cpp
@@ -43,7 +43,7 @@
             std::optional<libyang::DataNode> dataNode;
             auto session = m_srConn.sessionStart();
 
-            utils::valuesToYang(data, {}, session, dataNode);
+            utils::valuesToYang(data, {}, {}, session, dataNode);
             session.sendNotification(*dataNode, sysrepo::Wait::No); // No need to wait, it's just a notification.
         },
         [this](int32_t retVal, const std::string& lastError) {
@@ -131,7 +131,7 @@
             data[CZECHLIGHT_SYSTEM_FIRMWARE_MODULE_PREFIX + "installation/message"] = m_installMessage;
         }
 
-        utils::valuesToYang(data, {}, session, parent);
+        utils::valuesToYang(data, {}, {}, session, parent);
         return ::sysrepo::ErrorCode::Ok;
     };
 
diff --git a/src/system/IETFInterfaces.cpp b/src/system/IETFInterfaces.cpp
index 1aab3f7..0dd3d30 100644
--- a/src/system/IETFInterfaces.cpp
+++ b/src/system/IETFInterfaces.cpp
@@ -204,7 +204,7 @@
             values[yangPrefix + "/out-errors"] = std::to_string(rtnl_link_get_stat(link.get(), RTNL_LINK_TX_ERRORS));
         }
 
-        utils::valuesToYang(values, {}, session, parent);
+        utils::valuesToYang(values, {}, {}, session, parent);
         return sysrepo::ErrorCode::Ok;
     };
 
@@ -212,14 +212,14 @@
 
     m_srSubscribe->onOperGet(
         IETF_INTERFACES_MODULE_NAME, [this](auto session, auto, auto, auto, auto, auto, auto& parent) {
-            utils::valuesToYang(collectNeighboursIP(m_rtnetlink, AF_INET, m_log), {}, session, parent);
+            utils::valuesToYang(collectNeighboursIP(m_rtnetlink, AF_INET, m_log), {}, {}, session, parent);
             return sysrepo::ErrorCode::Ok;
         },
         IETF_INTERFACES + "/interface/ietf-ip:ipv4/neighbor");
 
     m_srSubscribe->onOperGet(
         IETF_INTERFACES_MODULE_NAME, [this](auto session, auto, auto, auto, auto, auto, auto& parent) {
-            utils::valuesToYang(collectNeighboursIP(m_rtnetlink, AF_INET6, m_log), {}, session, parent);
+            utils::valuesToYang(collectNeighboursIP(m_rtnetlink, AF_INET6, m_log), {}, {}, session, parent);
             return sysrepo::ErrorCode::Ok;
         },
         IETF_INTERFACES + "/interface/ietf-ip:ipv6/neighbor");
@@ -232,7 +232,7 @@
 
     if (action == NL_ACT_DEL) {
         std::lock_guard<std::mutex> lock(m_mtx);
-        utils::valuesPush(std::vector<utils::YANGPair>{}, {IETF_INTERFACES + "/interface[name='" + name + "']"}, m_srSession, sysrepo::Datastore::Operational);
+        utils::valuesPush(std::vector<utils::YANGPair>{}, {IETF_INTERFACES + "/interface[name='" + name + "']"}, {}, m_srSession, sysrepo::Datastore::Operational);
     } else if (action == NL_ACT_CHANGE || action == NL_ACT_NEW) {
         std::map<std::string, std::string> values;
         std::vector<std::string> deletePaths;
@@ -252,7 +252,7 @@
         values[IETF_INTERFACES + "/interface[name='" + name + "']/oper-status"] = operStatusToString(rtnl_link_get_operstate(link), m_log);
 
         std::lock_guard<std::mutex> lock(m_mtx);
-        utils::valuesPush(values, deletePaths, m_srSession, sysrepo::Datastore::Operational);
+        utils::valuesPush(values, deletePaths, {}, m_srSession, sysrepo::Datastore::Operational);
     } else {
         m_log->warn("Unhandled cache update action {} ({})", action, nlActionToString(action));
     }
@@ -287,7 +287,7 @@
     }
 
     std::lock_guard<std::mutex> lock(m_mtx);
-    utils::valuesPush(values, deletePaths, m_srSession, sysrepo::Datastore::Operational);
+    utils::valuesPush(values, deletePaths, {}, m_srSession, sysrepo::Datastore::Operational);
 }
 
 void IETFInterfaces::onRouteUpdate(rtnl_route*, int)
@@ -404,6 +404,6 @@
     }
 
     std::lock_guard<std::mutex> lock(m_mtx);
-    utils::valuesPush(values, {}, m_srSession, sysrepo::Datastore::Operational);
+    utils::valuesPush(values, {}, {}, m_srSession, sysrepo::Datastore::Operational);
 }
 }
diff --git a/src/system/IETFSystem.cpp b/src/system/IETFSystem.cpp
index 8595465..5439d09 100644
--- a/src/system/IETFSystem.cpp
+++ b/src/system/IETFSystem.cpp
@@ -132,7 +132,7 @@
         {IETF_SYSTEM_STATE_MODULE_PREFIX + "platform/os-version", osReleaseContents.at("VERSION")},
     };
 
-    utils::valuesPush(opsSystemStateData, {}, m_srSession, sysrepo::Datastore::Operational);
+    utils::valuesPush(opsSystemStateData, {}, {}, m_srSession, sysrepo::Datastore::Operational);
 }
 
 void IETFSystem::initSystemRestart()
@@ -222,7 +222,7 @@
             values[IETF_SYSTEM_DNS_PATH + "/server[name='"s + e + "']/udp-and-tcp/address"] = e;
         }
 
-        utils::valuesToYang(values, {}, session, parent);
+        utils::valuesToYang(values, {}, {}, session, parent);
         return sysrepo::ErrorCode::Ok;
     };
 
diff --git a/src/system/LED.cpp b/src/system/LED.cpp
index 07a9843..35ac43d 100644
--- a/src/system/LED.cpp
+++ b/src/system/LED.cpp
@@ -104,7 +104,7 @@
             }
         }
 
-        utils::valuesPush(data, {}, m_srSession, sysrepo::Datastore::Operational);
+        utils::valuesPush(data, {}, {}, m_srSession, sysrepo::Datastore::Operational);
 
         std::this_thread::sleep_for(POLL_INTERVAL);
     }
diff --git a/src/utils/sysrepo.cpp b/src/utils/sysrepo.cpp
index ddafbac..0147b49 100644
--- a/src/utils/sysrepo.cpp
+++ b/src/utils/sysrepo.cpp
@@ -64,12 +64,12 @@
     sr_log_set_cb(spdlog_sr_log_cb);
 }
 
-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 valuesToYang(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session, std::optional<libyang::DataNode>& parent)
 {
-    valuesToYang(mapToVector(values), removePaths, std::move(session), parent);
+    valuesToYang(mapToVector(values), removePaths, discardPaths, std::move(session), 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::vector<YANGPair>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session, std::optional<libyang::DataNode>& parent)
 {
     auto netconf = session.getContext().getModuleImplemented("ietf-netconf");
     auto log = spdlog::get("main");
@@ -99,26 +99,39 @@
             parent->newPath(propertyName, value, libyang::CreationOptions::Output);
         }
     }
+
+    for (const auto& propertyName : discardPaths) {
+        log->trace("Processing node discard {}", propertyName);
+
+        auto discard = session.getContext().newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{propertyName});
+
+        if (!parent) {
+            parent = discard;
+        } else {
+            parent->insertSibling(*discard);
+            parent->newPath(propertyName, std::nullopt, libyang::CreationOptions::Opaque);
+        }
+    }
 }
 
 /** @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, ::sysrepo::Session session, sysrepo::Datastore datastore)
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session, sysrepo::Datastore datastore)
 {
     auto oldDatastore = session.activeDatastore();
     session.switchDatastore(datastore);
 
-    valuesPush(values, removePaths, session);
+    valuesPush(values, removePaths, discardPaths, session);
 
     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, ::sysrepo::Session session)
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session)
 {
     if (values.empty() && removePaths.empty()) return;
 
     std::optional<libyang::DataNode> edit;
-    valuesToYang(values, removePaths, session, edit);
+    valuesToYang(values, removePaths, discardPaths, session, edit);
 
     if (edit) {
         session.editBatch(*edit, sysrepo::DefaultOperation::Merge);
@@ -127,24 +140,24 @@
 }
 
 /** @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, sysrepo::Session session, sysrepo::Datastore datastore)
+void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, sysrepo::Session session, sysrepo::Datastore datastore)
 {
     auto oldDatastore = session.activeDatastore();
     session.switchDatastore(datastore);
 
-    valuesPush(values, removePaths, session);
+    valuesPush(values, removePaths, discardPaths, session);
 
     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, sysrepo::Session session)
+void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, sysrepo::Session session)
 {
     if (values.empty() && removePaths.empty())
         return;
 
     std::optional<libyang::DataNode> edit;
-    valuesToYang(values, removePaths, session, edit);
+    valuesToYang(values, removePaths, discardPaths, session, edit);
 
     if (edit) {
         session.editBatch(*edit, sysrepo::DefaultOperation::Merge);
diff --git a/src/utils/sysrepo.h b/src/utils/sysrepo.h
index 5f83352..7f8a244 100644
--- a/src/utils/sysrepo.h
+++ b/src/utils/sysrepo.h
@@ -20,13 +20,13 @@
     YANGPair(std::string xpath, std::string value);
 };
 
-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 valuesToYang(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session, std::optional<libyang::DataNode>& parent);
+void valuesToYang(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session, std::optional<libyang::DataNode>& parent);
 
-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 valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session);
+void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session);
+void valuesPush(const std::map<std::string, std::string>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session, sysrepo::Datastore datastore);
+void valuesPush(const std::vector<YANGPair>& values, const std::vector<std::string>& removePaths, const std::vector<std::string>& discardPaths, ::sysrepo::Session session, sysrepo::Datastore datastore);
 
 void initLogsSysrepo();
 void ensureModuleImplemented(::sysrepo::Session session, const std::string& module, const std::string& revision);