blob: fda6799731531b629c6d879b36efe0a50fba8569 [file] [log] [blame]
#include "sse_plugin/event.h"
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/container/flat_hash_map.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
#include "proxy_config.pb.h"
#include "events.pb.h"
#include "vendor_events.pb.h"
#include "utils/status_macros.h"
namespace milotic {
namespace {
const int kVlogVerbosity = 1;
using ::platforms_vbmc::OtsEvent;
using ::platforms_vbmc::OtsUpdate;
// Finds MessageId from parsed_event, if exists, and parses it based Redfish
// format.
// <MessageRegistryPrefix>.<MajorVersion>.<MinorVersion>.<MessageKey>
absl::StatusOr<EventMessageId> FindEventMessageId(
const nlohmann::json& parsed_event) {
if (!parsed_event.is_object()) {
return absl::NotFoundError("event json is not object");
}
const auto message_id_it = parsed_event.find("MessageId");
if (message_id_it == parsed_event.end()) {
VLOG(kVlogVerbosity) << "event json does not contain MessageId"
<< parsed_event.dump();
return absl::NotFoundError("event json does not contain MessageId");
}
const auto* message_id = message_id_it.value().get_ptr<const std::string*>();
if (message_id == nullptr) {
return absl::InvalidArgumentError("MessageId is not string");
}
std::vector<std::string> message_id_parts = absl::StrSplit(*message_id, '.');
if (message_id_parts.size() != 4) {
return absl::InvalidArgumentError("MessageId not in correct format");
}
return EventMessageId{
.message_registry_prefix = std::move(message_id_parts[0]),
.major_version = std::move(message_id_parts[1]),
.minor_version = std::move(message_id_parts[2]),
.message_key = std::move(message_id_parts[3]),
};
}
// Finds ReasonId from OemPath whose value should be an array each of which
// contains `Reasons` which is an array each of which contains a `ReasonId` and
// `DateTime`, we select the `ReasonId` which has the max `DateTime`.
// Eg {"OemField": [{"Reasons": [{"ReasonId": xxx, "DateTime": yyy}]}]}
absl::StatusOr<int> FindReasonIdFromOemPath(
const nlohmann::json& parsed_event,
const nlohmann::json::json_pointer& oem_path) {
absl::TimeZone tz = absl::LocalTimeZone();
if (!parsed_event.is_object()) {
return absl::NotFoundError("event json is not object");
}
if (!parsed_event.contains(oem_path)) {
return absl::NotFoundError("event json does not Oem field");
}
const nlohmann::json& faults = parsed_event[oem_path];
if (!faults.is_array())
return absl::NotFoundError("faults in event json is null/not array");
int reason_id = -1;
for (auto fault : faults) {
if (!fault.is_object()) continue;
auto it = fault.find("Reasons");
if (it == fault.end()) continue;
const nlohmann::json& reasons = it.value();
if (!reasons.is_array()) continue;
absl::Time max_time;
for (auto reason : reasons) {
absl::Time reason_time;
std::string error_string;
std::string* time_value = reason["DateTime"].get_ptr<std::string*>();
std::int64_t* reason_value = reason["ReasonId"].get_ptr<std::int64_t*>();
if (time_value == nullptr || reason_value == nullptr) {
return absl::InvalidArgumentError(absl::StrFormat(
"Failed to parse time/reasonID from reason: %s", reason.dump()));
}
absl::ParseTime("%Y-%m-%dT%H:%M:%SZ", *time_value, tz, &reason_time,
&error_string);
if (!error_string.empty()) {
return absl::InvalidArgumentError(absl::StrFormat(
"Failed to parse time from value: %s", *time_value));
}
if (reason_time > max_time) {
max_time = reason_time;
reason_id = static_cast<int>(*reason_value);
}
}
}
if (reason_id == -1)
return absl::NotFoundError("No reason ID found in event");
return reason_id;
}
// Finds ReasonId from MessageArgs which is any array of strings and
// ReasonIdMessageArgsIndex which has 2 values MessageKey and MessageArgIndex,
// if MessageKey matches the event message key, then reason id is the
// present in MessageArgs at the MessageArgIndex position.
// Eg ReasonIdMessageArgsIndex: {ResourceIndicted, 1}
// {"MessageId":"H3Alert.1.1.ResourceIndicted", "MessageArgs":["0",
// "1349"]}
// In this case 1349 is at index 1 which should be converted to int.
absl::StatusOr<int> FindReasonIdFromMessageArg(
const nlohmann::json& parsed_event, absl::string_view message_key,
const milotic_grpc_proxy::EventReasonIdParse::ReasonIdMessageArgsIndex&
message_args_config) {
if (message_key != message_args_config.message_key()) {
return absl::NotFoundError("MessageKey mismatch");
}
auto it = parsed_event.find("MessageArgs");
if (it == parsed_event.end()) {
return absl::NotFoundError("MessageArgs not found");
}
const nlohmann::json& message_args = it.value();
if (!message_args.is_array())
return absl::NotFoundError("message_args in event json is null/not array");
if (message_args_config.message_arg_index() >=
static_cast<int>(message_args.size()))
return absl::NotFoundError("MessageArgIndex out of bounds");
const auto* reason_id_from_message_arg =
message_args.at(message_args_config.message_arg_index())
.get_ptr<const std::string*>();
// ReasonID is present in string which has to be converted to int.
int reason_id = -1;
if (reason_id_from_message_arg != nullptr &&
absl::SimpleAtoi(*reason_id_from_message_arg, &reason_id)) {
VLOG(kVlogVerbosity) << "reason id " << reason_id
<< " found in MessageArgs";
return reason_id;
}
return absl::NotFoundError("No reason ID found in MessageArgs");
}
absl::StatusOr<EventSeverity> FindReasonSeverity(
const absl::flat_hash_map<int, platforms_vbmc::VendorEvent>& registry,
int reason_id) {
auto it = registry.find(reason_id);
if (it != registry.end()) {
switch (it->second.severity()) {
case platforms_vbmc::OtsEvent::SEVERITY_OK:
return EventSeverity::kOk;
case platforms_vbmc::OtsEvent::SEVERITY_LOG:
return EventSeverity::kLog;
case platforms_vbmc::OtsEvent::SEVERITY_WARNING:
return EventSeverity::kWarning;
case platforms_vbmc::OtsEvent::SEVERITY_CRITICAL:
return EventSeverity::kCritical;
default:
return absl::InvalidArgumentError("Invalid event severity");
}
}
return absl::NotFoundError(
absl::StrFormat("Reason ID %d not found in registry", reason_id));
}
absl::string_view GetValueOrDefault(const nlohmann::json& json,
absl::string_view key) {
if (!json.is_object()) return "";
auto it = json.find(key);
if (it == json.end()) return "";
const auto* value = it.value().get_ptr<const std::string*>();
if (value == nullptr) return "";
return *value;
}
absl::StatusOr<Event> CreateEvent(const EventParseConfig& parse_config,
nlohmann::json parsed_event) {
// Validate and parse event ID.
int64_t event_id;
int reason_id = kInvalidReasonId;
auto event_id_it = parsed_event.find("EventId");
if (event_id_it == parsed_event.end()) {
return absl::InvalidArgumentError("EventId not found");
}
if (!absl::SimpleAtoi(absl::StrReplaceAll(event_id_it->dump(), {{"\"", ""}}),
&event_id)) {
return absl::InvalidArgumentError("EventId is not a number");
}
VLOG(kVlogVerbosity) << "event id " << event_id;
if (GetValueOrDefault(parsed_event, "MessageSeverity") == "OK") {
// No parsing required for OK events
return Event(parsed_event.dump(), event_id, EventSeverity::kOk);
}
absl::string_view message_severity =
GetValueOrDefault(parsed_event, "MessageSeverity");
// Log Non OK events
// EventId, MessageSeverity, MessageId, Message, EventTimestamp
LOG(INFO) << "Creating event with EventId: " << event_id
<< ", MessageSeverity: " << message_severity
<< ", MessageId: " << GetValueOrDefault(parsed_event, "MessageId")
<< ", Message: " << GetValueOrDefault(parsed_event, "Message")
<< ", Timestamp: "
<< GetValueOrDefault(parsed_event, "EventTimestamp");
absl::StatusOr<EventMessageId> message_id = FindEventMessageId(parsed_event);
// TODO: b/323233763 - Break into smaller functions
// Check if MessageKey is ignored.
if (parse_config.event_parse.ignore_event_sev_message_keys_size() > 0) {
if (!message_id.ok()) return message_id.status();
VLOG(kVlogVerbosity) << "Searching MessageKey in ignore list: "
<< message_id->message_key;
if (absl::c_linear_search(
parse_config.event_parse.ignore_event_sev_message_keys(),
message_id->message_key)) {
LOG(INFO) << "EventId: " << event_id
<< " created with OK Severity, message key "
<< message_id->message_key << " in ignore list";
return Event(parsed_event.dump(), event_id, EventSeverity::kOk);
}
}
// Check if MessageRegistryPrefix is ignored.
if (parse_config.event_parse.ignore_event_sev_message_prefix_size() > 0) {
if (!message_id.ok()) return message_id.status();
VLOG(kVlogVerbosity) << "Searching MessageRegistryPrefix in ignore list: "
<< message_id->message_registry_prefix;
if (absl::c_linear_search(
parse_config.event_parse.ignore_event_sev_message_prefix(),
message_id->message_registry_prefix)) {
LOG(INFO) << "EventId: " << event_id
<< " created with OK Severity, message prefix "
<< message_id->message_registry_prefix << " in ignore list";
return Event(parsed_event.dump(), event_id, EventSeverity::kOk);
}
}
// Check if ReasonId parsing is present.
if (parse_config.event_parse.reason_id_parse_size() > 0) {
for (const auto& reason_id_parse :
parse_config.event_parse.reason_id_parse()) {
// Check ReasonId in Oem Field.
if (reason_id_parse.has_oem_field_path()) {
VLOG(kVlogVerbosity) << "Checking ReasonId in Oem field";
nlohmann::json::json_pointer oem_path(reason_id_parse.oem_field_path());
if (absl::StatusOr<int> from_oem_path =
FindReasonIdFromOemPath(parsed_event, oem_path);
from_oem_path.ok()) {
reason_id = *from_oem_path;
VLOG(kVlogVerbosity)
<< "reason id " << reason_id << " found in Oem field";
break;
}
}
// Check ReasonId in MessageArgs.
if (reason_id_parse.has_message_args_index()) {
if (!message_id.ok()) return message_id.status();
if (absl::StatusOr<int> reason_id_from_message_arg =
FindReasonIdFromMessageArg(
parsed_event, message_id->message_key,
reason_id_parse.message_args_index());
reason_id_from_message_arg.ok()) {
reason_id = *reason_id_from_message_arg;
VLOG(kVlogVerbosity)
<< "reason id " << reason_id << " found in MessageArgs";
break;
}
}
// Check ReasonId in Message string using regex.
if (reason_id_parse.has_message_string_regex()) {
// TODO: b/323233763 - Add support for Message string regex.
}
}
}
// If ReasonId is found, get severity from registry
if (!parse_config.registry.empty() && reason_id != kInvalidReasonId) {
if (absl::StatusOr<EventSeverity> severity =
FindReasonSeverity(parse_config.registry, reason_id);
severity.ok()) {
LOG(INFO) << "EventId: " << event_id << " created with "
<< EventSeverityToString(*severity)
<< " Severity, reasonId: " << reason_id;
return Event(parsed_event.dump(), event_id, *severity, reason_id);
}
}
// If ignore_event_sev_wo_reason_id is set and we can't find ReasonId
if (reason_id == kInvalidReasonId &&
parse_config.event_parse.ignore_event_sev_wo_reason_id()) {
LOG(INFO) << "EventId: " << event_id
<< " created with OK Severity, missing reasonId";
return Event(parsed_event.dump(), event_id, EventSeverity::kOk);
}
// Finally use MessageSeverity for event severity.
ASSIGN_OR_RETURN(EventSeverity severity,
StringToEventSeverity(message_severity));
LOG(INFO) << "EventId: " << event_id << " created with "
<< EventSeverityToString(severity)
<< " Severity, using MessageSeverity";
return Event(parsed_event.dump(), event_id, severity, reason_id);
}
} // namespace
std::string EventSeverityToString(EventSeverity severity) {
switch (severity) {
case EventSeverity::kOk:
return "OK";
case EventSeverity::kLog:
return "Log";
case EventSeverity::kWarning:
return "Warning";
case EventSeverity::kCritical:
return "Critical";
default:
return "";
}
}
absl::StatusOr<EventSeverity> StringToEventSeverity(
absl::string_view severity) {
if (severity == "OK") {
return EventSeverity::kOk;
}
if (severity == "Warning") {
return EventSeverity::kWarning;
}
if (severity == "Critical") {
return EventSeverity::kCritical;
}
return absl::InvalidArgumentError("Invalid message severity");
}
OtsUpdate Event::ToProto() const {
OtsUpdate ots_update;
OtsEvent event_proto;
switch (severity_) {
case EventSeverity::kOk:
event_proto.set_severity(OtsEvent::SEVERITY_OK);
break;
case EventSeverity::kLog:
event_proto.set_severity(OtsEvent::SEVERITY_LOG);
break;
case EventSeverity::kWarning:
event_proto.set_severity(OtsEvent::SEVERITY_WARNING);
break;
case EventSeverity::kCritical:
event_proto.set_severity(OtsEvent::SEVERITY_CRITICAL);
break;
}
event_proto.mutable_event_json()->assign(event_json_);
*ots_update.mutable_event() = event_proto;
return ots_update;
}
absl::StatusOr<std::vector<Event>> Event::ParseRedfishEvent(
const EventParseConfig& parse_config, absl::string_view event_json) {
nlohmann::json parsed = nlohmann::json::parse(event_json, /*cb=*/nullptr,
/*allow_exceptions=*/false);
if (parsed.is_discarded()) {
return absl::InvalidArgumentError("Failed to parse json");
}
// parsed json should be similar to
// {"Id": 123, "Events": [{"EventId": 123, ...}]}.
if (!parsed.is_object()) {
return absl::InvalidArgumentError(
"Invalid event json, json is not an object");
}
// Validated `Id` is present.
auto id_it = parsed.find("Id");
if (id_it == parsed.end()) {
return absl::InvalidArgumentError("Invalid event json, Id is not present");
}
std::string* id_ptr = id_it->get_ptr<std::string*>();
if (id_ptr == nullptr) {
return absl::InvalidArgumentError("Id is not a string");
}
// Validate `Events` is present.
auto events_it = parsed.find("Events");
if (events_it == parsed.end()) {
return absl::InvalidArgumentError(
"Invalid event json, Events is not present");
}
// Validate `Events` json is an array and contains at least one event.
if (!events_it->is_array() || events_it->size() != 1) {
return absl::InvalidArgumentError(
"Invalid event json, Events should be an array with single element");
}
if (!events_it->at(0).is_object()) {
return absl::InvalidArgumentError(
absl::StrFormat("Invalid event json, Events[0] is not an object"));
}
if (!events_it->at(0).contains("EventId")) {
return absl::InvalidArgumentError(absl::StrFormat(
"Invalid event json, EventId is not present in Events[0]"));
}
std::string* event_id_ptr =
events_it->at(0)["EventId"].get_ptr<std::string*>();
if (event_id_ptr == nullptr) {
return absl::InvalidArgumentError("EventId is not a string");
}
if (*id_ptr != *event_id_ptr) {
return absl::InvalidArgumentError(
absl::StrFormat("EventId mismatch, Id: %s, Events[0][EventId]: %s",
*id_ptr, *event_id_ptr));
}
absl::StatusOr<Event> event = CreateEvent(parse_config, events_it->at(0));
if (!event.ok()) {
return event.status();
}
return std::vector<Event>{std::move(*event)};
}
absl::StatusOr<std::vector<Event>> Event::ParseReplayEvents(
const EventParseConfig& parse_config, absl::string_view event_json) {
std::vector<Event> events;
if (event_json.empty()) {
return events;
}
nlohmann::json parsed = nlohmann::json::parse(event_json, /*cb=*/nullptr,
/*allow_exceptions=*/false);
if (parsed.is_discarded()) {
return absl::InvalidArgumentError("Failed to parse json");
}
if (!parsed.is_array()) {
return absl::InvalidArgumentError("Invalid json");
}
LOG(INFO) << "Got " << parsed.size() << " events";
events.reserve(parsed.size());
for (const auto& parsed_event : parsed) {
if (absl::StatusOr<Event> event = CreateEvent(parse_config, parsed_event);
event.ok()) {
events.push_back(*event);
} else {
LOG(ERROR) << "Failed to create event: " << event.status();
return event.status();
}
}
LOG(INFO) << "Parsed " << events.size() << " events";
return events;
}
} // namespace milotic