PEL: Adding the support for Systems Key in message registry

The current implementation has the support for adding system specific
callouts with the help of 'System' key in message_registry.json.

Adding one more key named 'Systems' where it can have array of system
names in the form of strings. The 'Systems' key can be used to define
the shared callouts for a group of systems.

A unique callout to a specific system can be added using the existing
System key. If both 'System' and 'Systems' are not present or not
matching with the system name, then the default calloutList will be
taken if configured.

Tested:

The test setup has the following names for the compatible interface.

```
busctl -j get-property xyz.openbmc_project.EntityManager
/xyz/openbmc_project/inventory/system/chassis/Rainier_2U_Chassis
xyz.openbmc_project.Inventory.Decorator.Compatible Names
{
	"type" : "as",
	"data" : [
		"com.ibm.Hardware.Chassis.Model.Rainier2U",
		"com.ibm.Hardware.Chassis.Model.Rainier"
	]
}
```
The callout section in the message_registry.json for TestError1 is
defined as below.
```
"Callouts": [
        {
            "Systems": ["com.ibm.Hardware.Chassis.Model.Rainier",
                     "com.ibm.Hardware.Chassis.Model.Blue_Ridge"],
            "CalloutList": [
                {"Priority": "medium", "SymbolicFRU": "service_docs"}
            ]
        },
        {
            "System": "com.ibm.Hardware.Chassis.Model.Rainier",
            "CalloutList": [
                {"Priority": "high", "Procedure": "BMC0001"}
            ]
        },
        {
             "CalloutList": [
                { "LocCode": "P0", "Priority": "high" },
                { "LocCode": "P0-C15","Priority": "low" }
            ]
        }
    ]
```

Leads to PEL callouts section as below:
```
"Callout Section": {
        "Callout Count":        "2",
        "Callouts": [{
            "FRU Type":         "Maintenance Procedure Required",
            "Priority":         "Mandatory, replace all with this type
                                 as a unit",
            "Procedure":        "BMC0001"
        }, {
            "FRU Type":         "Symbolic FRU",
            "Priority":         "Medium Priority",
            "Part Number":      "SVCDOCS"
        }]
    }
```

Signed-off-by: Arya K Padman <aryakpadman@gmail.com>
Change-Id: Iea65816dcb822bb07043897488a6251929548dc7
diff --git a/extensions/openpower-pels/registry.cpp b/extensions/openpower-pels/registry.cpp
index 4dc1f0b..05b286a 100644
--- a/extensions/openpower-pels/registry.cpp
+++ b/extensions/openpower-pels/registry.cpp
@@ -21,6 +21,7 @@
 
 #include <phosphor-logging/lg2.hpp>
 
+#include <algorithm>
 #include <fstream>
 
 namespace openpower
@@ -330,10 +331,19 @@
  * @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.
+ * A sample calloutList array looks like the following.  The System and Systems
+ * key are optional.
+ *
+ * System key - Value of the key will be the system name as a string. The
+ * callouts for a specific system can define under this key.
+ *
+ * Systems key - Value of the key will be an array of system names in the form
+ * of string. The callouts common to the systems mentioned in the array can
+ * define under this key.
+ *
+ * If both System and Systems not present it means that entry applies to every
+ * configuration that doesn't have another entry with a matching System and
+ * Systems key.
  *
  *    {
  *        "System": "system1",
@@ -348,42 +358,85 @@
  *                "LocCode": "P1"
  *            }
  *        ]
+ *    },
+ *    {
+ *        "Systems": ["system1", 'system2"],
+ *        "CalloutList":
+ *        [
+ *            {
+ *                "Priority": "high",
+ *                "LocCode": "P0-C1"
+ *            },
+ *            {
+ *                "Priority": "low",
+ *                "LocCode": "P0"
+ *            }
+ *        ]
  *    }
+ *
+ * @param[in] json - The callout JSON
+ * @param[in] systemNames - List of compatible system type names
+ * @param[out] calloutLists - The JSON array which will hold the calloutlist to
+ * use specific to the system.
+ *
+ * @return - Throws runtime exception if json is not an array or if calloutLists
+ *           is empty.
  */
-const nlohmann::json&
-    findCalloutList(const nlohmann::json& json,
-                    const std::vector<std::string>& systemNames)
+static void findCalloutList(const nlohmann::json& json,
+                            const std::vector<std::string>& systemNames,
+                            nlohmann::json& calloutLists)
 {
-    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)
+    // Flag to indicate whether system specific callouts found or not
+    bool foundCallouts = false;
+
+    for (const auto& callouts : json)
     {
-        if (calloutList.contains("System"))
+        if (callouts.contains("System"))
         {
-            if (std::find(systemNames.begin(), systemNames.end(),
-                          calloutList["System"].get<std::string>()) !=
+            if (std::ranges::find(systemNames,
+                                  callouts["System"].get<std::string>()) !=
                 systemNames.end())
             {
-                callouts = &calloutList["CalloutList"];
-                break;
+                calloutLists.insert(calloutLists.end(),
+                                    callouts["CalloutList"].begin(),
+                                    callouts["CalloutList"].end());
+                foundCallouts = true;
             }
+            continue;
         }
-        else
+
+        if (callouts.contains("Systems"))
         {
-            // Any entry with no System key
-            callouts = &calloutList["CalloutList"];
+            std::vector<std::string> systems =
+                callouts["Systems"].get<std::vector<std::string>>();
+            auto inSystemNames = [systemNames](const auto& system) {
+                return (std::ranges::find(systemNames, system) !=
+                        systemNames.end());
+            };
+            if (std::ranges::any_of(systems, inSystemNames))
+            {
+                calloutLists.insert(calloutLists.end(),
+                                    callouts["CalloutList"].begin(),
+                                    callouts["CalloutList"].end());
+                foundCallouts = true;
+            }
+            continue;
+        }
+
+        // Any entry if neither System/Systems key matches with system name
+        if (!foundCallouts)
+        {
+            calloutLists.insert(calloutLists.end(),
+                                callouts["CalloutList"].begin(),
+                                callouts["CalloutList"].end());
         }
     }
-
-    if (!callouts)
+    if (calloutLists.empty())
     {
         std::string types;
         std::for_each(systemNames.begin(), systemNames.end(),
@@ -396,8 +449,6 @@
         throw std::runtime_error{
             "Could not find a CalloutList JSON for this error and system name"};
     }
-
-    return *callouts;
 }
 
 /**
@@ -464,18 +515,34 @@
  *       everything.
  *
  * The JSON looks like:
- *    [
- *        {
- *            "System": "systemA",
- *            "CalloutList":
- *            [
- *                {
- *                    "Priority": "high",
- *                    "LocCode": "P1-C5"
- *                }
- *            ]
- *         }
- *    ]
+ *    {
+ *        "System": "system1",
+ *        "CalloutList":
+ *        [
+ *            {
+ *                "Priority": "high",
+ *                "LocCode": "P1-C1"
+ *            },
+ *            {
+ *                "Priority": "low",
+ *                "LocCode": "P1"
+ *            }
+ *        ]
+ *    },
+ *    {
+ *        "Systems": ["system1", 'system2"],
+ *        "CalloutList":
+ *        [
+ *            {
+ *                "Priority": "high",
+ *                "LocCode": "P0-C1"
+ *            },
+ *            {
+ *                "Priority": "low",
+ *                "LocCode": "P0"
+ *            }
+ *        ]
+ *    }
  *
  * @param[in] json - The callout JSON
  * @param[in] systemNames - List of compatible system type names
@@ -488,11 +555,13 @@
 {
     std::vector<RegistryCallout> calloutEntries;
 
+    nlohmann::json calloutLists = nlohmann::json::array();
+
     // Find the CalloutList to use based on the system type
-    const auto& calloutList = findCalloutList(json, systemNames);
+    findCalloutList(json, systemNames, calloutLists);
 
     // We finally found the callouts, make the objects.
-    for (const auto& callout : calloutList)
+    for (const auto& callout : calloutLists)
     {
         calloutEntries.push_back(std::move(makeRegistryCallout(callout)));
     }