| #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 |