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
diff --git a/extensions/openpower-pels/registry.hpp b/extensions/openpower-pels/registry.hpp
index 09666f3..047853d 100644
--- a/extensions/openpower-pels/registry.hpp
+++ b/extensions/openpower-pels/registry.hpp
@@ -1,4 +1,6 @@
#pragma once
+#include "additional_data.hpp"
+
#include <filesystem>
#include <nlohmann/json.hpp>
#include <optional>
@@ -169,6 +171,18 @@
};
/**
+ * @brief Holds callout information pulled out of the JSON.
+ */
+struct RegistryCallout
+{
+ std::string priority;
+ std::string locCode;
+ std::string procedure;
+ std::string symbolicFRU;
+ std::string symbolicFRUTrusted;
+};
+
+/**
* @class Registry
*
* This class wraps the message registry JSON data and allows one to find
@@ -235,6 +249,26 @@
std::optional<Entry> lookup(const std::string& name, LookupType type,
bool toCache = false);
+ /**
+ * @brief Find the callouts to put into the PEL based on the calloutJSON
+ * data.
+ *
+ * The system type and AdditionalData are used to index into the correct
+ * callout table.
+ *
+ * Throws exceptions on failures.
+ *
+ * @param[in] calloutJSON - Where to look up the callouts
+ * @param[in] systemType - The system type from EntityManager
+ * @param[in] additionalData - The AdditionalData property
+ *
+ * @return std::vector<RegistryCallout> - The callouts to use
+ */
+ static std::vector<RegistryCallout>
+ getCallouts(const nlohmann::json& calloutJSON,
+ const std::string& systemType,
+ const AdditionalData& additionalData);
+
private:
/**
* @brief Parse message registry file using nlohmann::json
diff --git a/test/openpower-pels/registry_test.cpp b/test/openpower-pels/registry_test.cpp
index 2944ce0..92c1b14 100644
--- a/test/openpower-pels/registry_test.cpp
+++ b/test/openpower-pels/registry_test.cpp
@@ -22,6 +22,7 @@
#include <gtest/gtest.h>
using namespace openpower::pels::message;
+using namespace openpower::pels;
namespace fs = std::filesystem;
const auto registryData = R"(
@@ -334,3 +335,270 @@
EXPECT_THROW(getComponentID(0x11, 0x8800, R"({})"_json, "foo"),
std::runtime_error);
}
+
+// Test when callouts are in the JSON.
+TEST_F(RegistryTest, TestGetCallouts)
+{
+ {
+ // Callouts without AD, that depend on system type,
+ // where there is a default entry without a system type.
+ auto json = R"(
+ [
+ {
+ "System": "system1",
+ "CalloutList":
+ [
+ {
+ "Priority": "high",
+ "LocCode": "P1-C1"
+ },
+ {
+ "Priority": "low",
+ "LocCode": "P1"
+ },
+ {
+ "Priority": "low",
+ "SymbolicFRU": "service_docs"
+ }
+ ]
+ },
+ {
+ "CalloutList":
+ [
+ {
+ "Priority": "medium",
+ "Procedure": "no_vpd_for_fru"
+ },
+ {
+ "Priority": "low",
+ "LocCode": "P3-C8",
+ "SymbolicFRUTrusted": "service_docs"
+ }
+ ]
+
+ }
+ ])"_json;
+
+ AdditionalData ad;
+
+ auto callouts = Registry::getCallouts(json, "system1", ad);
+ EXPECT_EQ(callouts.size(), 3);
+ EXPECT_EQ(callouts[0].priority, "high");
+ EXPECT_EQ(callouts[0].locCode, "P1-C1");
+ EXPECT_EQ(callouts[0].procedure, "");
+ EXPECT_EQ(callouts[0].symbolicFRU, "");
+ EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
+ EXPECT_EQ(callouts[1].priority, "low");
+ EXPECT_EQ(callouts[1].locCode, "P1");
+ EXPECT_EQ(callouts[1].procedure, "");
+ EXPECT_EQ(callouts[1].symbolicFRU, "");
+ EXPECT_EQ(callouts[1].symbolicFRUTrusted, "");
+ EXPECT_EQ(callouts[2].priority, "low");
+ EXPECT_EQ(callouts[2].locCode, "");
+ EXPECT_EQ(callouts[2].procedure, "");
+ EXPECT_EQ(callouts[2].symbolicFRU, "service_docs");
+ EXPECT_EQ(callouts[2].symbolicFRUTrusted, "");
+
+ // system2 isn't in the JSON, so it will pick the default one
+ callouts = Registry::getCallouts(json, "system2", ad);
+ EXPECT_EQ(callouts.size(), 2);
+ EXPECT_EQ(callouts[0].priority, "medium");
+ EXPECT_EQ(callouts[0].locCode, "");
+ EXPECT_EQ(callouts[0].procedure, "no_vpd_for_fru");
+ EXPECT_EQ(callouts[0].symbolicFRU, "");
+ EXPECT_EQ(callouts[1].priority, "low");
+ EXPECT_EQ(callouts[1].locCode, "P3-C8");
+ EXPECT_EQ(callouts[1].procedure, "");
+ EXPECT_EQ(callouts[1].symbolicFRU, "");
+ EXPECT_EQ(callouts[1].symbolicFRUTrusted, "service_docs");
+ }
+
+ // Empty JSON array (treated as an error)
+ {
+ auto json = R"([])"_json;
+ AdditionalData ad;
+ EXPECT_THROW(Registry::getCallouts(json, "system1", ad),
+ std::runtime_error);
+ }
+
+ {
+ // Callouts without AD, that depend on system type,
+ // where there isn't a default entry without a system type.
+ auto json = R"(
+ [
+ {
+ "System": "system1",
+ "CalloutList":
+ [
+ {
+ "Priority": "high",
+ "LocCode": "P1-C1"
+ },
+ {
+ "Priority": "low",
+ "LocCode": "P1",
+ "SymbolicFRU": "1234567"
+ }
+ ]
+ },
+ {
+ "System": "system2",
+ "CalloutList":
+ [
+ {
+ "Priority": "medium",
+ "LocCode": "P7",
+ "CalloutType": "tool_fru"
+ }
+ ]
+
+ }
+ ])"_json;
+
+ AdditionalData ad;
+
+ auto callouts = Registry::getCallouts(json, "system1", ad);
+ EXPECT_EQ(callouts.size(), 2);
+ EXPECT_EQ(callouts[0].priority, "high");
+ EXPECT_EQ(callouts[0].locCode, "P1-C1");
+ EXPECT_EQ(callouts[0].procedure, "");
+ EXPECT_EQ(callouts[0].symbolicFRU, "");
+ EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
+ EXPECT_EQ(callouts[1].priority, "low");
+ EXPECT_EQ(callouts[1].locCode, "P1");
+ EXPECT_EQ(callouts[1].procedure, "");
+ EXPECT_EQ(callouts[1].symbolicFRU, "1234567");
+ EXPECT_EQ(callouts[1].symbolicFRUTrusted, "");
+
+ callouts = Registry::getCallouts(json, "system2", ad);
+ EXPECT_EQ(callouts.size(), 1);
+ EXPECT_EQ(callouts[0].priority, "medium");
+ EXPECT_EQ(callouts[0].locCode, "P7");
+ EXPECT_EQ(callouts[0].procedure, "");
+ EXPECT_EQ(callouts[0].symbolicFRU, "");
+ EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
+
+ // There is no entry for system3 or a default system,
+ // so this should fail.
+ EXPECT_THROW(Registry::getCallouts(json, "system3", ad),
+ std::runtime_error);
+ }
+
+ {
+ // Callouts that use the AdditionalData key PROC_NUM
+ // as an index into them, along with a system type.
+ // It supports PROC_NUMs 0 and 1.
+ auto json = R"(
+ {
+ "ADName": "PROC_NUM",
+ "CalloutsWithTheirADValues":
+ [
+ {
+ "ADValue": "0",
+ "Callouts":
+ [
+ {
+ "System": "system3",
+ "CalloutList":
+ [
+ {
+ "Priority": "high",
+ "LocCode": "P1-C5"
+ },
+ {
+ "Priority": "medium",
+ "LocCode": "P1-C6",
+ "SymbolicFRU": "1234567"
+ },
+ {
+ "Priority": "low",
+ "Procedure": "no_vpd_for_fru",
+ "CalloutType": "config_procedure"
+ }
+ ]
+ },
+ {
+ "CalloutList":
+ [
+ {
+ "Priority": "low",
+ "LocCode": "P55"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ADValue": "1",
+ "Callouts":
+ [
+ {
+ "CalloutList":
+ [
+ {
+ "Priority": "high",
+ "LocCode": "P1-C6",
+ "CalloutType": "external_fru"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ })"_json;
+
+ {
+ // Find callouts for PROC_NUM 0 on system3
+ std::vector<std::string> adData{"PROC_NUM=0"};
+ AdditionalData ad{adData};
+
+ auto callouts = Registry::getCallouts(json, "system3", ad);
+ EXPECT_EQ(callouts.size(), 3);
+ EXPECT_EQ(callouts[0].priority, "high");
+ EXPECT_EQ(callouts[0].locCode, "P1-C5");
+ EXPECT_EQ(callouts[0].procedure, "");
+ EXPECT_EQ(callouts[0].symbolicFRU, "");
+ EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
+ EXPECT_EQ(callouts[1].priority, "medium");
+ EXPECT_EQ(callouts[1].locCode, "P1-C6");
+ EXPECT_EQ(callouts[1].procedure, "");
+ EXPECT_EQ(callouts[1].symbolicFRU, "1234567");
+ EXPECT_EQ(callouts[1].symbolicFRUTrusted, "");
+ EXPECT_EQ(callouts[2].priority, "low");
+ EXPECT_EQ(callouts[2].locCode, "");
+ EXPECT_EQ(callouts[2].procedure, "no_vpd_for_fru");
+ EXPECT_EQ(callouts[2].symbolicFRU, "");
+ EXPECT_EQ(callouts[2].symbolicFRUTrusted, "");
+
+ // Find callouts for PROC_NUM 0 that uses the default system entry.
+ callouts = Registry::getCallouts(json, "system99", ad);
+ EXPECT_EQ(callouts.size(), 1);
+ EXPECT_EQ(callouts[0].priority, "low");
+ EXPECT_EQ(callouts[0].locCode, "P55");
+ EXPECT_EQ(callouts[0].procedure, "");
+ EXPECT_EQ(callouts[0].symbolicFRU, "");
+ EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
+ }
+ {
+ // Find callouts for PROC_NUM 1 that uses a default system entry.
+ std::vector<std::string> adData{"PROC_NUM=1"};
+ AdditionalData ad{adData};
+
+ auto callouts = Registry::getCallouts(json, "system1", ad);
+ EXPECT_EQ(callouts.size(), 1);
+ EXPECT_EQ(callouts[0].priority, "high");
+ EXPECT_EQ(callouts[0].locCode, "P1-C6");
+ EXPECT_EQ(callouts[0].procedure, "");
+ EXPECT_EQ(callouts[0].symbolicFRU, "");
+ EXPECT_EQ(callouts[0].symbolicFRUTrusted, "");
+ }
+ {
+ // There is no entry for PROC_NUM 2, it will fail.
+ std::vector<std::string> adData{"PROC_NUM=2"};
+ AdditionalData ad{adData};
+
+ EXPECT_THROW(Registry::getCallouts(json, "system1", ad),
+ std::runtime_error);
+ }
+ }
+}