PEL: Add SRC callouts from message registry

When an SRC is being built for a new PEL, it will now also add callouts
from the message registry JSON for that error.

This commit only adds support for callouts of maintenance procedures or
symbolic FRUs.  A future commit will add support for calling out a
regular piece of hardware

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I78c0d3440c748f21145d452742e3aa682f26003b
diff --git a/extensions/openpower-pels/data_interface.hpp b/extensions/openpower-pels/data_interface.hpp
index 5b6ff94..6a3e326 100644
--- a/extensions/openpower-pels/data_interface.hpp
+++ b/extensions/openpower-pels/data_interface.hpp
@@ -226,6 +226,18 @@
                                     std::string& ccin,
                                     std::string& serialNumber) const = 0;
 
+    /**
+     * @brief Gets the system type from Entity Manager
+     *
+     * @param[in] std::string - The system type string
+     */
+    virtual std::string getSystemType() const
+    {
+        // TODO, not implemented by entity manager yet, but adding now
+        // so it can be mocked.
+        return _systemType;
+    }
+
   protected:
     /**
      * @brief Sets the host on/off state and runs any
@@ -326,6 +338,11 @@
      * @brief The motherboard CCIN
      */
     std::string _motherboardCCIN;
+
+    /**
+     * @brief The system type
+     */
+    std::string _systemType;
 };
 
 /**
diff --git a/extensions/openpower-pels/src.cpp b/extensions/openpower-pels/src.cpp
index 5310947..0899417 100644
--- a/extensions/openpower-pels/src.cpp
+++ b/extensions/openpower-pels/src.cpp
@@ -120,7 +120,7 @@
 
     _asciiString = std::make_unique<src::AsciiString>(regEntry);
 
-    addCallouts(additionalData, dataIface);
+    addCallouts(regEntry, additionalData, dataIface);
 
     _size = baseSRCSize;
     _size += _callouts ? _callouts->flattenedSize() : 0;
@@ -502,7 +502,8 @@
     return ps;
 }
 
-void SRC::addCallouts(const AdditionalData& additionalData,
+void SRC::addCallouts(const message::Entry& regEntry,
+                      const AdditionalData& additionalData,
                       const DataInterfaceBase& dataIface)
 {
     auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
@@ -512,6 +513,11 @@
     }
 
     // TODO: CALLOUT_DEVICE_PATH
+
+    if (regEntry.callouts)
+    {
+        addRegistryCallouts(regEntry, additionalData, dataIface);
+    }
 }
 
 void SRC::addInventoryCallout(const std::string& inventoryPath,
@@ -543,8 +549,79 @@
     }
 
     _callouts->addCallout(std::move(callout));
+}
 
-} // namespace pels
+void SRC::addRegistryCallouts(const message::Entry& regEntry,
+                              const AdditionalData& additionalData,
+                              const DataInterfaceBase& dataIface)
+{
+    try
+    {
+        auto systemType = dataIface.getSystemType();
+
+        auto regCallouts = message::Registry::getCallouts(
+            regEntry.callouts.value(), systemType, additionalData);
+
+        for (const auto& regCallout : regCallouts)
+        {
+            addRegistryCallout(regCallout, dataIface);
+        }
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("Error parsing PEL message registry callout JSON",
+                        entry("ERROR=%s", e.what()));
+    }
+}
+
+void SRC::addRegistryCallout(const message::RegistryCallout& regCallout,
+                             const DataInterfaceBase& dataIface)
+{
+    std::unique_ptr<src::Callout> callout;
+
+    // TODO: expand this location code.
+    auto locCode = regCallout.locCode;
+
+    // Via the PEL values table, get the priority enum.
+    // The schema will have validated the priority was a valid value.
+    auto priorityIt =
+        pv::findByName(regCallout.priority, pv::calloutPriorityValues);
+    assert(priorityIt != pv::calloutPriorityValues.end());
+    auto priority =
+        static_cast<CalloutPriority>(std::get<pv::fieldValuePos>(*priorityIt));
+
+    if (!regCallout.procedure.empty())
+    {
+        // Procedure callout
+        callout =
+            std::make_unique<src::Callout>(priority, regCallout.procedure);
+    }
+    else if (!regCallout.symbolicFRU.empty())
+    {
+        // Symbolic FRU callout
+        callout = std::make_unique<src::Callout>(
+            priority, regCallout.symbolicFRU, locCode, false);
+    }
+    else if (!regCallout.symbolicFRUTrusted.empty())
+    {
+        // Symbolic FRU with trusted location code callout
+
+        // The registry wants it to be trusted, but that requires a valid
+        // location code for it to actually be.
+        callout = std::make_unique<src::Callout>(
+            priority, regCallout.symbolicFRUTrusted, locCode, !locCode.empty());
+    }
+    else
+    {
+        // TODO: HW callouts
+    }
+
+    if (callout)
+    {
+        createCalloutsObject();
+        _callouts->addCallout(std::move(callout));
+    }
+}
 
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/src.hpp b/extensions/openpower-pels/src.hpp
index a9fecf8..52a973c 100644
--- a/extensions/openpower-pels/src.hpp
+++ b/extensions/openpower-pels/src.hpp
@@ -345,10 +345,12 @@
      * The callout sources are the AdditionalData event log property
      * and the message registry JSON.
      *
-     * @param[in] additionalData - the AdditionalData values
+     * @param[in] regEntry - The message registry entry for the error
+     * @param[in] additionalData - The AdditionalData values
      * @param[in] dataIface - The DataInterface object
      */
-    void addCallouts(const AdditionalData& additionalData,
+    void addCallouts(const message::Entry& regEntry,
+                     const AdditionalData& additionalData,
                      const DataInterfaceBase& dataIface);
 
     /**
@@ -361,6 +363,26 @@
                              const DataInterfaceBase& dataIface);
 
     /**
+     * @brief Adds FRU callouts based on the registry entry JSON
+     *       for this error.
+     * @param[in] regEntry - The message registry entry for the error
+     * @param[in] additionalData - The AdditionalData values
+     * @param[in] dataIface - The DataInterface object
+     */
+    void addRegistryCallouts(const message::Entry& regEntry,
+                             const AdditionalData& additionalData,
+                             const DataInterfaceBase& dataIface);
+
+    /**
+     * @brief Adds a single FRU callout from the message registry.
+     *
+     * @param[in] callout - The registry callout structure
+     * @param[in] dataIface - The DataInterface object
+     */
+    void addRegistryCallout(const message::RegistryCallout& callout,
+                            const DataInterfaceBase& dataIface);
+
+    /**
      * @brief Creates the Callouts object _callouts
      *        so that callouts can be added to it.
      */
diff --git a/test/openpower-pels/mocks.hpp b/test/openpower-pels/mocks.hpp
index 1654a9d..c003dd8 100644
--- a/test/openpower-pels/mocks.hpp
+++ b/test/openpower-pels/mocks.hpp
@@ -33,6 +33,7 @@
                 (const std::string&, std::string&, std::string&, std::string&,
                  std::string&),
                 (const override));
+    MOCK_METHOD(std::string, getSystemType, (), (const override));
 
     void changeHostState(bool newState)
     {
diff --git a/test/openpower-pels/src_test.cpp b/test/openpower-pels/src_test.cpp
index c982ca5..9342050 100644
--- a/test/openpower-pels/src_test.cpp
+++ b/test/openpower-pels/src_test.cpp
@@ -411,3 +411,115 @@
     ASSERT_TRUE(src.callouts());
     EXPECT_EQ(src.callouts()->callouts().size(), 1);
 }
+
+TEST_F(SRCTest, RegistryCalloutTest)
+{
+    message::Entry entry;
+    entry.src.type = 0xBD;
+    entry.src.reasonCode = 0xABCD;
+    entry.subsystem = 0x42;
+    entry.src.powerFault = false;
+
+    entry.callouts = R"(
+        [
+        {
+            "System": "systemA",
+            "CalloutList":
+            [
+                {
+                    "Priority": "high",
+                    "SymbolicFRU": "service_docs"
+                },
+                {
+                    "Priority": "medium",
+                    "Procedure": "no_vpd_for_fru"
+                }
+            ]
+        },
+        {
+            "System": "systemB",
+            "CalloutList":
+            [
+                {
+                    "Priority": "high",
+                    "LocCode": "P0-C8",
+                    "SymbolicFRUTrusted": "service_docs"
+                },
+                {
+                    "Priority": "medium",
+                    "SymbolicFRUTrusted": "service_docs"
+                }
+            ]
+
+        }
+        ])"_json;
+
+    {
+        // Call out a symbolic FRU and a procedure
+        AdditionalData ad;
+        NiceMock<MockDataInterface> dataIface;
+        EXPECT_CALL(dataIface, getSystemType).WillOnce(Return("systemA"));
+
+        SRC src{entry, ad, dataIface};
+
+        auto& callouts = src.callouts()->callouts();
+        ASSERT_EQ(callouts.size(), 2);
+
+        EXPECT_EQ(callouts[0]->locationCodeSize(), 0);
+        EXPECT_EQ(callouts[0]->priority(), 'H');
+
+        EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
+        EXPECT_EQ(callouts[1]->priority(), 'M');
+
+        auto& fru1 = callouts[0]->fruIdentity();
+        EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
+        EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
+        EXPECT_FALSE(fru1->getMaintProc());
+        EXPECT_FALSE(fru1->getSN());
+        EXPECT_FALSE(fru1->getCCIN());
+
+        auto& fru2 = callouts[1]->fruIdentity();
+        EXPECT_EQ(fru2->getMaintProc().value(), "BMCSP01");
+        EXPECT_EQ(fru2->failingComponentType(),
+                  src::FRUIdentity::maintenanceProc);
+        EXPECT_FALSE(fru2->getPN());
+        EXPECT_FALSE(fru2->getSN());
+        EXPECT_FALSE(fru2->getCCIN());
+    }
+
+    {
+        // Call out a trusted symbolic FRU with a location code, and
+        // another one without.
+        AdditionalData ad;
+        NiceMock<MockDataInterface> dataIface;
+        EXPECT_CALL(dataIface, getSystemType).WillOnce(Return("systemB"));
+
+        SRC src{entry, ad, dataIface};
+
+        auto& callouts = src.callouts()->callouts();
+        EXPECT_EQ(callouts.size(), 2);
+
+        EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
+        EXPECT_EQ(callouts[0]->priority(), 'H');
+
+        EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
+        EXPECT_EQ(callouts[1]->priority(), 'M');
+
+        auto& fru1 = callouts[0]->fruIdentity();
+        EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
+        EXPECT_EQ(fru1->failingComponentType(),
+                  src::FRUIdentity::symbolicFRUTrustedLocCode);
+        EXPECT_FALSE(fru1->getMaintProc());
+        EXPECT_FALSE(fru1->getSN());
+        EXPECT_FALSE(fru1->getCCIN());
+
+        // It asked for a trusted symbolic FRU, but no location code
+        // was provided so it is switched back to a normal one
+        auto& fru2 = callouts[1]->fruIdentity();
+        EXPECT_EQ(fru2->getPN().value(), "SVCDOCS");
+        EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRU);
+        EXPECT_FALSE(fru2->getMaintProc());
+        EXPECT_FALSE(fru2->getSN());
+        EXPECT_FALSE(fru2->getCCIN());
+    }
+}