blob: 07237afff22d3755be9634a08de90b2d2bf77966 [file]
#include "tlbmc/true_hitless/true_hitless_metrics_parser.h"
#include <algorithm>
#include <array>
#include <filesystem> // NOLINT
#include <fstream>
#include <string>
#include <utility>
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "g3/macros.h"
#include <nlohmann/json.hpp>
#include "json_utils.h"
#include "tlbmc/time/time.h"
#include "tlbmc/true_hitless_metrics.pb.h"
namespace milotic_tlbmc {
static constexpr std::array<absl::string_view, 3> kIntermediateStateKeys = {
"true_hitless_initialize_service", "emmc_available_postprocessing_service",
"emmc_unavailable_postprocessing_service"};
using ::milotic::authz::GetValueAsArray;
using ::milotic::authz::GetValueAsBool;
using ::milotic::authz::GetValueAsInt;
using ::milotic::authz::GetValueAsString;
template <class TypeNameFirst, class TypeNameSecond>
struct TypeComparator {
// Compared by the first element which is the type name.
bool operator()(const std::pair<TypeNameFirst, TypeNameSecond>& lhs,
const std::pair<TypeNameFirst, TypeNameSecond>& rhs) const {
return lhs.first < rhs.first;
}
};
constexpr std::array<std::pair<std::string_view, ServiceStatus>, 5>
kServiceStatusMap = {
// go/keep-sorted start
std::pair<std::string_view, ServiceStatus>{"active",
SERVICE_STATUS_ACTIVE},
std::pair<std::string_view, ServiceStatus>{"failed",
SERVICE_STATUS_FAILED},
std::pair<std::string_view, ServiceStatus>{"inactive",
SERVICE_STATUS_INACTIVE},
std::pair<std::string_view, ServiceStatus>{"not-found",
SERVICE_STATUS_NOT_FOUND},
std::pair<std::string_view, ServiceStatus>{"unknown",
SERVICE_STATUS_UNKNOWN}
// go/keep-sorted end
};
absl::StatusOr<ServiceStatus>
TrueHitlessMetricsParser::ConvertStringToServiceStatus(
absl::string_view status) {
const auto* it =
std::lower_bound(kServiceStatusMap.begin(), kServiceStatusMap.end(),
std::pair<std::string_view, ServiceStatus>{
status, SERVICE_STATUS_UNKNOWN},
TypeComparator<std::string_view, ServiceStatus>());
if (it == kServiceStatusMap.end() || it->first != status) {
return absl::InvalidArgumentError(
absl::StrCat("ServiceStatus has unknown value: ", status));
}
return it->second;
}
absl::StatusOr<ActivationState>
TrueHitlessMetricsParser::ParseActivationStateFromJson(
const nlohmann::json& json) {
ActivationState activation_state;
const bool* activated = GetValueAsBool(json, "activated");
if (activated == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"ActivationState activated field is not found: ", json.dump()));
}
activation_state.set_activated(*activated);
const bool* image_checksum_valid =
GetValueAsBool(json, "image_checksum_valid");
if (image_checksum_valid != nullptr) {
activation_state.set_image_checksum_valid(*image_checksum_valid);
}
return activation_state;
}
absl::StatusOr<EmmcAvailabilityTargets>
TrueHitlessMetricsParser::ParseEmmcAvailabilityTargetsFromJson(
const nlohmann::json& emmc_status_json) {
EmmcAvailabilityTargets emmc_availability_targets;
if (!emmc_status_json.contains("emmc_available_target") ||
!emmc_status_json.contains("emmc_unavailable_target")) {
return absl::InvalidArgumentError(
absl::StrCat("EmmcStatus is missing emmc_available_target or "
"emmc_unavailable_target: ",
emmc_status_json.dump()));
}
const std::string* available_status =
GetValueAsString(emmc_status_json["emmc_available_target"], "status");
const std::string* unavailable_status =
GetValueAsString(emmc_status_json["emmc_unavailable_target"], "status");
if (available_status == nullptr || unavailable_status == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"EmmcStatus status field is not found: ", emmc_status_json.dump()));
}
ECCLESIA_ASSIGN_OR_RETURN(ServiceStatus available_service_status,
ConvertStringToServiceStatus(*available_status));
ECCLESIA_ASSIGN_OR_RETURN(ServiceStatus unavailable_service_status,
ConvertStringToServiceStatus(*unavailable_status));
emmc_availability_targets.mutable_available_target_status()->set_status(
available_service_status);
emmc_availability_targets.mutable_unavailable_target_status()->set_status(
unavailable_service_status);
const auto* available_time_to_active_seconds = GetValueAsInt(
emmc_status_json["emmc_available_target"], "time_to_active_seconds");
if (available_time_to_active_seconds != nullptr) {
emmc_availability_targets.mutable_available_target_status()
->set_time_to_active_seconds(*available_time_to_active_seconds);
}
const auto* unavailable_time_to_active_seconds = GetValueAsInt(
emmc_status_json["emmc_unavailable_target"], "time_to_active_seconds");
if (unavailable_time_to_active_seconds != nullptr) {
emmc_availability_targets.mutable_unavailable_target_status()
->set_time_to_active_seconds(*unavailable_time_to_active_seconds);
}
return emmc_availability_targets;
}
absl::StatusOr<IntermediateState>
TrueHitlessMetricsParser::ParseIntermediateStateFromJson(
const nlohmann::json& json) {
IntermediateState intermediate_state;
for (absl::string_view key : kIntermediateStateKeys) {
IntermediateServiceStatus* intermediate_service_status =
intermediate_state.add_intermediate_service_statuses();
intermediate_service_status->set_name(key);
if (!json.contains(key)) {
return absl::InvalidArgumentError(absl::StrCat(
"IntermediateState key is not found ", key, ": ", json.dump()));
}
const std::string* status = GetValueAsString(json[key], "status");
if (status == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("IntermediateState status field is not found for ", key,
": ", json[key].dump()));
}
ECCLESIA_ASSIGN_OR_RETURN(ServiceStatus service_status,
ConvertStringToServiceStatus(*status));
intermediate_service_status->set_status(service_status);
const auto* last_executed_timestamp_seconds =
GetValueAsInt(json[key], "last_execution_time");
if (last_executed_timestamp_seconds != nullptr) {
google::protobuf::Timestamp last_executed_timestamp =
MilliSecondsToTimestamp(*last_executed_timestamp_seconds * 1000);
*intermediate_service_status->mutable_last_execution_time() =
last_executed_timestamp;
}
const bool* postprocessing_complete =
GetValueAsBool(json[key], "postprocessing_complete");
if (postprocessing_complete != nullptr) {
intermediate_service_status->set_postprocessing_complete(
*postprocessing_complete);
}
}
return intermediate_state;
}
absl::StatusOr<ManagedServices>
TrueHitlessMetricsParser::ParseManagedServicesFromJson(
const nlohmann::json::array_t& managed_services_json) {
ManagedServices managed_services;
for (const nlohmann::json& managed_service_json : managed_services_json) {
ManagedServiceState* managed_service_state =
managed_services.add_service_states();
const std::string* name =
GetValueAsString(managed_service_json, "service_name");
if (name == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("ManagedServiceState name field is not found: ",
managed_service_json.dump()));
}
managed_service_state->set_name(*name);
const std::string* status =
GetValueAsString(managed_service_json, "systemd_status");
if (status == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("ManagedServiceState status field is not found: ",
managed_service_json.dump()));
}
ECCLESIA_ASSIGN_OR_RETURN(ServiceStatus service_status,
ConvertStringToServiceStatus(*status));
managed_service_state->set_status(service_status);
const bool* block_override_applied =
GetValueAsBool(managed_service_json, "block_override_applied");
if (block_override_applied != nullptr) {
managed_service_state->set_block_override_applied(
*block_override_applied);
}
const bool* emmc_override_applied =
GetValueAsBool(managed_service_json, "emmc_override_applied");
if (emmc_override_applied != nullptr) {
managed_service_state->set_emmc_override_applied(*emmc_override_applied);
}
const bool* binary_running_on_emmc =
GetValueAsBool(managed_service_json, "binary_running_on_emmc");
if (binary_running_on_emmc != nullptr) {
managed_service_state->set_binary_running_on_emmc(
*binary_running_on_emmc);
}
const auto* restart_count =
GetValueAsInt(managed_service_json, "restart_count");
if (restart_count != nullptr) {
managed_service_state->set_restart_count(*restart_count);
}
const auto* coredump_count =
GetValueAsInt(managed_service_json, "coredump_count");
if (coredump_count != nullptr) {
managed_service_state->set_coredump_count(*coredump_count);
}
const auto* restart_time_ms =
GetValueAsInt(managed_service_json, "restart_time");
if (restart_time_ms != nullptr) {
managed_service_state->set_restart_time_ms(*restart_time_ms);
}
}
return managed_services;
}
absl::StatusOr<VersionState>
TrueHitlessMetricsParser::ParseVersionStateFromJson(
const nlohmann::json& json) {
VersionState version_state;
const std::string* true_hitless_version =
GetValueAsString(json, "true_hitless_version");
if (true_hitless_version == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"VersionState true_hitless_version field is not found: ", json.dump()));
}
version_state.set_true_hitless_version(*true_hitless_version);
const std::string* gbmc_version = GetValueAsString(json, "gbmc_version");
if (gbmc_version == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"VersionState gbmc_version field is not found: ", json.dump()));
}
version_state.set_gbmc_version(*gbmc_version);
const bool* compatible = GetValueAsBool(json, "compatible");
if (compatible != nullptr) {
version_state.set_compatible(*compatible);
}
return version_state;
}
TrueHitlessMetricsParser::TrueHitlessMetricsParser(
absl::string_view true_hitless_metrics_path)
: true_hitless_metrics_path_(true_hitless_metrics_path) {}
TrueHitlessMetrics TrueHitlessMetricsParser::ParseTrueHitlessMetricsFromJson()
const {
TrueHitlessMetrics true_hitless_metrics;
if (!std::filesystem::exists(true_hitless_metrics_path_)) {
true_hitless_metrics.set_error_message(
absl::StrCat("True Hitless Metrics file does not exist: ",
true_hitless_metrics_path_));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
std::ifstream json_file(true_hitless_metrics_path_);
if (!json_file.is_open()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Failed to open True Hitless Metrics file: ",
true_hitless_metrics_path_));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
nlohmann::json json =
nlohmann::json::parse(json_file, nullptr, /*allow_exceptions=*/false,
/*ignore_comments=*/true);
if (json.is_discarded()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Failed to parse True Hitless Status from JSON at ",
true_hitless_metrics_path_, ", JSON is malformed."));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
// Parse activation state.
auto activation_state_it = json.find("true_hitless_activation");
if (activation_state_it == json.end()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Incomplete True Hitless Status JSON: "
"true_hitless_activation is not found: ",
json.dump()));
return true_hitless_metrics;
}
absl::StatusOr<ActivationState> activation_state =
ParseActivationStateFromJson(*activation_state_it);
if (!activation_state.ok()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Failed to parse activation state from JSON: ",
activation_state.status()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
*true_hitless_metrics.mutable_activation_state() = *activation_state;
// Parse emmc availability targets.
auto emmc_availability_targets_it = json.find("emmc_status");
if (emmc_availability_targets_it == json.end()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Incomplete True Hitless Status JSON: "
"emmc_status is not found: ",
json.dump()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
absl::StatusOr<EmmcAvailabilityTargets> emmc_availability_targets =
ParseEmmcAvailabilityTargetsFromJson(*emmc_availability_targets_it);
if (!emmc_availability_targets.ok()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Failed to parse emmc availability targets from JSON: ",
emmc_availability_targets.status()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
*true_hitless_metrics.mutable_emmc_availability_targets() =
*emmc_availability_targets;
// Parse intermediate state.
auto intermediate_state_it = json.find("intermediate_state");
if (intermediate_state_it == json.end()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Incomplete True Hitless Status JSON: intermediate_state "
"is not found: ",
json.dump()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
absl::StatusOr<IntermediateState> intermediate_state =
ParseIntermediateStateFromJson(*intermediate_state_it);
if (!intermediate_state.ok()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Failed to parse intermediate state from JSON: ",
intermediate_state.status()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
*true_hitless_metrics.mutable_intermediate_state() = *intermediate_state;
// Parse managed services.
auto service_status_it = json.find("service_status");
if (service_status_it == json.end()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Incomplete True Hitless Status JSON: service_status "
"is not found: ",
json.dump()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
const nlohmann::json::array_t* managed_services_array =
GetValueAsArray(json["service_status"], "true_hitless_managed_services");
if (managed_services_array != nullptr) {
absl::StatusOr<ManagedServices> managed_services =
ParseManagedServicesFromJson(*managed_services_array);
if (!managed_services.ok()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Failed to parse managed services from JSON: ",
managed_services.status()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
*true_hitless_metrics.mutable_managed_services() = *managed_services;
}
// Parse version state.
auto version_state_it = json.find("versions");
if (version_state_it == json.end()) {
true_hitless_metrics.set_error_message(
absl::StrCat("Incomplete True Hitless Status JSON: versions is not "
"found: ",
json.dump()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
absl::StatusOr<VersionState> version_state =
ParseVersionStateFromJson(*version_state_it);
if (!version_state.ok()) {
true_hitless_metrics.set_error_message(absl::StrCat(
"Failed to parse version state from JSON: ", version_state.status()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
*true_hitless_metrics.mutable_version_state() = *version_state;
// Parse the timestamp.
auto* timestamp_it = GetValueAsInt(json, "last_update_time");
if (timestamp_it == nullptr) {
true_hitless_metrics.set_error_message(
absl::StrCat("Incomplete True Hitless Status JSON: last_update_time "
"is not found: ",
json.dump()));
LOG(ERROR) << true_hitless_metrics.error_message();
return true_hitless_metrics;
}
google::protobuf::Timestamp last_update_timestamp =
MilliSecondsToTimestamp(*timestamp_it * 1000);
*true_hitless_metrics.mutable_last_updated_timestamp() =
last_update_timestamp;
return true_hitless_metrics;
}
} // namespace milotic_tlbmc