metric_config: add lower bound support

Add the lower bound support to the metric config and fix the parsing
logic to update threshold configs accordingly. Update the collection
test for lower bound.

Change-Id: I6fd3bd8d85974f95ba877eb8d19a77c8acffef7c
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/health_metric_config.cpp b/health_metric_config.cpp
index 04ee169..e506f17 100644
--- a/health_metric_config.cpp
+++ b/health_metric_config.cpp
@@ -8,6 +8,7 @@
 #include <cmath>
 #include <fstream>
 #include <unordered_map>
+#include <unordered_set>
 #include <utility>
 
 PHOSPHOR_LOG2_USING;
@@ -21,6 +22,15 @@
 extern json defaultHealthMetricConfig;
 
 // Valid thresholds from config
+static const auto validThresholdTypesWithBound =
+    std::unordered_set<std::string>{"Critical_Lower", "Critical_Upper",
+                                    "Warning_Lower", "Warning_Upper"};
+
+static const auto validThresholdBounds =
+    std::unordered_map<std::string, ThresholdIntf::Bound>{
+        {"Lower", ThresholdIntf::Bound::Lower},
+        {"Upper", ThresholdIntf::Bound::Upper}};
+
 static const auto validThresholdTypes =
     std::unordered_map<std::string, ThresholdIntf::Type>{
         {"Critical", ThresholdIntf::Type::Critical},
@@ -74,7 +84,7 @@
 
     for (auto& [key, value] : thresholds->items())
     {
-        if (!validThresholdTypes.contains(key))
+        if (!validThresholdTypesWithBound.contains(key))
         {
             warning("Invalid ThresholdType: {TYPE}", "TYPE", key);
             continue;
@@ -86,11 +96,15 @@
             throw std::invalid_argument("Invalid threshold value");
         }
 
-        // ThresholdIntf::Bound::Upper is the only use case for
-        // ThresholdIntf::Bound
-        self.thresholds.emplace(std::make_tuple(validThresholdTypes.at(key),
-                                                ThresholdIntf::Bound::Upper),
-                                config);
+        static constexpr auto keyDelimiter = "_";
+        std::string typeStr = key.substr(0, key.find_first_of(keyDelimiter));
+        std::string boundStr = key.substr(key.find_last_of(keyDelimiter) + 1,
+                                          key.length());
+
+        self.thresholds.emplace(
+            std::make_tuple(validThresholdTypes.at(typeStr),
+                            validThresholdBounds.at(boundStr)),
+            config);
     }
 }
 
@@ -183,12 +197,12 @@
         "Frequency": 1,
         "Window_size": 120,
         "Threshold": {
-            "Critical": {
+            "Critical_Upper": {
                 "Value": 90.0,
                 "Log": true,
                 "Target": ""
             },
-            "Warning": {
+            "Warning_Upper": {
                 "Value": 80.0,
                 "Log": false,
                 "Target": ""
@@ -199,12 +213,12 @@
         "Frequency": 1,
         "Window_size": 120,
         "Threshold": {
-            "Critical": {
+            "Critical_Upper": {
                 "Value": 90.0,
                 "Log": true,
                 "Target": ""
             },
-            "Warning": {
+            "Warning_Upper": {
                 "Value": 80.0,
                 "Log": false,
                 "Target": ""
@@ -215,24 +229,46 @@
         "Frequency": 1,
         "Window_size": 120,
         "Threshold": {
-            "Critical": {
+            "Critical_Upper": {
                 "Value": 90.0,
                 "Log": true,
                 "Target": ""
             },
-            "Warning": {
+            "Warning_Upper": {
                 "Value": 80.0,
                 "Log": false,
                 "Target": ""
             }
         }
     },
+    "Memory": {
+        "Frequency": 1,
+        "Window_size": 120,
+        "Threshold": {
+            "Critical_Upper": {
+                "Value": 85.0,
+                "Log": true,
+                "Target": ""
+            }
+        }
+    },
     "Memory_Available": {
         "Frequency": 1,
         "Window_size": 120,
         "Threshold": {
-            "Critical": {
-                "Value": 85.0,
+            "Critical_Lower": {
+                "Value": 15.0,
+                "Log": true,
+                "Target": ""
+            }
+        }
+    },
+    "Memory_Free": {
+        "Frequency": 1,
+        "Window_size": 120,
+        "Threshold": {
+            "Critical_Lower": {
+                "Value": 15.0,
                 "Log": true,
                 "Target": ""
             }
@@ -242,7 +278,7 @@
         "Frequency": 1,
         "Window_size": 120,
         "Threshold": {
-            "Critical": {
+            "Critical_Upper": {
                 "Value": 85.0,
                 "Log": true,
                 "Target": ""
@@ -253,7 +289,7 @@
         "Frequency": 1,
         "Window_size": 120,
         "Threshold": {
-            "Critical": {
+            "Critical_Upper": {
                 "Value": 85.0,
                 "Log": true,
                 "Target": ""
@@ -265,8 +301,8 @@
         "Frequency": 1,
         "Window_size": 120,
         "Threshold": {
-            "Critical": {
-                "Value": 85.0,
+            "Critical_Lower": {
+                "Value": 15.0,
                 "Log": true,
                 "Target": ""
             }
@@ -277,8 +313,8 @@
         "Frequency": 1,
         "Window_size": 120,
         "Threshold": {
-            "Critical": {
-                "Value": 85.0,
+            "Critical_Lower": {
+                "Value": 15.0,
                 "Log": true,
                 "Target": ""
             }
diff --git a/test/test_health_metric_collection.cpp b/test/test_health_metric_collection.cpp
index bb1512a..4cd6a6b 100644
--- a/test/test_health_metric_collection.cpp
+++ b/test/test_health_metric_collection.cpp
@@ -54,7 +54,7 @@
         }
     }
 
-    void updateThreshold(double value)
+    void updateThreshold(ThresholdIntf::Bound bound, double value)
     {
         for (auto& [key, values] : configs)
         {
@@ -62,7 +62,10 @@
             {
                 for (auto& threshold : config.thresholds)
                 {
-                    threshold.second.value = value;
+                    if (get<ThresholdIntf::Bound>(threshold.first) == bound)
+                    {
+                        threshold.second.value = value;
+                    }
                 }
             }
         }
@@ -86,8 +89,9 @@
 
 TEST_F(HealthMetricCollectionTest, TestCreation)
 {
-    // Change threshold value to 100 to avoid threshold assertion
-    updateThreshold(100);
+    // Change threshold values to avoid threshold assertion
+    updateThreshold(ThresholdIntf::Bound::Upper, 100);
+    updateThreshold(ThresholdIntf::Bound::Lower, 0);
 
     EXPECT_CALL(sdbusMock,
                 sd_bus_emit_properties_changed_strv(
@@ -124,8 +128,9 @@
 
 TEST_F(HealthMetricCollectionTest, TestThresholdAsserted)
 {
-    // Change threshold value to 0 to trigger threshold assertion
-    updateThreshold(0);
+    // Change threshold values to trigger threshold assertion
+    updateThreshold(ThresholdIntf::Bound::Upper, 0);
+    updateThreshold(ThresholdIntf::Bound::Lower, 100);
 
     // Test metric value property change
     EXPECT_CALL(sdbusMock,
@@ -154,7 +159,7 @@
                 sd_bus_message_new_signal(IsNull(), NotNull(), NotNull(),
                                           StrEq(thresholdInterface),
                                           StrEq("AssertionChanged")))
-        .Times(11);
+        .Times(12);
 
     createCollection();
 }