hardware: raise alarms when FspYh read failures happen

When the PDU or PSU read failure happen, we should also raise an alarm.
The read failures are detected inside the FspYh class which does not
have access to sysrepo. Therefore during the data polling we should also
pick up any alarms that data readers want to be published.

Change-Id: I42b1709d59a960b7f9ef65a65b47a0ef9b4a93d8
diff --git a/tests/hardware_fspyh.cpp b/tests/hardware_fspyh.cpp
index 6578a0c..fb33f5f 100644
--- a/tests/hardware_fspyh.cpp
+++ b/tests/hardware_fspyh.cpp
@@ -91,16 +91,21 @@
         {"/ietf-hardware:hardware/component[name='ne:psu']/parent", "ne"},
         {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "disabled"}};
 
+    const velia::ietf_hardware::SideLoadedAlarm alarmUnplugged = {"velia-alarms:sensor-missing-alarm", "/ietf-hardware:hardware/component[name='ne:psu']", "critical", "PSU is unplugged."};
+    const velia::ietf_hardware::SideLoadedAlarm alarmPlugged = {"velia-alarms:sensor-missing-alarm", "/ietf-hardware:hardware/component[name='ne:psu']", "cleared", "PSU is unplugged."};
+
     std::set<std::string> expectedThresholdsKeys;
 
     for (auto i : {0, 1, 2, 3, 4}) {
         std::this_thread::sleep_for(std::chrono::seconds(4));
         velia::ietf_hardware::DataTree expected;
+        std::set<velia::ietf_hardware::SideLoadedAlarm> expectedAlarms;
 
         switch (i) {
         case 0:
             expected = expectedDisabled;
             expectedThresholdsKeys.clear();
+            expectedAlarms = {alarmUnplugged};
             break;
         case 1:
             expected = {
@@ -215,10 +220,12 @@
                 "/ietf-hardware:hardware/component[name='ne:psu:voltage-5Vsb']/sensor-data/value",
                 "/ietf-hardware:hardware/component[name='ne:psu:voltage-in']/sensor-data/value",
             };
+            expectedAlarms = {alarmPlugged};
             break;
         case 2:
             expected = expectedDisabled;
             expectedThresholdsKeys.clear();
+            expectedAlarms = {alarmUnplugged};
             break;
         case 3:
             // Here I simulate read failure by a file from the hwmon directory. This happens when the user wants data from
@@ -226,22 +233,26 @@
             fakeI2c->removeHwmonFile("temp1_input");
             expected = expectedDisabled;
             expectedThresholdsKeys.clear();
+            expectedAlarms = {alarmUnplugged};
             break;
         case 4:
             expected = expectedDisabled;
             expectedThresholdsKeys.clear();
+            expectedAlarms = {alarmUnplugged};
             break;
         }
 
-        auto res = psu->readValues();
+        auto [data, thresholds, sideLoadedAlarms] = psu->readValues();
 
         CAPTURE((int)counter);
-        REQUIRE(res.data == expected);
+        REQUIRE(data == expected);
 
         std::set<std::string> thresholdsKeys;
-        std::transform(res.thresholds.begin(), res.thresholds.end(), std::inserter(thresholdsKeys, thresholdsKeys.begin()), [](const auto& kv) { return kv.first; });
+        std::transform(thresholds.begin(), thresholds.end(), std::inserter(thresholdsKeys, thresholdsKeys.begin()), [](const auto& kv) { return kv.first; });
         REQUIRE(thresholdsKeys == expectedThresholdsKeys);
 
+        REQUIRE(sideLoadedAlarms == expectedAlarms);
+
         counter++;
     }
 
diff --git a/tests/hardware_ietf-hardware.cpp b/tests/hardware_ietf-hardware.cpp
index 0e73c5b..b9828d4 100644
--- a/tests/hardware_ietf-hardware.cpp
+++ b/tests/hardware_ietf-hardware.cpp
@@ -97,6 +97,7 @@
 
         velia::ietf_hardware::SensorPollData operator()()
         {
+            velia::ietf_hardware::SideLoadedAlarm alarm;
             velia::ietf_hardware::ThresholdsBySensorPath thr;
             velia::ietf_hardware::DataTree res = {
                 {COMPONENT("ne:psu") "/class", "iana-hardware:power-supply"},
@@ -121,9 +122,13 @@
                     .warningHigh = OneThreshold<int64_t>{15000, 2000},
                     .criticalHigh = std::nullopt,
                 };
+
+                alarm = {"velia-alarms:sensor-missing", COMPONENT("ne:psu"), "cleared", "PSU missing."};
+            } else {
+                alarm = {"velia-alarms:sensor-missing", COMPONENT("ne:psu"), "critical", "PSU missing."};
             }
 
-            return {res, thr};
+            return {res, thr, {alarm}};
         }
     };
     bool psuActive = true;
@@ -260,7 +265,7 @@
     };
 
     {
-        auto [data, alarms, activeSensors] = ietfHardware->process();
+        auto [data, alarms, activeSensors, sideLoadedAlarms] = ietfHardware->process();
         NUKE_LAST_CHANGE(data);
         REQUIRE(data == expected);
         REQUIRE(alarms == std::map<std::string, velia::ietf_hardware::State>{
@@ -289,12 +294,13 @@
                     COMPONENT("ne:fans:fan4:rpm") "/sensor-data/value",
                     COMPONENT("ne:psu:child") "/sensor-data/value",
                 });
+        REQUIRE(sideLoadedAlarms == std::set<velia::ietf_hardware::SideLoadedAlarm>{{"velia-alarms:sensor-missing", COMPONENT("ne:psu"), "cleared", "PSU missing."}});
     }
 
     fanValues[1] = 500;
     expected[COMPONENT("ne:fans:fan2:rpm") "/sensor-data/value"] = "500";
     {
-        auto [data, alarms, activeSensors] = ietfHardware->process();
+        auto [data, alarms, activeSensors, sideLoadedAlarms] = ietfHardware->process();
         NUKE_LAST_CHANGE(data);
         REQUIRE(data == expected);
         REQUIRE(alarms == std::map<std::string, velia::ietf_hardware::State>{
@@ -313,6 +319,7 @@
                     COMPONENT("ne:fans:fan4:rpm") "/sensor-data/value",
                     COMPONENT("ne:psu:child") "/sensor-data/value",
                 });
+        REQUIRE(sideLoadedAlarms == std::set<velia::ietf_hardware::SideLoadedAlarm>{{"velia-alarms:sensor-missing", COMPONENT("ne:psu"), "cleared", "PSU missing."}});
     }
 
     psuActive = false;
@@ -332,7 +339,7 @@
     expected[COMPONENT("ne:fans:fan3:rpm") "/sensor-data/value"] = "5000";
 
     {
-        auto [data, alarms, activeSensors] = ietfHardware->process();
+        auto [data, alarms, activeSensors, sideLoadedAlarms] = ietfHardware->process();
         NUKE_LAST_CHANGE(data);
 
         REQUIRE(data == expected);
@@ -352,6 +359,7 @@
                     COMPONENT("ne:fans:fan3:rpm") "/sensor-data/value",
                     COMPONENT("ne:fans:fan4:rpm") "/sensor-data/value",
                 });
+        REQUIRE(sideLoadedAlarms == std::set<velia::ietf_hardware::SideLoadedAlarm>{{"velia-alarms:sensor-missing", COMPONENT("ne:psu"), "critical", "PSU missing."}});
     }
 
     psuActive = true;
@@ -368,7 +376,7 @@
     expected[COMPONENT("ne:psu:child") "/sensor-data/value-type"] = "volts-DC";
 
     {
-        auto [data, alarms, activeSensors] = ietfHardware->process();
+        auto [data, alarms, activeSensors, sideLoadedAlarms] = ietfHardware->process();
         NUKE_LAST_CHANGE(data);
 
         REQUIRE(data == expected);
@@ -388,6 +396,7 @@
                     COMPONENT("ne:fans:fan4:rpm") "/sensor-data/value",
                     COMPONENT("ne:psu:child") "/sensor-data/value",
                 });
+        REQUIRE(sideLoadedAlarms == std::set<velia::ietf_hardware::SideLoadedAlarm>{{"velia-alarms:sensor-missing", COMPONENT("ne:psu"), "cleared", "PSU missing."}});
     }
 
 
@@ -399,7 +408,7 @@
     expected[COMPONENT("ne:fans:fan2:rpm") "/sensor-data/oper-status"] = "nonoperational";
 
     {
-        auto [data, alarms, activeSensors] = ietfHardware->process();
+        auto [data, alarms, activeSensors, sideLoadedAlarms] = ietfHardware->process();
         NUKE_LAST_CHANGE(data);
 
         REQUIRE(data == expected);
@@ -420,5 +429,6 @@
                     COMPONENT("ne:fans:fan4:rpm") "/sensor-data/value",
                     COMPONENT("ne:psu:child") "/sensor-data/value",
                 });
+        REQUIRE(sideLoadedAlarms == std::set<velia::ietf_hardware::SideLoadedAlarm>{{"velia-alarms:sensor-missing", COMPONENT("ne:psu"), "cleared", "PSU missing."}});
     }
 }
diff --git a/tests/sysrepo_ietf-hardware.cpp b/tests/sysrepo_ietf-hardware.cpp
index 47d53ca..bf35e79 100644
--- a/tests/sysrepo_ietf-hardware.cpp
+++ b/tests/sysrepo_ietf-hardware.cpp
@@ -205,6 +205,7 @@
 
         velia::ietf_hardware::SensorPollData operator()()
         {
+            velia::ietf_hardware::SideLoadedAlarm alarm;
             velia::ietf_hardware::ThresholdsBySensorPath thr;
             velia::ietf_hardware::DataTree res = {
                 {COMPONENT("ne:psu") "/class", "iana-hardware:power-supply"},
@@ -229,9 +230,13 @@
                     .warningHigh = OneThreshold<int64_t>{15000, 2000},
                     .criticalHigh = std::nullopt,
                 };
+
+                alarm = {"velia-alarms:sensor-missing-alarm", COMPONENT("ne:psu"), "cleared", "PSU missing."};
+            } else {
+                alarm = {"velia-alarms:sensor-missing-alarm", COMPONENT("ne:psu"), "critical", "PSU missing."};
             }
 
-            return {res, thr};
+            return {res, thr, {alarm}};
         }
     };
     ietfHardware->registerDataReader(PsuDataReader{psuActive, psuSensorValue});
@@ -319,6 +324,7 @@
                                            {COMPONENT("ne:temperature-cpu") "/state/oper-state", "enabled"},
                                        }))
             .IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_INVENTORY_ADD_RESOURCE("velia-alarms:sensor-missing-alarm", "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);
@@ -341,6 +347,7 @@
                                            {COMPONENT("ne:temperature-cpu") "/sensor-data/value", "222"},
                                        }))
             .IN_SEQUENCE(seq1);
+        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", "cleared", "Sensor value crossed low threshold.").IN_SEQUENCE(seq1);
         REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu:child", "warning", "Sensor value not reported. Maybe the sensor was unplugged?").IN_SEQUENCE(seq1);
         REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
@@ -365,6 +372,7 @@
                                            {COMPONENT("ne:psu:child") "/state/oper-state", "enabled"},
                                        }))
             .IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu", "cleared", "PSU missing.").IN_SEQUENCE(seq1);
         REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu:child", "cleared", "Sensor value not reported. Maybe the sensor was unplugged?").IN_SEQUENCE(seq1);
         REQUIRE_ALARM_RPC("velia-alarms:sensor-high-value-alarm", "ne:psu:child", "warning", "Sensor value crossed high threshold.").IN_SEQUENCE(seq1);
         psuSensorValue = 50000;
@@ -385,6 +393,7 @@
                                            {COMPONENT("ne:psu") "/state/oper-state", "disabled"},
                                        }))
             .IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu", "critical", "PSU missing.").IN_SEQUENCE(seq1);
         REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu:child", "warning", "Sensor value not reported. Maybe the sensor was unplugged?").IN_SEQUENCE(seq1);
         REQUIRE_ALARM_RPC("velia-alarms:sensor-high-value-alarm", "ne:psu:child", "cleared", "Sensor value crossed high threshold.").IN_SEQUENCE(seq1);
         psuActive = false;
@@ -504,6 +513,8 @@
                                            {COMPONENT("ne:temperature-cpu") "/state/oper-state", "enabled"},
                                        }))
             .IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_INVENTORY_ADD_RESOURCE("velia-alarms:sensor-missing-alarm", "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);
 
         auto ietfHardwareSysrepo = std::make_shared<velia::ietf_hardware::sysrepo::Sysrepo>(srSess, ietfHardware, 150ms);
@@ -530,6 +541,7 @@
                                            {COMPONENT("ne:psu:child") "/state/oper-state", "enabled"},
                                        }))
             .IN_SEQUENCE(seq1);
+        REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu", "cleared", "PSU missing.").IN_SEQUENCE(seq1);
         psuActive = true;
         waitForCompletionAndBitMore(seq1);