PEL: Extract MRUs from callout JSON

The callout JSON can also contain MRU (Manufacturing Replaceable Unit)
IDs and priorities that should be added to a callout along with the FRU.

Extract them from the JSON and add them to the callout if they are
there.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I25b801d5fd4371719d7a4f39056cdc3d2bf29dfb
diff --git a/extensions/openpower-pels/src.cpp b/extensions/openpower-pels/src.cpp
index 6462598..1861608 100644
--- a/extensions/openpower-pels/src.cpp
+++ b/extensions/openpower-pels/src.cpp
@@ -754,7 +754,8 @@
 void SRC::addInventoryCallout(const std::string& inventoryPath,
                               const std::optional<CalloutPriority>& priority,
                               const std::optional<std::string>& locationCode,
-                              const DataInterfaceBase& dataIface)
+                              const DataInterfaceBase& dataIface,
+                              const std::vector<src::MRU::MRUCallout>& mrus)
 {
     std::string locCode;
     std::string fn;
@@ -781,7 +782,8 @@
             CalloutPriority p =
                 priority ? priority.value() : CalloutPriority::high;
 
-            callout = std::make_unique<src::Callout>(p, locCode, fn, ccin, sn);
+            callout =
+                std::make_unique<src::Callout>(p, locCode, fn, ccin, sn, mrus);
         }
         catch (const SdBusError& e)
         {
@@ -790,8 +792,8 @@
             addDebugData(msg);
 
             // Just create the callout with empty FRU fields
-            callout = std::make_unique<src::Callout>(CalloutPriority::high,
-                                                     locCode, fn, ccin, sn);
+            callout = std::make_unique<src::Callout>(
+                CalloutPriority::high, locCode, fn, ccin, sn, mrus);
         }
     }
     catch (const SdBusError& e)
@@ -800,8 +802,6 @@
                           ": " + e.what();
         addDebugData(msg);
 
-        // If this were to happen, people would have to look in the UserData
-        // section that contains CALLOUT_INVENTORY_PATH to see what failed.
         callout = std::make_unique<src::Callout>(CalloutPriority::high,
                                                  "no_vpd_for_fru");
     }
@@ -1116,6 +1116,7 @@
     {
         // A hardware FRU
         std::string inventoryPath;
+        std::vector<src::MRU::MRUCallout> mrus;
 
         if (jsonCallout.contains("InventoryPath"))
         {
@@ -1143,6 +1144,11 @@
             }
         }
 
+        if (jsonCallout.contains("MRUs"))
+        {
+            mrus = getMRUsFromJSON(jsonCallout.at("MRUs"));
+        }
+
         // If the location code was also passed in, use that here too
         // so addInventoryCallout doesn't have to look it up.
         std::optional<std::string> lc;
@@ -1151,7 +1157,7 @@
             lc = locCode;
         }
 
-        addInventoryCallout(inventoryPath, priority, lc, dataIface);
+        addInventoryCallout(inventoryPath, priority, lc, dataIface, mrus);
     }
 
     if (callout)
@@ -1187,5 +1193,43 @@
     return priority;
 }
 
+std::vector<src::MRU::MRUCallout>
+    SRC::getMRUsFromJSON(const nlohmann::json& mruJSON)
+{
+    std::vector<src::MRU::MRUCallout> mrus;
+
+    // Looks like:
+    // [
+    //     {
+    //         "ID": 100,
+    //         "Priority": "H"
+    //     }
+    // ]
+    if (!mruJSON.is_array())
+    {
+        addDebugData("MRU callout JSON is not an array");
+        return mrus;
+    }
+
+    for (const auto& mruCallout : mruJSON)
+    {
+        try
+        {
+            auto priority = getPriorityFromJSON(mruCallout);
+            auto id = mruCallout.at("ID").get<uint32_t>();
+
+            src::MRU::MRUCallout mru{static_cast<uint32_t>(priority), id};
+            mrus.push_back(std::move(mru));
+        }
+        catch (const std::exception& e)
+        {
+            addDebugData(fmt::format("Invalid MRU entry in JSON: {}: {}",
+                                     mruCallout.dump(), e.what()));
+        }
+    }
+
+    return mrus;
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/src.hpp b/extensions/openpower-pels/src.hpp
index 0ec01f6..9757a92 100644
--- a/extensions/openpower-pels/src.hpp
+++ b/extensions/openpower-pels/src.hpp
@@ -384,11 +384,14 @@
      * @param[in] priority - An optional priority (uses high if nullopt)
      * @param[in] locationCode - The expanded location code (or look it up)
      * @param[in] dataIface - The DataInterface object
+     * @param[in] mrus - The MRUs to add to the callout
      */
-    void addInventoryCallout(const std::string& inventoryPath,
-                             const std::optional<CalloutPriority>& priority,
-                             const std::optional<std::string>& locationCode,
-                             const DataInterfaceBase& dataIface);
+    void
+        addInventoryCallout(const std::string& inventoryPath,
+                            const std::optional<CalloutPriority>& priority,
+                            const std::optional<std::string>& locationCode,
+                            const DataInterfaceBase& dataIface,
+                            const std::vector<src::MRU::MRUCallout>& mrus = {});
 
     /**
      * @brief Adds FRU callouts based on the registry entry JSON
@@ -462,6 +465,15 @@
     CalloutPriority getPriorityFromJSON(const nlohmann::json& json);
 
     /**
+     * @brief Exracts MRU values and their priorities from the
+     *        input JSON array.
+     *
+     * @param[in] mruJSON - The JSON array
+     */
+    std::vector<src::MRU::MRUCallout>
+        getMRUsFromJSON(const nlohmann::json& mruJSON);
+
+    /**
      * @brief The SRC version field
      */
     uint8_t _version;
diff --git a/test/openpower-pels/src_test.cpp b/test/openpower-pels/src_test.cpp
index c4b80a4..5795ece 100644
--- a/test/openpower-pels/src_test.cpp
+++ b/test/openpower-pels/src_test.cpp
@@ -990,6 +990,15 @@
         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
+
+        auto& mruCallouts = callouts[0]->mru();
+        ASSERT_TRUE(mruCallouts);
+        auto& mrus = mruCallouts->mrus();
+        ASSERT_EQ(mrus.size(), 2);
+        EXPECT_EQ(mrus[0].id, 42);
+        EXPECT_EQ(mrus[0].priority, 'H');
+        EXPECT_EQ(mrus[1].id, 43);
+        EXPECT_EQ(mrus[1].priority, 'M');
     }
 
     // Check callout 1