blob: dc10784cc5fd39efbc73fdb0efe740af0a03f9ac [file] [log] [blame]
#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();
}
absl::Status StateMonitorBMC::BuildFromConfig(const GpowerdConfig& config) {
if (config.has_ping_config()) {
RETURN_IF_ERROR(CreateIMCPingMonitor());
}
RETURN_IF_ERROR(InitializeBootnumLoader(config));
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;
return absl::InvalidArgumentError(
"failed to create bmcweb state monitor for: " + state_name);
}
} else {
LOG(ERROR) << "update function is not supported TODO b/372971177";
return absl::UnimplementedError("update function is not supported ");
}
} 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();
return absl::InvalidArgumentError(
absl::StrCat("state monitor type is not supported: ",
resource_to_monitor.state_gathering_info().info_case()));
}
}
return absl::OkStatus();
}
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(
"state changed from: %d (%s) to: %d (%s)", 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