health: populate alarms' alarm-inventory container

RFC 8632 says [1] that we MUST populate alarm inventory list with data
about all the alarms that can appear. We don't have many alarms in
velia yet, so this is quite straightforward.

[1] https://www.rfc-editor.org/rfc/rfc8632.html#page-16

Change-Id: Iae1c237cfc0cc54eff835a6c83962598e5bf96ac
diff --git a/src/health/alarms/SystemdUnits.cpp b/src/health/alarms/SystemdUnits.cpp
index c1aac27..241de97 100644
--- a/src/health/alarms/SystemdUnits.cpp
+++ b/src/health/alarms/SystemdUnits.cpp
@@ -12,6 +12,7 @@
 namespace {
 const auto ALARM_ID = "velia-alarms:systemd-unit-failure";
 const auto ALARM_SEVERITY = "critical";
+const auto ALARM_INVENTORY_DESCRIPTION = "The systemd service is considered in failed state.";
 }
 
 namespace velia::health {
@@ -27,13 +28,15 @@
     utils::ensureModuleImplemented(m_srSession, "sysrepo-ietf-alarms", "2022-02-17");
     utils::ensureModuleImplemented(m_srSession, "velia-alarms", "2022-07-12");
 
+    createOrUpdateAlarmInventoryEntry(m_srSession, ALARM_ID, std::nullopt, {ALARM_SEVERITY}, true, ALARM_INVENTORY_DESCRIPTION);
+
     // Subscribe to systemd events. Systemd may not generate signals unless explicitly called
     m_proxyManager->callMethod("Subscribe").onInterface(managerIface).withArguments().dontExpectReply();
 
     // Register to a signal introducing new unit
     m_proxyManager->uponSignal("UnitNew").onInterface(managerIface).call([&](const std::string& unitName, const sdbus::ObjectPath& unitObjectPath) {
         if (m_proxyUnits.find(unitObjectPath) == m_proxyUnits.end()) {
-            registerSystemdUnit(connection, unitName, unitObjectPath);
+            registerSystemdUnit(m_srSession, connection, unitName, unitObjectPath);
         }
     });
     m_proxyManager->finishRegistration();
@@ -48,7 +51,7 @@
         const auto& unitName = unit.get<0>();
         const auto& unitObjectPath = unit.get<6>();
 
-        registerSystemdUnit(connection, unitName, unitObjectPath);
+        registerSystemdUnit(m_srSession, connection, unitName, unitObjectPath);
     }
 }
 
@@ -59,8 +62,10 @@
 }
 
 /** @brief Registers a systemd unit by its unit name and unit dbus objectpath. */
-void SystemdUnits::registerSystemdUnit(sdbus::IConnection& connection, const std::string& unitName, const sdbus::ObjectPath& unitObjectPath)
+void SystemdUnits::registerSystemdUnit(sysrepo::Session session, sdbus::IConnection& connection, const std::string& unitName, const sdbus::ObjectPath& unitObjectPath)
 {
+    addResourceToAlarmInventoryEntry(session, ALARM_ID, std::nullopt, unitName);
+
     auto proxyUnit = sdbus::createProxy(connection, m_busName, unitObjectPath);
     proxyUnit->uponSignal("PropertiesChanged").onInterface("org.freedesktop.DBus.Properties").call([&, unitName](const std::string& iface, const std::map<std::string, sdbus::Variant>& changed, [[maybe_unused]] const std::vector<std::string>& invalidated) {
         if (iface != m_unitIface) {
diff --git a/src/health/alarms/SystemdUnits.h b/src/health/alarms/SystemdUnits.h
index a29ba1f..27f9210 100644
--- a/src/health/alarms/SystemdUnits.h
+++ b/src/health/alarms/SystemdUnits.h
@@ -36,7 +36,7 @@
     /** Current unit state. */
     std::map<std::string, std::pair<std::string, std::string>> m_unitState;
 
-    void registerSystemdUnit(sdbus::IConnection& connection, const std::string& unitName, const sdbus::ObjectPath& unitObjectPath);
+    void registerSystemdUnit(sysrepo::Session session, sdbus::IConnection& connection, const std::string& unitName, const sdbus::ObjectPath& unitObjectPath);
     void onUnitStateChange(const std::string& name, const std::string& activeState, const std::string& nSubState);
 };
 
diff --git a/src/health/alarms/alarms.cpp b/src/health/alarms/alarms.cpp
index e5c6e36..c47dd08 100644
--- a/src/health/alarms/alarms.cpp
+++ b/src/health/alarms/alarms.cpp
@@ -4,11 +4,15 @@
  * Written by Tomáš Pecka <tomas.pecka@fit.cvut.cz>
  *
  */
+#include <sysrepo-cpp/Enum.hpp>
 #include "alarms.h"
+#include "utils/UniqueResource.h"
+#include "utils/libyang.h"
 
 using namespace std::string_literals;
 
 namespace {
+const auto alarmInventory = "/ietf-alarms:alarms/alarm-inventory"s;
 const auto alarmRpc = "/sysrepo-ietf-alarms:create-or-update-alarm";
 }
 
@@ -25,4 +29,46 @@
 
     session.sendRPC(inputNode);
 }
+
+void createOrUpdateAlarmInventoryEntry(sysrepo::Session session, const std::string& alarmId, const std::optional<std::string>& alarmTypeQualifier, const std::vector<std::string>& severities, bool willClear, const std::string& description)
+{
+    const auto prefix = alarmInventory + "/alarm-type[alarm-type-id='" + alarmId + "'][alarm-type-qualifier='" + alarmTypeQualifier.value_or("") + "']";
+
+    sysrepo::Datastore originalDS;
+    auto restoreDatastore = make_unique_resource(
+        [&]() {
+            originalDS = session.activeDatastore();
+            session.switchDatastore(sysrepo::Datastore::Operational);
+        },
+        [&]() {
+            session.switchDatastore(originalDS);
+        });
+
+    session.setItem(prefix + "/will-clear", willClear ? "true" : "false");
+    session.setItem(prefix + "/description", description);
+
+    for (const auto& severity : severities) {
+        session.setItem(prefix + "/severity-level", severity);
+    }
+
+    session.applyChanges();
+}
+
+void addResourceToAlarmInventoryEntry(sysrepo::Session session, const std::string& alarmId, const std::optional<std::string>& alarmTypeQualifier, const std::string& resource)
+{
+    const auto prefix = alarmInventory + "/alarm-type[alarm-type-id='" + alarmId + "'][alarm-type-qualifier='" + alarmTypeQualifier.value_or("") + "']";
+
+    sysrepo::Datastore originalDS;
+    auto restoreDatastore = make_unique_resource(
+        [&]() {
+            originalDS = session.activeDatastore();
+            session.switchDatastore(sysrepo::Datastore::Operational);
+        },
+        [&]() {
+            session.switchDatastore(originalDS);
+        });
+
+    session.setItem(prefix + "/resource", resource);
+    session.applyChanges();
+}
 }
diff --git a/src/health/alarms/alarms.h b/src/health/alarms/alarms.h
index b5cbe73..a10a3db 100644
--- a/src/health/alarms/alarms.h
+++ b/src/health/alarms/alarms.h
@@ -10,4 +10,6 @@
 
 namespace velia::health {
 void createOrUpdateAlarm(sysrepo::Session session, const std::string& alarmId, const std::optional<std::string>& alarmQualifierType, const std::string& alarmResource, const std::string& severity, const std::string& alarmText);
+void createOrUpdateAlarmInventoryEntry(sysrepo::Session session, const std::string& alarmId, const std::optional<std::string>& alarmTypeQualifier, const std::vector<std::string>& severities, bool willClear, const std::string& description);
+void addResourceToAlarmInventoryEntry(sysrepo::Session session, const std::string& alarmId, const std::optional<std::string>& alarmTypeQualifier, const std::string& resource);
 }