blob: a96f661d8408bdb09536772d7a81d3a11cded54c [file] [log] [blame]
/**
* Copyright © 2019 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "registry.hpp"
#include "json_utils.hpp"
#include "pel_types.hpp"
#include "pel_values.hpp"
#include <phosphor-logging/log.hpp>
#include <fstream>
namespace openpower
{
namespace pels
{
namespace message
{
namespace pv = pel_values;
namespace fs = std::filesystem;
using namespace phosphor::logging;
constexpr auto debugFilePath = "/etc/phosphor-logging/";
namespace helper
{
uint8_t getSubsystem(const std::string& subsystemName)
{
// Get the actual value to use in the PEL for the string name
auto ss = pv::findByName(subsystemName, pv::subsystemValues);
if (ss == pv::subsystemValues.end())
{
// Schema validation should be catching this.
log<level::ERR>("Invalid subsystem name used in message registry",
entry("SUBSYSTEM=%s", subsystemName.c_str()));
throw std::runtime_error("Invalid subsystem used in message registry");
}
return std::get<pv::fieldValuePos>(*ss);
}
uint8_t getSeverity(const std::string& severityName)
{
auto s = pv::findByName(severityName, pv::severityValues);
if (s == pv::severityValues.end())
{
// Schema validation should be catching this.
log<level::ERR>("Invalid severity name used in message registry",
entry("SEVERITY=%s", severityName.c_str()));
throw std::runtime_error("Invalid severity used in message registry");
}
return std::get<pv::fieldValuePos>(*s);
}
std::vector<RegistrySeverity> getSeverities(const nlohmann::json& severity)
{
std::vector<RegistrySeverity> severities;
// The plain string value, like "unrecoverable"
if (severity.is_string())
{
RegistrySeverity s;
s.severity = getSeverity(severity.get<std::string>());
severities.push_back(std::move(s));
}
else
{
// An array, with an element like:
// {
// "SevValue": "unrecoverable",
// "System", "systemA"
// }
for (const auto& sev : severity)
{
RegistrySeverity s;
s.severity = getSeverity(sev["SevValue"].get<std::string>());
if (sev.contains("System"))
{
s.system = sev["System"].get<std::string>();
}
severities.push_back(std::move(s));
}
}
return severities;
}
uint16_t getActionFlags(const std::vector<std::string>& flags)
{
uint16_t actionFlags = 0;
// Make the bitmask based on the array of flag names
for (const auto& flag : flags)
{
auto s = pv::findByName(flag, pv::actionFlagsValues);
if (s == pv::actionFlagsValues.end())
{
// Schema validation should be catching this.
log<level::ERR>("Invalid action flag name used in message registry",
entry("FLAG=%s", flag.c_str()));
throw std::runtime_error(
"Invalid action flag used in message registry");
}
actionFlags |= std::get<pv::fieldValuePos>(*s);
}
return actionFlags;
}
uint8_t getEventType(const std::string& eventTypeName)
{
auto t = pv::findByName(eventTypeName, pv::eventTypeValues);
if (t == pv::eventTypeValues.end())
{
log<level::ERR>("Invalid event type used in message registry",
entry("EVENT_TYPE=%s", eventTypeName.c_str()));
throw std::runtime_error("Invalid event type used in message registry");
}
return std::get<pv::fieldValuePos>(*t);
}
uint8_t getEventScope(const std::string& eventScopeName)
{
auto s = pv::findByName(eventScopeName, pv::eventScopeValues);
if (s == pv::eventScopeValues.end())
{
log<level::ERR>("Invalid event scope used in registry",
entry("EVENT_SCOPE=%s", eventScopeName.c_str()));
throw std::runtime_error(
"Invalid event scope used in message registry");
}
return std::get<pv::fieldValuePos>(*s);
}
uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name)
{
std::string rc = src["ReasonCode"];
uint16_t reasonCode = strtoul(rc.c_str(), nullptr, 16);
if (reasonCode == 0)
{
log<phosphor::logging::level::ERR>(
"Invalid reason code in message registry",
entry("ERROR_NAME=%s", name.c_str()),
entry("REASON_CODE=%s", rc.c_str()));
throw std::runtime_error("Invalid reason code in message registry");
}
return reasonCode;
}
uint8_t getSRCType(const nlohmann::json& src, const std::string& name)
{
// Looks like: "22"
std::string srcType = src["Type"];
size_t type = strtoul(srcType.c_str(), nullptr, 16);
if ((type == 0) || (srcType.size() != 2)) // 1 hex byte
{
log<phosphor::logging::level::ERR>(
"Invalid SRC Type in message registry",
entry("ERROR_NAME=%s", name.c_str()),
entry("SRC_TYPE=%s", srcType.c_str()));
throw std::runtime_error("Invalid SRC Type in message registry");
}
return type;
}
bool getSRCDeconfigFlag(const nlohmann::json& src)
{
return src["DeconfigFlag"].get<bool>();
}
bool getSRCCheckstopFlag(const nlohmann::json& src)
{
return src["CheckstopFlag"].get<bool>();
}
std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
getSRCHexwordFields(const nlohmann::json& src, const std::string& name)
{
std::map<SRC::WordNum, SRC::AdditionalDataField> hexwordFields;
// Build the map of which AdditionalData fields to use for which SRC words
// Like:
// {
// "8":
// {
// "AdditionalDataPropSource": "TEST"
// }
//
// }
for (const auto& word : src["Words6To9"].items())
{
std::string num = word.key();
size_t wordNum = std::strtoul(num.c_str(), nullptr, 10);
if (wordNum == 0)
{
log<phosphor::logging::level::ERR>(
"Invalid SRC word number in message registry",
entry("ERROR_NAME=%s", name.c_str()),
entry("SRC_WORD_NUM=%s", num.c_str()));
throw std::runtime_error("Invalid SRC word in message registry");
}
auto attributes = word.value();
// Use an empty string for the description if it does not exist.
auto itr = attributes.find("Description");
std::string desc = (attributes.end() != itr) ? *itr : "";
std::tuple<std::string, std::string> adPropSourceDesc(
attributes["AdditionalDataPropSource"], desc);
hexwordFields[wordNum] = std::move(adPropSourceDesc);
}
if (!hexwordFields.empty())
{
return hexwordFields;
}
return std::nullopt;
}
std::optional<std::vector<SRC::WordNum>>
getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name)
{
std::vector<SRC::WordNum> symptomIDFields;
// Looks like:
// "SymptomIDFields": ["SRCWord3", "SRCWord6"],
for (const std::string field : src["SymptomIDFields"])
{
// Just need the last digit off the end, e.g. SRCWord6.
// The schema enforces the format of these.
auto srcWordNum = field.substr(field.size() - 1);
size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10);
if (num == 0)
{
log<phosphor::logging::level::ERR>(
"Invalid symptom ID field in message registry",
entry("ERROR_NAME=%s", name.c_str()),
entry("FIELD_NAME=%s", srcWordNum.c_str()));
throw std::runtime_error("Invalid symptom ID in message registry");
}
symptomIDFields.push_back(num);
}
if (!symptomIDFields.empty())
{
return symptomIDFields;
}
return std::nullopt;
}
uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
const nlohmann::json& pelEntry, const std::string& name)
{
uint16_t id = 0;
// If the ComponentID field is there, use that. Otherwise, if it's a
// 0xBD BMC error SRC, use the reasoncode.
if (pelEntry.contains("ComponentID"))
{
std::string componentID = pelEntry["ComponentID"];
id = strtoul(componentID.c_str(), nullptr, 16);
}
else
{
// On BMC error SRCs (BD), can just get the component ID from
// the first byte of the reason code.
if (srcType == static_cast<uint8_t>(SRCType::bmcError))
{
id = reasonCode & 0xFF00;
}
else
{
log<level::ERR>("Missing component ID field in message registry",
entry("ERROR_NAME=%s", name.c_str()));
throw std::runtime_error(
"Missing component ID field in message registry");
}
}
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::vector<std::string>& systemNames)
{
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 (std::find(systemNames.begin(), systemNames.end(),
calloutList["System"].get<std::string>()) !=
systemNames.end())
{
callouts = &calloutList["CalloutList"];
break;
}
}
else
{
// Any entry with no System key
callouts = &calloutList["CalloutList"];
}
}
if (!callouts)
{
std::string types;
std::for_each(systemNames.begin(), systemNames.end(),
[&types](const auto& t) { types += t + '|'; });
log<level::WARNING>(
"No matching system name entry or default system name entry "
" for PEL callout list",
entry("SYSTEMNAMES=%s", types.c_str()));
throw std::runtime_error{
"Could not find a CalloutList JSON for this error and system name"};
}
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";
callout.useInventoryLocCode = false;
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>();
}
if (json.contains("UseInventoryLocCode"))
{
callout.useInventoryLocCode = json["UseInventoryLocCode"].get<bool>();
}
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] systemNames - List of compatible system type names
*
* @return std::vector<RegistryCallout> - The callouts to use
*/
std::vector<RegistryCallout>
getCalloutsWithoutAD(const nlohmann::json& json,
const std::vector<std::string>& systemNames)
{
std::vector<RegistryCallout> calloutEntries;
// Find the CalloutList to use based on the system type
const auto& calloutList = findCalloutList(json, systemNames);
// 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] systemNames - List of compatible system type names
* @param[in] additionalData - The AdditionalData property
*
* @return std::vector<RegistryCallout> - The callouts to use
*/
std::vector<RegistryCallout>
getCalloutsUsingAD(const nlohmann::json& json,
const std::vector<std::string>& systemNames,
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())
{
// This can happen if not all possible values were in the
// message registry and that's fine. There may be a
// "CalloutsWhenNoADMatch" section that contains callouts
// to use in this case.
if (json.contains("CalloutsWhenNoADMatch"))
{
return getCalloutsWithoutAD(json["CalloutsWhenNoADMatch"],
systemNames);
}
return std::vector<RegistryCallout>{};
}
// Proceed to find the callouts possibly based on system type.
return getCalloutsWithoutAD((*it)["Callouts"], systemNames);
}
/**
* @brief Returns the journal capture information
*
* The JSON looks like:
* "JournalCapture": {
* "NumLines": 30
* }
*
* "JournalCapture":
* {
* "Sections": [
* {
* "SyslogID": "phosphor-log-manager",
* "NumLines": 20
* }
* ]
* }
*
* @param json - The journal capture JSON
* @return JournalCapture - The filled in variant
*/
JournalCapture getJournalCapture(const nlohmann::json& json)
{
JournalCapture capt;
// Primary key is either NumLines or Sections.
if (json.contains("NumLines"))
{
capt = json.at("NumLines").get<size_t>();
}
else if (json.contains("Sections"))
{
AppCaptureList captures;
for (const auto& capture : json.at("Sections"))
{
AppCapture ac;
ac.syslogID = capture.at("SyslogID").get<std::string>();
ac.numLines = capture.at("NumLines").get<size_t>();
captures.push_back(std::move(ac));
}
capt = captures;
}
else
{
log<level::ERR>("JournalCapture section not the right format");
throw std::runtime_error{"JournalCapture section not the right format"};
}
return capt;
}
} // namespace helper
std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
bool toCache)
{
std::optional<nlohmann::json> registryTmp;
auto& registryOpt = (_registry) ? _registry : registryTmp;
if (!registryOpt)
{
registryOpt = readRegistry(_registryFile);
if (!registryOpt)
{
return std::nullopt;
}
else if (toCache)
{
// Save message registry in memory for peltool
_registry = std::move(registryTmp);
}
}
auto& reg = (_registry) ? _registry : registryTmp;
const auto& registry = reg.value();
// Find an entry with this name in the PEL array.
auto e = std::find_if(registry["PELs"].begin(), registry["PELs"].end(),
[&name, &type](const nlohmann::json& j) {
return ((name == j.at("Name").get<std::string>() &&
type == LookupType::name) ||
(name == j.at("SRC").at("ReasonCode").get<std::string>() &&
type == LookupType::reasonCode));
});
if (e != registry["PELs"].end())
{
// Fill in the Entry structure from the JSON. Most, but not all, fields
// are optional.
try
{
Entry entry;
entry.name = (*e)["Name"];
if (e->contains("Subsystem"))
{
entry.subsystem = helper::getSubsystem((*e)["Subsystem"]);
}
if (e->contains("ActionFlags"))
{
entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]);
}
if (e->contains("MfgActionFlags"))
{
entry.mfgActionFlags =
helper::getActionFlags((*e)["MfgActionFlags"]);
}
if (e->contains("Severity"))
{
entry.severity = helper::getSeverities((*e)["Severity"]);
}
if (e->contains("MfgSeverity"))
{
entry.mfgSeverity = helper::getSeverities((*e)["MfgSeverity"]);
}
if (e->contains("EventType"))
{
entry.eventType = helper::getEventType((*e)["EventType"]);
}
if (e->contains("EventScope"))
{
entry.eventScope = helper::getEventScope((*e)["EventScope"]);
}
auto& src = (*e)["SRC"];
entry.src.reasonCode = helper::getSRCReasonCode(src, name);
if (src.contains("Type"))
{
entry.src.type = helper::getSRCType(src, name);
}
else
{
entry.src.type = static_cast<uint8_t>(SRCType::bmcError);
}
// Now that we know the SRC type and reason code,
// we can get the component ID.
entry.componentID = helper::getComponentID(
entry.src.type, entry.src.reasonCode, *e, name);
if (src.contains("Words6To9"))
{
entry.src.hexwordADFields = helper::getSRCHexwordFields(src,
name);
}
if (src.contains("SymptomIDFields"))
{
entry.src.symptomID = helper::getSRCSymptomIDFields(src, name);
}
if (src.contains("DeconfigFlag"))
{
entry.src.deconfigFlag = helper::getSRCDeconfigFlag(src);
}
if (src.contains("CheckstopFlag"))
{
entry.src.checkstopFlag = helper::getSRCCheckstopFlag(src);
}
auto& doc = (*e)["Documentation"];
entry.doc.message = doc["Message"];
entry.doc.description = doc["Description"];
if (doc.contains("MessageArgSources"))
{
entry.doc.messageArgSources = doc["MessageArgSources"];
}
// If there are callouts defined, save the JSON for later
if (_loadCallouts)
{
if (e->contains("Callouts"))
{
entry.callouts = (*e)["Callouts"];
}
else if (e->contains("CalloutsUsingAD"))
{
entry.callouts = (*e)["CalloutsUsingAD"];
}
}
if (e->contains("JournalCapture"))
{
entry.journalCapture =
helper::getJournalCapture((*e)["JournalCapture"]);
}
return entry;
}
catch (const std::exception& ex)
{
log<level::ERR>("Found invalid message registry field",
entry("ERROR=%s", ex.what()));
}
}
return std::nullopt;
}
std::optional<nlohmann::json>
Registry::readRegistry(const std::filesystem::path& registryFile)
{
// Look in /etc first in case someone put a test file there
fs::path debugFile{fs::path{debugFilePath} / registryFileName};
nlohmann::json registry;
std::ifstream file;
if (fs::exists(debugFile))
{
log<level::INFO>("Using debug PEL message registry");
file.open(debugFile);
}
else
{
file.open(registryFile);
}
try
{
registry = nlohmann::json::parse(file);
}
catch (const std::exception& e)
{
log<level::ERR>("Error parsing message registry JSON",
entry("JSON_ERROR=%s", e.what()));
return std::nullopt;
}
return registry;
}
std::vector<RegistryCallout>
Registry::getCallouts(const nlohmann::json& calloutJSON,
const std::vector<std::string>& systemNames,
const AdditionalData& additionalData)
{
// The JSON may either use an AdditionalData key
// as an index, or not.
if (helper::calloutUsesAdditionalData(calloutJSON))
{
return helper::getCalloutsUsingAD(calloutJSON, systemNames,
additionalData);
}
return helper::getCalloutsWithoutAD(calloutJSON, systemNames);
}
} // namespace message
} // namespace pels
} // namespace openpower