PEL: Find message registry callouts to use

Add Registry::getCallouts() to find, based on the system type and
AdditionalData property, the callouts that apply for the callout JSON
passed in.  This will be used to choose the callouts to add to a PEL
based on the system and current failure informaton.

These files describe the actual JSON format:
* extensions/openpower-pels/registry/README.md
* extensions/openpower-pels/registry/schema/schema.json

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I5901e459981b10f6c338d3ef827d9128338dca50
diff --git a/extensions/openpower-pels/registry.cpp b/extensions/openpower-pels/registry.cpp
index 8b7ac18..830a013 100644
--- a/extensions/openpower-pels/registry.cpp
+++ b/extensions/openpower-pels/registry.cpp
@@ -262,6 +262,262 @@
     return id;
 }
 
+/**
+ * @brief Says if the JSON is the format that contains AdditionalData keys
+ *        as in index into them.
+ *
+ * @param[in] json - The highest level callout JSON
+ *
+ * @return bool - If it is the AdditionalData format or not
+ */
+bool calloutUsesAdditionalData(const nlohmann::json& json)
+{
+    return (json.contains("ADName") &&
+            json.contains("CalloutsWithTheirADValues"));
+}
+
+/**
+ * @brief Finds the callouts to use when there is no AdditionalData,
+ *        but the system type may be used as a key.
+ *
+ * One entry in the array looks like the following.  The System key
+ * is optional and if not present it means that entry applies to
+ * every configuration that doesn't have another entry with a matching
+ * System key.
+ *
+ *    {
+ *        "System": "system1",
+ *        "CalloutList":
+ *        [
+ *            {
+ *                "Priority": "high",
+ *                "LocCode": "P1-C1"
+ *            },
+ *            {
+ *                "Priority": "low",
+ *                "LocCode": "P1"
+ *            }
+ *        ]
+ *    }
+ */
+const nlohmann::json& findCalloutList(const nlohmann::json& json,
+                                      const std::string& systemType)
+{
+    const nlohmann::json* callouts = nullptr;
+
+    if (!json.is_array())
+    {
+        throw std::runtime_error{"findCalloutList was not passed a JSON array"};
+    }
+
+    // The entry with the system type match will take precedence over the entry
+    // without any "System" field in it at all, which will match all other
+    // cases.
+    for (const auto& calloutList : json)
+    {
+        if (calloutList.contains("System"))
+        {
+            if (systemType == calloutList["System"].get<std::string>())
+            {
+                callouts = &calloutList["CalloutList"];
+                break;
+            }
+        }
+        else
+        {
+            // Any entry with no System key
+            callouts = &calloutList["CalloutList"];
+        }
+    }
+
+    if (!callouts)
+    {
+        log<level::WARNING>(
+            "No matching system type entry or default system type entry "
+            " for PEL callout list",
+            entry("SYSTEMTYPE=%s", systemType.c_str()));
+
+        throw std::runtime_error{
+            "Could not find a CalloutList JSON for this error and system type"};
+    }
+
+    return *callouts;
+}
+
+/**
+ * @brief Creates a RegistryCallout based on the input JSON.
+ *
+ * The JSON looks like:
+ *     {
+ *          "Priority": "high",
+ *          "LocCode": "E1"
+ *          ...
+ *     }
+ *
+ * Schema validation enforces what keys are present.
+ *
+ * @param[in] json - The JSON dictionary entry for a callout
+ *
+ * @return RegistryCallout - A filled in RegistryCallout
+ */
+RegistryCallout makeRegistryCallout(const nlohmann::json& json)
+{
+    RegistryCallout callout;
+
+    callout.priority = "high";
+
+    if (json.contains("Priority"))
+    {
+        callout.priority = json["Priority"].get<std::string>();
+    }
+
+    if (json.contains("LocCode"))
+    {
+        callout.locCode = json["LocCode"].get<std::string>();
+    }
+
+    if (json.contains("Procedure"))
+    {
+        callout.procedure = json["Procedure"].get<std::string>();
+    }
+    else if (json.contains("SymbolicFRU"))
+    {
+        callout.symbolicFRU = json["SymbolicFRU"].get<std::string>();
+    }
+    else if (json.contains("SymbolicFRUTrusted"))
+    {
+        callout.symbolicFRUTrusted =
+            json["SymbolicFRUTrusted"].get<std::string>();
+    }
+
+    return callout;
+}
+
+/**
+ * @brief Returns the callouts to use when an AdditionalData key is
+ *        required to find the correct entries.
+ *
+ *       The System property is used to find which CalloutList to use.
+ *       If System is missing, then that CalloutList is valid for
+ *       everything.
+ *
+ * The JSON looks like:
+ *    [
+ *        {
+ *            "System": "systemA",
+ *            "CalloutList":
+ *            [
+ *                {
+ *                    "Priority": "high",
+ *                    "LocCode": "P1-C5"
+ *                }
+ *            ]
+ *         }
+ *    ]
+ *
+ * @param[in] json - The callout JSON
+ * @param[in] systemType - The system type from EntityManager
+ *
+ * @return std::vector<RegistryCallout> - The callouts to use
+ */
+std::vector<RegistryCallout> getCalloutsWithoutAD(const nlohmann::json& json,
+                                                  const std::string& systemType)
+{
+    std::vector<RegistryCallout> calloutEntries;
+
+    // Find the CalloutList to use based on the system type
+    const auto& calloutList = findCalloutList(json, systemType);
+
+    // We finally found the callouts, make the objects.
+    for (const auto& callout : calloutList)
+    {
+        calloutEntries.push_back(std::move(makeRegistryCallout(callout)));
+    }
+
+    return calloutEntries;
+}
+
+/**
+ * @brief Returns the callouts to use when an AdditionalData key is
+ *        required to find the correct entries.
+ *
+ * The JSON looks like:
+ *    {
+ *        "ADName": "PROC_NUM",
+ *        "CalloutsWithTheirADValues":
+ *        [
+ *            {
+ *                "ADValue": "0",
+ *                "Callouts":
+ *                [
+ *                    {
+ *                        "CalloutList":
+ *                        [
+ *                            {
+ *                                "Priority": "high",
+ *                                "LocCode": "P1-C5"
+ *                            }
+ *                        ]
+ *                    }
+ *                ]
+ *            }
+ *        ]
+ *     }
+ *
+ * Note that the "Callouts" entry above is the same as the top level
+ * entry used when there is no AdditionalData key.
+ *
+ * @param[in] json - The callout JSON
+ * @param[in] systemType - The system type from EntityManager
+ * @param[in] additionalData - The AdditionalData property
+ *
+ * @return std::vector<RegistryCallout> - The callouts to use
+ */
+std::vector<RegistryCallout>
+    getCalloutsUsingAD(const nlohmann::json& json,
+                       const std::string& systemType,
+                       const AdditionalData& additionalData)
+{
+    // This indicates which AD field we'll be using
+    auto keyName = json["ADName"].get<std::string>();
+
+    // Get the actual value from the AD data
+    auto adValue = additionalData.getValue(keyName);
+
+    if (!adValue)
+    {
+        // The AdditionalData did not contain the necessary key
+        log<level::WARNING>(
+            "The PEL message registry callouts JSON "
+            "said to use an AdditionalData key that isn't in the "
+            "AdditionalData event log property",
+            entry("ADNAME=%s\n", keyName.c_str()));
+        throw std::runtime_error{
+            "Missing AdditionalData entry for this callout"};
+    }
+
+    const auto& callouts = json["CalloutsWithTheirADValues"];
+
+    // find the entry with that AD value
+    auto it = std::find_if(
+        callouts.begin(), callouts.end(), [adValue](const nlohmann::json& j) {
+            return *adValue == j["ADValue"].get<std::string>();
+        });
+
+    if (it == callouts.end())
+    {
+        log<level::WARNING>(
+            "No callout entry found for the AdditionalData value used",
+            entry("AD_VALUE=%s", adValue->c_str()));
+
+        throw std::runtime_error{
+            "No callout entry found for the AdditionalData value used"};
+    }
+
+    // Proceed to find the callouts possibly based on system type.
+    return getCalloutsWithoutAD((*it)["Callouts"], systemType);
+}
+
 } // namespace helper
 
 std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
@@ -432,6 +688,22 @@
     return registry;
 }
 
+std::vector<RegistryCallout>
+    Registry::getCallouts(const nlohmann::json& calloutJSON,
+                          const std::string& systemType,
+                          const AdditionalData& additionalData)
+{
+    // The JSON may either use an AdditionalData key
+    // as an index, or not.
+    if (helper::calloutUsesAdditionalData(calloutJSON))
+    {
+        return helper::getCalloutsUsingAD(calloutJSON, systemType,
+                                          additionalData);
+    }
+
+    return helper::getCalloutsWithoutAD(calloutJSON, systemType);
+}
+
 } // namespace message
 } // namespace pels
 } // namespace openpower