regulators: Implements support for rail

Enhance the configuration file parser to support the rail element.

Signed-off-by: Bob King <Bob_King@wistron.com>
Change-Id: Id10809cac4271295c5eb3bd318be775e1d470286
diff --git a/phosphor-regulators/src/config_file_parser.cpp b/phosphor-regulators/src/config_file_parser.cpp
index ce95d2d..cc96545 100644
--- a/phosphor-regulators/src/config_file_parser.cpp
+++ b/phosphor-regulators/src/config_file_parser.cpp
@@ -519,15 +519,55 @@
                                                          exponent, isVerified);
 }
 
+std::unique_ptr<Rail> parseRail(const json& element)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Optional comments property; value not stored
+    if (element.contains("comments"))
+    {
+        ++propertyCount;
+    }
+
+    // Required id property
+    const json& idElement = getRequiredProperty(element, "id");
+    std::string id = parseString(idElement);
+    ++propertyCount;
+
+    // Optional configuration property
+    std::unique_ptr<Configuration> configuration{};
+    auto configurationIt = element.find("configuration");
+    if (configurationIt != element.end())
+    {
+        configuration = parseConfiguration(*configurationIt);
+        ++propertyCount;
+    }
+
+    // Optional sensor_monitoring property
+    std::unique_ptr<SensorMonitoring> sensorMonitoring{};
+    auto sensorMonitoringIt = element.find("sensor_monitoring");
+    if (sensorMonitoringIt != element.end())
+    {
+        sensorMonitoring = parseSensorMonitoring(*sensorMonitoringIt);
+        ++propertyCount;
+    }
+
+    // Verify no invalid properties exist
+    verifyPropertyCount(element, propertyCount);
+
+    return std::make_unique<Rail>(id, std::move(configuration),
+                                  std::move(sensorMonitoring));
+}
+
 std::vector<std::unique_ptr<Rail>> parseRailArray(const json& element)
 {
     verifyIsArray(element);
     std::vector<std::unique_ptr<Rail>> rails;
-    // TODO: Not implemented yet
-    // for (auto& railElement : element)
-    // {
-    //     rails.emplace_back(parseRail(railElement));
-    // }
+    for (auto& railElement : element)
+    {
+        rails.emplace_back(parseRail(railElement));
+    }
     return rails;
 }
 
@@ -638,6 +678,28 @@
     return std::make_unique<RunRuleAction>(ruleID);
 }
 
+std::unique_ptr<SensorMonitoring> parseSensorMonitoring(const json& element)
+{
+    verifyIsObject(element);
+    unsigned int propertyCount{0};
+
+    // Optional comments property; value not stored
+    if (element.contains("comments"))
+    {
+        ++propertyCount;
+    }
+
+    // Required rule_id or actions property
+    std::vector<std::unique_ptr<Action>> actions{};
+    actions = parseRuleIDOrActionsProperty(element);
+    ++propertyCount;
+
+    // Verify no invalid properties exist
+    verifyPropertyCount(element, propertyCount);
+
+    return std::make_unique<SensorMonitoring>(std::move(actions));
+}
+
 } // namespace internal
 
 } // namespace phosphor::power::regulators::config_file_parser
diff --git a/phosphor-regulators/src/config_file_parser.hpp b/phosphor-regulators/src/config_file_parser.hpp
index 251f236..66c4c93 100644
--- a/phosphor-regulators/src/config_file_parser.hpp
+++ b/phosphor-regulators/src/config_file_parser.hpp
@@ -28,6 +28,7 @@
 #include "rail.hpp"
 #include "rule.hpp"
 #include "run_rule_action.hpp"
+#include "sensor_monitoring.hpp"
 
 #include <nlohmann/json.hpp>
 
@@ -397,6 +398,18 @@
     parsePMBusWriteVoutCommand(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a rail.
+ *
+ * Returns the corresponding C++ Rail object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return Rail object
+ */
+std::unique_ptr<Rail> parseRail(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing an array of rails.
  *
  * Returns the corresponding C++ Rail objects.
@@ -480,6 +493,19 @@
 std::unique_ptr<RunRuleAction> parseRunRule(const nlohmann::json& element);
 
 /**
+ * Parses a JSON element containing a sensor monitoring operation.
+ *
+ * Returns the corresponding C++ SensorMonitoring object.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return SensorMonitoring object
+ */
+std::unique_ptr<SensorMonitoring>
+    parseSensorMonitoring(const nlohmann::json& element);
+
+/**
  * Parses a JSON element containing a string.
  *
  * Returns the corresponding C++ string.
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
index e848fd7..be8a4c8 100644
--- a/phosphor-regulators/test/config_file_parser_tests.cpp
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -29,6 +29,7 @@
 #include "rail.hpp"
 #include "rule.hpp"
 #include "run_rule_action.hpp"
+#include "sensor_monitoring.hpp"
 #include "tmp_file.hpp"
 
 #include <sys/stat.h> // for chmod()
@@ -956,7 +957,7 @@
 
     // Test where works: All properties specified
     {
-        // TODO : add rails and presence_detection properties
+        // TODO : add presence_detection property
         const json element = R"(
             {
               "id": "vdd_regulator",
@@ -970,7 +971,13 @@
               "configuration":
               {
                   "rule_id": "configure_ir35221_rule"
-              }
+              },
+              "rails":
+              [
+                {
+                  "id": "vdd"
+                }
+              ]
             }
         )"_json;
         std::unique_ptr<Device> device = parseDevice(element);
@@ -980,7 +987,41 @@
         EXPECT_NE(&(device->getI2CInterface()), nullptr);
         // EXPECT_NE(device->getPresenceDetection(), nullptr);
         EXPECT_NE(device->getConfiguration(), nullptr);
-        // EXPECT_NE(device->getRails().size(), 0);
+        EXPECT_EQ(device->getRails().size(), 1);
+    }
+
+    // Test where fails: rails property exists and is_regulator is false
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd_regulator",
+              "is_regulator": false,
+              "fru": "/system/chassis/motherboard/regulator2",
+              "i2c_interface":
+              {
+                  "bus": 1,
+                  "address": "0x70"
+              },
+              "configuration":
+              {
+                  "rule_id": "configure_ir35221_rule"
+              },
+              "rails":
+              [
+                {
+                  "id": "vdd"
+                }
+              ]
+            }
+        )"_json;
+        parseDevice(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(),
+                     "Invalid rails property when is_regulator is false");
     }
 
     // Test where fails: id value is invalid
@@ -2076,6 +2117,209 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseRail)
+{
+    // Test where works: Only required properties specified
+    {
+        const json element = R"(
+            {
+              "id": "vdd"
+            }
+        )"_json;
+        std::unique_ptr<Rail> rail = parseRail(element);
+        EXPECT_EQ(rail->getID(), "vdd");
+        EXPECT_EQ(rail->getConfiguration(), nullptr);
+        EXPECT_EQ(rail->getSensorMonitoring(), nullptr);
+    }
+
+    // Test where works: All properties specified
+    {
+        const json element = R"(
+            {
+              "comments": [ "comments property" ],
+              "id": "vdd",
+              "configuration": {
+                "volts": 1.1,
+                "actions": [
+                  {
+                    "pmbus_write_vout_command": {
+                      "format": "linear"
+                    }
+                  }
+                ]
+              },
+              "sensor_monitoring": {
+                "actions": [
+                  { "run_rule": "read_sensors_rule" }
+                ]
+              }
+            }
+        )"_json;
+        std::unique_ptr<Rail> rail = parseRail(element);
+        EXPECT_EQ(rail->getID(), "vdd");
+        EXPECT_NE(rail->getConfiguration(), nullptr);
+        EXPECT_NE(rail->getSensorMonitoring(), nullptr);
+    }
+
+    // Test where fails: id property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "configuration": {
+                "volts": 1.1,
+                "actions": [
+                  {
+                    "pmbus_write_vout_command": {
+                      "format": "linear"
+                    }
+                  }
+                ]
+              }
+            }
+        )"_json;
+        parseRail(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: id");
+    }
+
+    // Test where fails: id property is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "id": "",
+              "configuration": {
+                "volts": 1.1,
+                "actions": [
+                  {
+                    "pmbus_write_vout_command": {
+                      "format": "linear"
+                    }
+                  }
+                ]
+              }
+            }
+        )"_json;
+        parseRail(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an empty string");
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ "0xFF", "0x01" ] )"_json;
+        parseRail(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+
+    // Test where fails: configuration value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd",
+              "configuration": "config"
+            }
+        )"_json;
+        parseRail(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+
+    // Test where fails: sensor_monitoring value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "comments": [ "comments property" ],
+              "id": "vdd",
+              "configuration": {
+                "volts": 1.1,
+                "actions": [
+                  {
+                    "pmbus_write_vout_command": {
+                      "format": "linear"
+                    }
+                  }
+                ]
+              },
+              "sensor_monitoring": 1
+            }
+        )"_json;
+        parseRail(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+
+    // Test where fails: Invalid property specified
+    try
+    {
+        const json element = R"(
+            {
+              "id": "vdd",
+              "foo" : true
+            }
+        )"_json;
+        parseRail(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an invalid property");
+    }
+}
+
+TEST(ConfigFileParserTests, ParseRailArray)
+{
+    // Test where works
+    {
+        const json element = R"(
+            [
+              { "id": "vdd" },
+              { "id": "vio" }
+            ]
+        )"_json;
+        std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
+        EXPECT_EQ(rails.size(), 2);
+        EXPECT_EQ(rails[0]->getID(), "vdd");
+        EXPECT_EQ(rails[1]->getID(), "vio");
+    }
+
+    // Test where fails: Element is not an array
+    try
+    {
+        const json element = R"(
+            {
+              "foo": "bar"
+            }
+        )"_json;
+        parseRailArray(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an array");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseRoot)
 {
     // Test where works: Only required properties specified
@@ -2503,6 +2747,136 @@
     }
 }
 
+TEST(ConfigFileParserTests, ParseSensorMonitoring)
+{
+    // Test where works: actions property specified
+    {
+        const json element = R"(
+            {
+              "actions": [
+                { "run_rule": "read_sensors_rule" }
+              ]
+            }
+        )"_json;
+        std::unique_ptr<SensorMonitoring> sensorMonitoring =
+            parseSensorMonitoring(element);
+        EXPECT_EQ(sensorMonitoring->getActions().size(), 1);
+    }
+
+    // Test where works: rule_id property specified
+    {
+        const json element = R"(
+            {
+              "comments": [ "comments property" ],
+              "rule_id": "set_voltage_rule"
+            }
+        )"_json;
+        std::unique_ptr<SensorMonitoring> sensorMonitoring =
+            parseSensorMonitoring(element);
+        EXPECT_EQ(sensorMonitoring->getActions().size(), 1);
+    }
+
+    // Test where fails: actions object is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "actions": 1
+            }
+        )"_json;
+        parseSensorMonitoring(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an array");
+    }
+
+    // Test where fails: rule_id value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "rule_id": 1
+            }
+        )"_json;
+        parseSensorMonitoring(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+
+    // Test where fails: Required actions or rule_id property not specified
+    try
+    {
+        const json element = R"(
+            {
+              "comments": [ "comments property" ]
+            }
+        )"_json;
+        parseSensorMonitoring(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
+                               "either rule_id or actions");
+    }
+
+    // Test where fails: Required actions or rule_id property both specified
+    try
+    {
+        const json element = R"(
+            {
+              "rule_id": "set_voltage_rule",
+              "actions": [
+                { "run_rule": "read_sensors_rule" }
+              ]
+            }
+        )"_json;
+        parseSensorMonitoring(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Invalid property combination: Must contain "
+                               "either rule_id or actions");
+    }
+
+    // Test where fails: Element is not an object
+    try
+    {
+        const json element = R"( [ "foo", "bar" ] )"_json;
+        parseSensorMonitoring(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+
+    // Test where fails: Invalid property specified
+    try
+    {
+        const json element = R"(
+            {
+              "foo": "bar",
+              "actions": [
+                { "run_rule": "read_sensors_rule" }
+              ]
+            }
+        )"_json;
+        parseSensorMonitoring(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an invalid property");
+    }
+}
+
 TEST(ConfigFileParserTests, ParseString)
 {
     // Test where works: Empty string