| #include "bmc/state_monitor_bmc.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "one/node_entities_api.pb.h" |
| #include "bmc/address_lookup.h" |
| #include "bmc/daemon_context_bmc.h" |
| #include "bmc/redfish.h" |
| #include "daemon_context.h" |
| #include "safepower_agent.pb.h" |
| #include "safepower_agent_config.pb.h" |
| #include "state_updater.h" |
| #include "static_state.h" |
| #include "one/resolved_entities.pb.h" |
| #include "absl/functional/bind_front.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/time.h" |
| #include "nlohmann/json.hpp" |
| #include "nlohmann/json_fwd.hpp" |
| #include "bmc/status_macros.h" |
| |
| namespace safepower_agent { |
| |
| using safepower_agent_config::GpowerdConfig; |
| using safepower_agent_config::StateGatheringInfo; |
| |
| static void GetBootNumCallback(StaticStateLoader::GetStateCallback callback, |
| absl::StatusOr<nlohmann::json> js) { |
| if (!js.ok()) { |
| callback(js.status()); |
| return; |
| } |
| auto boot_info = js->find("BootInfo"); |
| if (boot_info == js->end()) { |
| callback(absl::NotFoundError( |
| absl::StrCat("BootInfo not found in ", js->dump()))); |
| return; |
| } |
| if (!boot_info->is_object()) { |
| callback(absl::InvalidArgumentError( |
| absl::StrCat("BootInfo is not an object in ", js->dump()))); |
| return; |
| } |
| auto boot_num = boot_info->find("BootCount"); |
| if (boot_num == boot_info->end()) { |
| callback(absl::NotFoundError( |
| absl::StrCat("BootCount not found in ", js->dump()))); |
| return; |
| } |
| auto* boot_num_int = boot_num->get_ptr<int64_t*>(); |
| if (boot_num_int == nullptr) { |
| callback(absl::InvalidArgumentError( |
| absl::StrCat("BootCount is not an int64_t in ", js->dump()))); |
| return; |
| } |
| absl::string_view entity_tag = |
| DaemonContext::Get().offline_node_entities().entity_tag(); |
| safepower_agent_proto::SystemState state; |
| (*state.mutable_node_state())[entity_tag] |
| .mutable_boot_state() |
| ->set_boot_counter(*boot_num_int); |
| callback(state); |
| } |
| |
| StateMonitorBMC::StateMonitorBMC( |
| std::shared_ptr<StateUpdater<safepower_agent_proto::SystemState>> reactor) |
| : reactor_(reactor) {} |
| |
| absl::Status StateMonitorBMC::InitializeBootnumLoader( |
| const GpowerdConfig& config) { |
| if (DaemonContext::Get().offline_node_entities().entity_tag().empty()) { |
| return absl::FailedPreconditionError( |
| "offline_node_entities is not initialized in DaemonContext: No " |
| "entity_tag found"); |
| } |
| if (bootnum_loader_.has_value()) { |
| return absl::InternalError("StateMonitorBMC already initialized"); |
| } |
| StaticStateLoaderConfig loader_config = { |
| .retry_interval = absl::Milliseconds( |
| config.redfish_boot_count_config().retry_interval_ms()), |
| .max_retries = -1, |
| }; |
| if (config.redfish_boot_count_config().has_timeout_sec()) { |
| loader_config.timeout = |
| absl::Seconds(config.redfish_boot_count_config().timeout_sec()); |
| } |
| bootnum_loader_.emplace( |
| "bootnum_loader", |
| [c = config.redfish_boot_count_config()]( |
| StaticStateLoader::GetStateCallback callback) { |
| LOG(INFO) << "Attempting to get bootnum from " << c.ip() << ":" |
| << c.port() << c.uri(); |
| Redfish::Get(c.uri(), |
| absl::bind_front(&GetBootNumCallback, std::move(callback)), |
| c.ip(), c.port()); |
| }, |
| reactor_, loader_config); |
| return absl::OkStatus(); |
| } |
| |
| void StateMonitorBMC::BuildFromConfig(const GpowerdConfig& config) { |
| // TODO: b/468358615 - Retry the ping, and bootnum loader creation if it |
| // fails. |
| if (config.has_ping_config()) { |
| absl::Status icmp_ping_status = CreateIMCPingMonitor(); |
| if (!icmp_ping_status.ok()) { |
| LOG(ERROR) << "Failed to create ICMP ping state monitor: " |
| << icmp_ping_status << " continuing"; |
| } |
| } |
| absl::Status bootnum_status = InitializeBootnumLoader(config); |
| if (!bootnum_status.ok()) { |
| LOG(ERROR) << "Failed to initialize bootnum loader: " << bootnum_status |
| << " continuing"; |
| } |
| |
| |
| for (auto& resource_to_monitor : config.state_monitor_config()) { |
| if (resource_to_monitor.state_gathering_info().info_case() == |
| StateGatheringInfo::kRedfish) { |
| std::string state_name(resource_to_monitor.state_name()); |
| std::string uri( |
| resource_to_monitor.state_gathering_info().redfish().uri()); |
| std::string json_key( |
| resource_to_monitor.state_gathering_info().redfish().json_key()); |
| std::string node_entity_tag( |
| resource_to_monitor.system_component().node_entity_tag()); |
| uint64_t collection_interval_ms = |
| resource_to_monitor.state_gathering_info().collection_interval_ms(); |
| auto prototype = resource_to_monitor.state_gathering_info().prototype(); |
| if (prototype.has_power_state() && |
| (prototype.power_state().has_state())) { |
| // create bmcweb state monitor for power state |
| if (!CreateRedfishUpdatePowerState(state_name, uri, json_key, |
| collection_interval_ms, |
| node_entity_tag) |
| .ok()) { |
| LOG(ERROR) << "failed to create bmcweb state monitor for: " |
| << state_name; |
| } |
| } else { |
| LOG(ERROR) << "update function is not supported TODO b/372971177"; |
| } |
| } else if (resource_to_monitor.state_gathering_info().info_case() == |
| StateGatheringInfo::kDbus) { |
| (void)CreateDbusStateMonitor("", "", "", "", "", 1000); |
| } else { |
| LOG(ERROR) << "state monitor type is not supported; " |
| << resource_to_monitor.state_gathering_info().info_case(); |
| } |
| } |
| } |
| |
| void StateMonitorBMC::InsertPingStateMonitor( |
| std::string node_entity_tag, absl::StatusOr<std::string> ip_address) { |
| if (!ip_address.ok()) { |
| LOG(ERROR) << "Failed to lookup address for node: " << node_entity_tag |
| << " with status: " << ip_address.status(); |
| return; |
| } |
| LOG(INFO) << "creating ping state monitor for: " << node_entity_tag; |
| |
| ping_state_monitors_.emplace_back( |
| std::move(node_entity_tag), *std::move(ip_address), |
| DaemonContext::Get().config().gpowerd_config().ping_config(), reactor_); |
| absl::Status status = ping_state_monitors_.back().Start(); |
| if (!status.ok()) { |
| LOG(DFATAL) << "Failed to start ping state monitor for " |
| << ping_state_monitors_.back().node_entity_tag() << ": " |
| << status; |
| } |
| } |
| |
| absl::Status StateMonitorBMC::CreateIMCPingMonitor() { |
| LOG(INFO) << "Creating ping state monitors"; |
| if (!DaemonContext::Get().offline_node_entities().has_resolved_config()) { |
| return absl::FailedPreconditionError( |
| "offline_node_entities is not initialized in DaemonContext: No " |
| "resolved_config found"); |
| } |
| for (const auto& [node_entity_tag, node_entity] : DaemonContext::Get() |
| .offline_node_entities() |
| .resolved_config() |
| .entities()) { |
| if (node_entity.node_type_info().os_type() == |
| net_model_unm::proto::NOST_NODEOS && |
| node_entity.node_type_info().is_control()) { |
| LookupAddress(node_entity_tag, |
| absl::bind_front(&StateMonitorBMC::InsertPingStateMonitor, |
| this, node_entity_tag)); |
| } |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status StateMonitorBMC::CreateRedfishUpdatePowerState( |
| std::string state_name, std::string uri, std::string json_key, |
| int collection_interval_ms, std::string node_entity_tag) { |
| LOG(INFO) << "creating bmcweb state monitor for:" << state_name; |
| LOG(INFO) << "uri: " << uri; |
| LOG(INFO) << "json_key: " << json_key; |
| LOG(INFO) << "collection_interval_ms: " << collection_interval_ms; |
| |
| return DaemonContextBMC::Get().scheduler().PeriodicCall( |
| [state_name, uri, json_key, this, node_entity_tag] { |
| Redfish::Get(uri, [state_name, uri, json_key, this, |
| node_entity_tag](absl::StatusOr<nlohmann::json> js) { |
| if (!js.ok()) { |
| LOG(ERROR) << "Failed to get " << uri |
| << " from BMC: " << js.status(); |
| return; |
| } |
| |
| auto js_value = js->find(json_key); |
| std::string state_str = js_value != js->end() ? *js_value : ""; |
| safepower_agent_proto::PowerStateSpecifier temp_power_state = |
| ConvertStatePowerStringToEnum(state_str); |
| const safepower_agent_proto::SystemState current_state = |
| this->reactor_->state(); |
| |
| safepower_agent_proto::PowerStateSpecifier last_ps = |
| safepower_agent_proto::POWER_STATE_UNSPECIFIED; |
| auto node_it = current_state.node_state().find(node_entity_tag); |
| if (node_it != current_state.node_state().end()) { |
| last_ps = node_it->second.power_state().state(); |
| } |
| if (last_ps == temp_power_state) return; |
| absl::string_view last_ps_str = PowerStateSpecifier_Name(last_ps); |
| absl::string_view temp_ps_str = |
| PowerStateSpecifier_Name(temp_power_state); |
| LOG(INFO) << absl::StrFormat( |
| "%s changed from: %d (%s) to: %d (%s)", state_name, |
| last_ps, last_ps_str, temp_power_state, temp_ps_str); |
| |
| // add new state power state to a new system proto |
| safepower_agent_proto::SystemState new_state; |
| safepower_agent_proto::PowerState ps; |
| ps.set_state(temp_power_state); |
| |
| safepower_agent_proto::NodeState ns; |
| *(ns.mutable_power_state()) = ps; |
| |
| new_state.mutable_node_state()->insert({node_entity_tag, ns}); |
| |
| reactor_->UpdateState(new_state); |
| }); |
| }, |
| absl::Milliseconds(collection_interval_ms), state_name); |
| } |
| |
| safepower_agent_proto::PowerStateSpecifier |
| StateMonitorBMC::ConvertStatePowerStringToEnum(absl::string_view state_str) { |
| if (state_str == "On") { |
| return safepower_agent_proto::POWER_STATE_ON; |
| } else if (state_str == "Off") { |
| return safepower_agent_proto::POWER_STATE_OFF; |
| } else if (state_str == "Paused") { // not in bmcweb |
| return safepower_agent_proto::POWER_STATE_PAUSED; |
| } else if (state_str == "PoweringOn") { |
| return safepower_agent_proto::POWER_STATE_POWERING_ON; |
| } else if (state_str == "PoweringOff") { |
| return safepower_agent_proto::POWER_STATE_POWERING_OFF; |
| } else { |
| LOG(ERROR) << "failed to map power state string to enum: " << state_str; |
| return safepower_agent_proto::POWER_STATE_UNSPECIFIED; |
| } |
| } |
| |
| absl::Status StateMonitorBMC::CreateDbusStateMonitor( |
| std::string state_name, std::string dbus_path, std::string dbus_method, |
| std::string dbus_interface, std::string dbus_value, |
| int collection_interval_ms) { |
| LOG(INFO) << "creating dbus state monitor for:" << state_name; |
| LOG(INFO) << "dbus_path: " << dbus_path; |
| LOG(INFO) << "dbus_method: " << dbus_method; |
| LOG(INFO) << "dbus_interface: " << dbus_interface; |
| LOG(INFO) << "dbus_value: " << dbus_value; |
| LOG(INFO) << "collection_interval_ms: " << collection_interval_ms; |
| return absl::UnimplementedError("CreateDbusStateMonitor is not implemented"); |
| } |
| |
| } // namespace safepower_agent |