pseq: Modify JSON parsing for entire file

Modify functions that parse the entire JSON configuration file for the
phosphor-power-sequencer application. Parse the new top level JSON
properties.

Tested:
* Ran automated tests

Change-Id: I08f20fc09aa4eff5bc819398834afac61054f8db
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/phosphor-power-sequencer/src/config_file_parser.cpp b/phosphor-power-sequencer/src/config_file_parser.cpp
index 9e4a18d..44c7877 100644
--- a/phosphor-power-sequencer/src/config_file_parser.cpp
+++ b/phosphor-power-sequencer/src/config_file_parser.cpp
@@ -74,7 +74,8 @@
     return pathName;
 }
 
-std::vector<std::unique_ptr<Rail>> parse(const std::filesystem::path& pathName)
+std::vector<std::unique_ptr<Chassis>> parse(
+    const std::filesystem::path& pathName, Services& services)
 {
     try
     {
@@ -83,7 +84,7 @@
         json rootElement = json::parse(file);
 
         // Parse tree of JSON elements and return corresponding C++ objects
-        return internal::parseRoot(rootElement);
+        return internal::parseRoot(rootElement, services);
     }
     catch (const std::exception& e)
     {
@@ -482,23 +483,37 @@
     return rails;
 }
 
-std::vector<std::unique_ptr<Rail>> parseRoot(const json& element)
+std::vector<std::unique_ptr<Chassis>> parseRoot(const json& element,
+                                                Services& services)
 {
-    std::map<std::string, std::string> variables{};
-
     verifyIsObject(element);
     unsigned int propertyCount{0};
 
-    // Required rails property
-    const json& railsElement = getRequiredProperty(element, "rails");
-    std::vector<std::unique_ptr<Rail>> rails =
-        parseRailArray(railsElement, variables);
+    // Optional comments property; value not stored
+    if (element.contains("comments"))
+    {
+        ++propertyCount;
+    }
+
+    // Optional chassis_templates property
+    std::map<std::string, JSONRefWrapper> chassisTemplates{};
+    auto chassisTemplatesIt = element.find("chassis_templates");
+    if (chassisTemplatesIt != element.end())
+    {
+        chassisTemplates = parseChassisTemplateArray(*chassisTemplatesIt);
+        ++propertyCount;
+    }
+
+    // Required chassis property
+    const json& chassisElement = getRequiredProperty(element, "chassis");
+    std::vector<std::unique_ptr<Chassis>> chassis =
+        parseChassisArray(chassisElement, chassisTemplates, services);
     ++propertyCount;
 
     // Verify no invalid properties exist
     verifyPropertyCount(element, propertyCount);
 
-    return rails;
+    return chassis;
 }
 
 std::map<std::string, std::string> parseVariables(const json& element)
diff --git a/phosphor-power-sequencer/src/config_file_parser.hpp b/phosphor-power-sequencer/src/config_file_parser.hpp
index f43ad0e..b5b54fb 100644
--- a/phosphor-power-sequencer/src/config_file_parser.hpp
+++ b/phosphor-power-sequencer/src/config_file_parser.hpp
@@ -69,14 +69,16 @@
 /**
  * Parses the specified JSON configuration file.
  *
- * Returns the corresponding C++ Rail objects.
+ * Returns the corresponding C++ Chassis objects.
  *
  * Throws a ConfigFileParserError if an error occurs.
  *
  * @param pathName configuration file path name
- * @return vector of Rail objects
+ * @param services system services like hardware presence and the journal
+ * @return vector of Chassis objects
  */
-std::vector<std::unique_ptr<Rail>> parse(const std::filesystem::path& pathName);
+std::vector<std::unique_ptr<Chassis>> parse(
+    const std::filesystem::path& pathName, Services& services);
 
 /*
  * Internal implementation details for parse()
@@ -270,14 +272,16 @@
 /**
  * Parses the JSON root element of the entire configuration file.
  *
- * Returns the corresponding C++ Rail objects.
+ * Returns the corresponding C++ Chassis objects.
  *
  * Throws an exception if parsing fails.
  *
  * @param element JSON element
- * @return vector of Rail objects
+ * @param services system services like hardware presence and the journal
+ * @return vector of Chassis objects
  */
-std::vector<std::unique_ptr<Rail>> parseRoot(const json& element);
+std::vector<std::unique_ptr<Chassis>> parseRoot(const json& element,
+                                                Services& services);
 
 /**
  * Parses a JSON element containing an object with variable names and values.
diff --git a/phosphor-power-sequencer/src/power_control.cpp b/phosphor-power-sequencer/src/power_control.cpp
index fa43ff9..4b8315c 100644
--- a/phosphor-power-sequencer/src/power_control.cpp
+++ b/phosphor-power-sequencer/src/power_control.cpp
@@ -371,14 +371,14 @@
 }
 
 bool PowerControl::parseConfigFile(const std::filesystem::path& configFile,
-                                   std::vector<std::unique_ptr<Rail>>& rails)
+                                   std::vector<std::unique_ptr<Rail>>&)
 {
     // Parse JSON configuration file
     bool wasParsed{false};
     try
     {
-        rails = config_file_parser::parse(configFile);
-        wasParsed = true;
+        config_file_parser::parse(configFile, services);
+        // wasParsed = true;
     }
     catch (const std::exception& e)
     {
diff --git a/phosphor-power-sequencer/test/config_file_parser_tests.cpp b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
index 2fdf5af..78899bb 100644
--- a/phosphor-power-sequencer/test/config_file_parser_tests.cpp
+++ b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
@@ -252,18 +252,18 @@
     {
         const json configFileContents = R"(
             {
-                "rails": [
-                    {
-                        "name": "VDD_CPU0",
-                        "page": 11,
-                        "check_status_vout": true
-                    },
-                    {
-                        "name": "VCS_CPU1",
-                        "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
-                        "gpio": { "line": 60 }
-                    }
-                ]
+              "chassis": [
+                {
+                  "number": 1,
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
+                  "power_sequencers": []
+                },
+                {
+                  "number": 2,
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis2",
+                  "power_sequencers": []
+                }
+              ]
             }
         )"_json;
 
@@ -271,28 +271,36 @@
         fs::path pathName{configFile.getPath()};
         writeConfigFile(pathName, configFileContents);
 
-        auto rails = parse(pathName);
+        MockServices services{};
+        auto chassis = parse(pathName, services);
 
-        EXPECT_EQ(rails.size(), 2);
-        EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
-        EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
+        EXPECT_EQ(chassis.size(), 2);
+        EXPECT_EQ(chassis[0]->getNumber(), 1);
+        EXPECT_EQ(chassis[0]->getInventoryPath(),
+                  "/xyz/openbmc_project/inventory/system/chassis1");
+        EXPECT_EQ(chassis[1]->getNumber(), 2);
+        EXPECT_EQ(chassis[1]->getInventoryPath(),
+                  "/xyz/openbmc_project/inventory/system/chassis2");
     }
 
     // Test where fails: File does not exist
     {
         fs::path pathName{"/tmp/non_existent_file"};
-        EXPECT_THROW(parse(pathName), ConfigFileParserError);
+        MockServices services{};
+        EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
     }
 
     // Test where fails: File is not readable
     {
         const json configFileContents = R"(
             {
-                "rails": [
-                    {
-                        "name": "VDD_CPU0"
-                    }
-                ]
+              "chassis": [
+                {
+                  "number": 1,
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
+                  "power_sequencers": []
+                }
+              ]
             }
         )"_json;
 
@@ -301,7 +309,8 @@
         writeConfigFile(pathName, configFileContents);
 
         chmod(pathName.c_str(), 0222);
-        EXPECT_THROW(parse(pathName), ConfigFileParserError);
+        MockServices services{};
+        EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
     }
 
     // Test where fails: File is not valid JSON
@@ -312,7 +321,8 @@
         fs::path pathName{configFile.getPath()};
         writeConfigFile(pathName, configFileContents);
 
-        EXPECT_THROW(parse(pathName), ConfigFileParserError);
+        MockServices services{};
+        EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
     }
 
     // Test where fails: JSON does not conform to config file format
@@ -323,7 +333,8 @@
         fs::path pathName{configFile.getPath()};
         writeConfigFile(pathName, configFileContents);
 
-        EXPECT_THROW(parse(pathName), ConfigFileParserError);
+        MockServices services{};
+        EXPECT_THROW(parse(pathName, services), ConfigFileParserError);
     }
 }
 
@@ -2516,35 +2527,83 @@
 
 TEST(ConfigFileParserTests, ParseRoot)
 {
-    // Test where works
+    // Test where works: Only required properties specified
     {
         const json element = R"(
             {
-                "rails": [
-                    {
-                        "name": "VDD_CPU0",
-                        "page": 11,
-                        "check_status_vout": true
-                    },
-                    {
-                        "name": "VCS_CPU1",
-                        "presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
-                        "gpio": { "line": 60 }
-                    }
-                ]
+              "chassis": [
+                {
+                  "number": 1,
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
+                  "power_sequencers": []
+                },
+                {
+                  "number": 2,
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis2",
+                  "power_sequencers": []
+                }
+              ]
             }
         )"_json;
-        auto rails = parseRoot(element);
-        EXPECT_EQ(rails.size(), 2);
-        EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
-        EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
+        MockServices services{};
+        auto chassis = parseRoot(element, services);
+        EXPECT_EQ(chassis.size(), 2);
+        EXPECT_EQ(chassis[0]->getNumber(), 1);
+        EXPECT_EQ(chassis[0]->getInventoryPath(),
+                  "/xyz/openbmc_project/inventory/system/chassis1");
+        EXPECT_EQ(chassis[1]->getNumber(), 2);
+        EXPECT_EQ(chassis[1]->getInventoryPath(),
+                  "/xyz/openbmc_project/inventory/system/chassis2");
+    }
+
+    // Test where works: All properties specified
+    {
+        const json element = R"(
+            {
+              "comments": [ "Config file for a FooBar one-chassis system" ],
+              "chassis_templates": [
+                {
+                  "id": "foo_chassis",
+                  "number": "${chassis_number}",
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
+                  "power_sequencers": []
+                },
+                {
+                  "id": "bar_chassis",
+                  "number": "${chassis_number}",
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/bar_chassis${chassis_number}",
+                  "power_sequencers": []
+                }
+              ],
+              "chassis": [
+                {
+                  "template_id": "foo_chassis",
+                  "template_variable_values": { "chassis_number": "2" }
+                },
+                {
+                  "template_id": "bar_chassis",
+                  "template_variable_values": { "chassis_number": "3" }
+                }
+              ]
+            }
+        )"_json;
+        MockServices services{};
+        auto chassis = parseRoot(element, services);
+        EXPECT_EQ(chassis.size(), 2);
+        EXPECT_EQ(chassis[0]->getNumber(), 2);
+        EXPECT_EQ(chassis[0]->getInventoryPath(),
+                  "/xyz/openbmc_project/inventory/system/chassis2");
+        EXPECT_EQ(chassis[1]->getNumber(), 3);
+        EXPECT_EQ(chassis[1]->getInventoryPath(),
+                  "/xyz/openbmc_project/inventory/system/bar_chassis3");
     }
 
     // Test where fails: Element is not an object
     try
     {
         const json element = R"( [ "VDD_CPU0", "VCS_CPU1" ] )"_json;
-        parseRoot(element);
+        MockServices services{};
+        parseRoot(element, services);
         ADD_FAILURE() << "Should not have reached this line.";
     }
     catch (const std::invalid_argument& e)
@@ -2552,30 +2611,48 @@
         EXPECT_STREQ(e.what(), "Element is not an object");
     }
 
-    // Test where fails: Required rails property not specified
+    // Test where fails: Required chassis property not specified
     try
     {
         const json element = R"(
             {
+              "comments": [ "Config file for a FooBar one-chassis system" ],
+              "chassis_templates": [
+                {
+                  "id": "foo_chassis",
+                  "number": "${chassis_number}",
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis${chassis_number}",
+                  "power_sequencers": []
+                }
+              ]
             }
         )"_json;
-        parseRoot(element);
+        MockServices services{};
+        parseRoot(element, services);
         ADD_FAILURE() << "Should not have reached this line.";
     }
     catch (const std::invalid_argument& e)
     {
-        EXPECT_STREQ(e.what(), "Required property missing: rails");
+        EXPECT_STREQ(e.what(), "Required property missing: chassis");
     }
 
-    // Test where fails: rails value is invalid
+    // Test where fails: chassis_templates value is invalid
     try
     {
         const json element = R"(
             {
-                "rails": 31
+              "chassis_templates": true,
+              "chassis": [
+                {
+                  "number": 1,
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
+                  "power_sequencers": []
+                }
+              ]
             }
         )"_json;
-        parseRoot(element);
+        MockServices services{};
+        parseRoot(element, services);
         ADD_FAILURE() << "Should not have reached this line.";
     }
     catch (const std::invalid_argument& e)
@@ -2583,22 +2660,46 @@
         EXPECT_STREQ(e.what(), "Element is not an array");
     }
 
+    // Test where fails: chassis value is invalid
+    try
+    {
+        const json element = R"(
+            {
+              "chassis": [
+                {
+                  "number": "one",
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
+                  "power_sequencers": []
+                }
+              ]
+            }
+        )"_json;
+        MockServices services{};
+        parseRoot(element, services);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an integer");
+    }
+
     // Test where fails: Invalid property specified
     try
     {
         const json element = R"(
             {
-                "rails": [
-                    {
-                        "name": "VDD_CPU0",
-                        "page": 11,
-                        "check_status_vout": true
-                    }
-                ],
-                "foo": true
+              "chassis": [
+                {
+                  "number": 1,
+                  "inventory_path": "/xyz/openbmc_project/inventory/system/chassis1",
+                  "power_sequencers": []
+                }
+              ],
+              "foo": true
             }
         )"_json;
-        parseRoot(element);
+        MockServices services{};
+        parseRoot(element, services);
         ADD_FAILURE() << "Should not have reached this line.";
     }
     catch (const std::invalid_argument& e)