hardware: push new discovered alarm resources in batches

Another part of optimizing alarm-inventory accesses. Instead of
performing 4 updates, we now perform only one.

Change-Id: Ic70fd0e1ce947127610ba58572aafc06ac2e92cd
diff --git a/src/health/SystemdUnits.cpp b/src/health/SystemdUnits.cpp
index fe5ed31..105c56a 100644
--- a/src/health/SystemdUnits.cpp
+++ b/src/health/SystemdUnits.cpp
@@ -83,7 +83,7 @@
         }
 
         if (registerAlarmInventory == RegisterAlarmInventory::Yes) {
-            alarms::addResourcesToInventory(m_srSession, ALARM_ID, {unitName});
+            alarms::addResourcesToInventory(m_srSession, {{ALARM_ID, {unitName}}});
         }
 
         proxyUnit = m_proxyUnits.emplace(unitObjectPath, sdbus::createProxy(connection, m_busName, unitObjectPath)).first->second.get();
diff --git a/src/ietf-hardware/sysrepo/Sysrepo.cpp b/src/ietf-hardware/sysrepo/Sysrepo.cpp
index 8b830ce..02e6720 100644
--- a/src/ietf-hardware/sysrepo/Sysrepo.cpp
+++ b/src/ietf-hardware/sysrepo/Sysrepo.cpp
@@ -115,10 +115,12 @@
             seenSensors.merge(activeSensors);
 
             if (!newSensors.empty()) {
-                alarms::addResourcesToInventory(m_session, ALARM_THRESHOLD_CROSSING_LOW, newSensors);
-                alarms::addResourcesToInventory(m_session, ALARM_THRESHOLD_CROSSING_HIGH, newSensors);
-                alarms::addResourcesToInventory(m_session, ALARM_SENSOR_MISSING, newSensors);
-                alarms::addResourcesToInventory(m_session, ALARM_SENSOR_NONOPERATIONAL, newSensors);
+                alarms::addResourcesToInventory(m_session, {
+                                   {ALARM_THRESHOLD_CROSSING_LOW, newSensors},
+                                   {ALARM_THRESHOLD_CROSSING_HIGH, newSensors},
+                                   {ALARM_SENSOR_MISSING, newSensors},
+                                   {ALARM_SENSOR_NONOPERATIONAL, newSensors},
+                               });
             }
 
             /* Some data readers can stop returning data in some cases (e.g. ejected PSU).
@@ -139,7 +141,7 @@
             /* Publish sideloaded alarms */
             for (const auto& [alarm, resource, severity, text] : sideLoadedAlarms) {
                 // Sideloaded alarms are not registered using the code above, let's register those too
-                alarms::addResourcesToInventory(m_session, ALARM_SENSOR_MISSING, {resource});
+                alarms::addResourcesToInventory(m_session, {{ALARM_SENSOR_MISSING, {resource}}});
 
                 bool isActive = activeSideLoadedAlarms.contains({alarm, resource});
                 if (isActive && severity == "cleared") {
@@ -221,7 +223,7 @@
 
             prevValues = std::move(hwStateValues);
             std::this_thread::sleep_for(m_pollInterval);
-        }
+            }
     });
 }
 
diff --git a/src/utils/alarms.cpp b/src/utils/alarms.cpp
index 8bbcf96..f079f8d 100644
--- a/src/utils/alarms.cpp
+++ b/src/utils/alarms.cpp
@@ -50,14 +50,16 @@
     session.applyChanges();
 }
 
-void addResourcesToInventory(sysrepo::Session session, const std::string& alarmId, const std::vector<std::string>& resources)
+void addResourcesToInventory(sysrepo::Session session, const std::map<std::string, std::vector<std::string>>& resourcesPerAlarm)
 {
-    const auto prefix = alarmInventory + "/alarm-type[alarm-type-id='" + alarmId + "'][alarm-type-qualifier='']";
-
     utils::ScopedDatastoreSwitch s(session, sysrepo::Datastore::Operational);
 
-    for (const auto& resource : resources) {
-        session.setItem(prefix + "/resource", resource);
+    for (const auto& [alarmId, resources] : resourcesPerAlarm) {
+        const auto prefix = alarmInventory + "/alarm-type[alarm-type-id='" + alarmId + "'][alarm-type-qualifier='']";
+
+        for (const auto& resource : resources) {
+            session.setItem(prefix + "/resource", resource);
+        }
     }
     session.applyChanges();
 }
diff --git a/src/utils/alarms.h b/src/utils/alarms.h
index 7749a62..9f5cbbc 100644
--- a/src/utils/alarms.h
+++ b/src/utils/alarms.h
@@ -4,6 +4,7 @@
  * Written by Tomáš Pecka <tomas.pecka@fit.cvut.cz>
  *
  */
+#include <map>
 #include <optional>
 #include <string>
 #include <sysrepo-cpp/Session.hpp>
@@ -16,5 +17,5 @@
 
 void push(sysrepo::Session session, const std::string& alarmId, const std::string& alarmResource, const std::string& severity, const std::string& alarmText);
 void pushInventory(sysrepo::Session session, const std::string& alarmId, const std::string& description, const std::vector<std::string>& resources, const std::vector<std::string>& severities = {}, WillClear willClear = WillClear::Yes);
-void addResourcesToInventory(sysrepo::Session session, const std::string& alarmId, const std::vector<std::string>& resources);
+void addResourcesToInventory(sysrepo::Session session, const std::map<std::string, std::vector<std::string>>& resourcesPerAlarm);
 }
diff --git a/tests/sysrepo_ietf-hardware.cpp b/tests/sysrepo_ietf-hardware.cpp
index cbdbfbb..c3689da 100644
--- a/tests/sysrepo_ietf-hardware.cpp
+++ b/tests/sysrepo_ietf-hardware.cpp
@@ -19,8 +19,11 @@
     REQUIRE_NEW_ALARM_INVENTORY_ENTRY(alarmWatcher, ALARM_TYPE, (std::vector<std::string>{}), \
             (std::vector<std::string>{}), true, DESCRIPTION)
 
-#define REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(ALARM_TYPE, ...) \
-    REQUIRE_NEW_ALARM_INVENTORY_RESOURCES(alarmWatcher, (std::vector<std::string>{ALARM_TYPE}), (std::vector<std::string>{__VA_ARGS__}))
+#define VEC(...) (std::vector<std::string>{__VA_ARGS__})
+#define ALARMS(...) VEC(__VA_ARGS__)
+#define COMPONENTS(...) VEC(__VA_ARGS__)
+
+#define REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(ALARMS, COMPONENTS) REQUIRE_NEW_ALARM_INVENTORY_RESOURCES(alarmWatcher, ALARMS, COMPONENTS)
 
 #define REQUIRE_ALARM_RPC(ALARM_TYPE, RESOURCE, SEVERITY, TEXT) \
     REQUIRE_NEW_ALARM(alarmWatcher, ALARM_TYPE, COMPONENT(RESOURCE), SEVERITY, TEXT)
@@ -143,10 +146,16 @@
         REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-missing-alarm", "Sensor is missing.").IN_SEQUENCE(seq1);
         REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-nonoperational", "Sensor is flagged as nonoperational.").IN_SEQUENCE(seq1);
 
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-low-value-alarm", COMPONENT("ne:power"), COMPONENT("ne:psu:child"), COMPONENT("ne:temperature-cpu")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-high-value-alarm", COMPONENT("ne:power"), COMPONENT("ne:psu:child"), COMPONENT("ne:temperature-cpu")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-missing-alarm", COMPONENT("ne:power"), COMPONENT("ne:psu:child"), COMPONENT("ne:temperature-cpu")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-nonoperational", COMPONENT("ne:power"), COMPONENT("ne:psu:child"), COMPONENT("ne:temperature-cpu")).IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(
+            ALARMS("velia-alarms:sensor-low-value-alarm",
+                   "velia-alarms:sensor-high-value-alarm",
+                   "velia-alarms:sensor-missing-alarm",
+                   "velia-alarms:sensor-nonoperational"),
+            COMPONENTS(
+                COMPONENT("ne:power"),
+                COMPONENT("ne:psu:child"),
+                COMPONENT("ne:temperature-cpu")))
+            .IN_SEQUENCE(seq1);
 
         REQUIRE_DATASTORE_CHANGE(dsChangeHardware, (ValueChanges{
                                            {COMPONENT("ne") "/class", "iana-hardware:chassis"},
@@ -186,7 +195,7 @@
                                            {COMPONENT("ne:temperature-cpu") "/state/oper-state", "enabled"},
                                        }))
             .IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-missing-alarm", COMPONENT("ne:psu")).TIMES(AT_LEAST(1));
+        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(ALARMS("velia-alarms:sensor-missing-alarm"), COMPONENTS(COMPONENT("ne:psu"))).TIMES(AT_LEAST(1));
         REQUIRE_ALARM_RPC("velia-alarms:sensor-low-value-alarm", "ne:power", "critical", "Sensor value crossed low threshold.").IN_SEQUENCE(seq1);
 
         auto ietfHardwareSysrepo = std::make_shared<velia::ietf_hardware::sysrepo::Sysrepo>(srSess, ietfHardware, 150ms);
@@ -345,10 +354,15 @@
         REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-missing-alarm", "Sensor is missing.").IN_SEQUENCE(seq1);
         REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-nonoperational", "Sensor is flagged as nonoperational.").IN_SEQUENCE(seq1);
 
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-low-value-alarm", COMPONENT("ne:power"), COMPONENT("ne:temperature-cpu")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-high-value-alarm", COMPONENT("ne:power"), COMPONENT("ne:temperature-cpu")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-missing-alarm", COMPONENT("ne:power"), COMPONENT("ne:temperature-cpu")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-nonoperational", COMPONENT("ne:power"), COMPONENT("ne:temperature-cpu")).IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(
+            ALARMS("velia-alarms:sensor-low-value-alarm",
+                   "velia-alarms:sensor-high-value-alarm",
+                   "velia-alarms:sensor-missing-alarm",
+                   "velia-alarms:sensor-nonoperational"),
+            COMPONENTS(
+                COMPONENT("ne:power"),
+                COMPONENT("ne:temperature-cpu")))
+            .IN_SEQUENCE(seq1);
 
         REQUIRE_DATASTORE_CHANGE(dsChangeHardware, (ValueChanges{
                                            {COMPONENT("ne") "/class", "iana-hardware:chassis"},
@@ -379,7 +393,7 @@
                                            {COMPONENT("ne:temperature-cpu") "/state/oper-state", "enabled"},
                                        }))
             .IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-missing-alarm", COMPONENT("ne:psu")).TIMES(AT_LEAST(1));
+        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(ALARMS("velia-alarms:sensor-missing-alarm"), COMPONENTS(COMPONENT("ne:psu"))).TIMES(AT_LEAST(1));
         REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu", "critical", "PSU missing.").IN_SEQUENCE(seq1);
         REQUIRE_ALARM_RPC("velia-alarms:sensor-low-value-alarm", "ne:power", "critical", "Sensor value crossed low threshold.").IN_SEQUENCE(seq1);
 
@@ -390,10 +404,13 @@
         std::string lastChange = directLeafNodeQuery(modulePrefix + "/last-change");
 
         // PSU inserted
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-low-value-alarm", COMPONENT("ne:psu:child")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-high-value-alarm", COMPONENT("ne:psu:child")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-missing-alarm", COMPONENT("ne:psu:child")).IN_SEQUENCE(seq1);
-        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES("velia-alarms:sensor-nonoperational", COMPONENT("ne:psu:child")).IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_INVENTORY_ADD_RESOURCES(
+            ALARMS("velia-alarms:sensor-low-value-alarm",
+                   "velia-alarms:sensor-high-value-alarm",
+                   "velia-alarms:sensor-missing-alarm",
+                   "velia-alarms:sensor-nonoperational"),
+            COMPONENTS(COMPONENT("ne:psu:child")))
+            .IN_SEQUENCE(seq1);
         REQUIRE_DATASTORE_CHANGE(dsChangeHardware, (ValueChanges{
                                            {COMPONENT("ne:psu") "/state/oper-state", "enabled"},
                                            {COMPONENT("ne:psu:child") "/class", "iana-hardware:sensor"},