blob: 516ac3548875b87ac32538e88f906d207dd91a23 [file] [log] [blame] [edit]
#include "bmc/register_actions_bmc.h"
#include <sys/stat.h>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "action_context.h"
#include "bmc/redfish.h"
#include "daemon_context.h"
#include "disruption_manager.h"
#include "safepower_agent.pb.h"
#include "safepower_agent_config.pb.h"
#include "absl/functional/any_invocable.h"
#include "absl/functional/bind_front.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
// NOLINTBEGIN(readability/boost)
#include "absl/time/time.h"
#include "boost/asio/ip/tcp.hpp"
#include "boost/beast/core.hpp"
#include "boost/beast/http.hpp"
// NOLINTEND(readability/boost)
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
namespace safepower_agent {
using safepower_agent_config::SafePowerAgentConfig;
// runs after the action is called, parses the response to a status
inline absl::Status GenericRedfishParser(absl::StatusOr<nlohmann::json> js) {
if (!js.ok()) {
LOG(ERROR) << "BMC connection failed: " << js.status();
return js.status();
}
auto extended_info = (*js)["@Message.ExtendedInfo"];
if (extended_info.size() != 1) {
LOG(ERROR) << "redfish response has an abnormal number of message : "
<< *js;
}
auto info = extended_info[0];
auto message_id = info.find("MessageId");
if (message_id == info.end()) {
LOG(ERROR) << "Failed to parse bmc's response to JSON: " << *js;
return absl::InvalidArgumentError("Failed to parse bmc's response to JSON");
}
std::string status = *message_id;
std::vector<std::string> v = absl::StrSplit(status, '.');
if (v.size() >= 4 && v[4] == "Success") {
return absl::OkStatus();
} else {
LOG(ERROR) << "Failed to power off :" << *message_id;
return absl::NotFoundError("Failed to power off :" + message_id->dump());
}
}
struct GenericRedfishPowerOperationConfig {
std::string uri;
nlohmann::json body;
std::string ip;
uint16_t port;
absl::Duration call_timeout = absl::Seconds(10);
};
// runs when then action is called, perform the action
inline void GenericRedfishPowerOperation(
const GenericRedfishPowerOperationConfig& config,
const safepower_agent_proto::Action&,
absl::AnyInvocable<void(absl::Status) &&> cb) {
LOG(INFO) << "Redfish action " << config.ip << ":" << config.port << " "
<< config.uri << " " << config.body;
Redfish::Post(
config.uri,
[callback =
std::move(cb)](absl::StatusOr<nlohmann::json> result) mutable {
std::move(callback)(GenericRedfishParser(std::move(result)));
},
config.body, config.ip, config.port);
}
// Callable that runs after all disruption callbacks are completed, then waits
// for the disruption to happen or time out. This is a callable because the
// callback handle needs to be owned by the callback.
class DisruptiveRedfishPowerOperation {
public:
explicit DisruptiveRedfishPowerOperation(
GenericRedfishPowerOperationConfig config, absl::Duration timeout)
: config_(std::move(config)), timeout_(timeout) {}
DisruptiveRedfishPowerOperation(DisruptiveRedfishPowerOperation&&) = default;
DisruptiveRedfishPowerOperation& operator=(
DisruptiveRedfishPowerOperation&&) = default;
void operator()(const safepower_agent_proto::Action& /*action*/,
absl::AnyInvocable<void(absl::Status) &&> cb) {
callback_ = std::move(cb);
auto& disruption_manager = DaemonContext::Get().disruption_manager();
// Run after all the other disruption callbacks are completed.
run_handle_ = disruption_manager.OnDisruptionStart(
absl::bind_front(&DisruptiveRedfishPowerOperation::Run, this));
absl::Status status = disruption_manager.ExpectDisruptionIn(timeout_);
if (!status.ok()) {
LOG(DFATAL) << "Failed to expect disruption: " << status;
run_handle_ = {}; // Cancel the disruption start callback.
std::move(callback_)(status);
return;
}
}
private:
void Run() {
GenericRedfishPowerOperation(config_, {}, [this](absl::Status status) {
if (!status.ok()) {
std::move(callback_)(status);
return;
}
// TODO: b/416304657 - Monitor progress of power
// operations and end early if it won't work. For now, we just let the
// disruption happen or expire without calling the callback.
});
}
GenericRedfishPowerOperationConfig config_;
absl::Duration timeout_;
absl::AnyInvocable<void(absl::Status) &&> callback_;
DisruptionManager::CallbackHandle run_handle_;
};
absl::Status RegisterNoOpAction(ActionContextManager& action_context_manager) {
return action_context_manager.RegisterAction(
safepower_agent_proto::Action::default_instance(),
[](const safepower_agent_proto::Action& /*action*/,
absl::AnyInvocable<void(absl::Status) &&> cb) {
LOG(WARNING) << "Running No-op action";
std::move(cb)(absl::OkStatus());
});
}
// runs at startup to register all actions from the config
absl::Status RegisterActionFromConfig(
ActionContextManager* action_context_manager,
const SafePowerAgentConfig& config) {
if (absl::Status status = RegisterNoOpAction(*action_context_manager);
!status.ok()) {
LOG(ERROR) << "Unable to register no-op action :" << status;
}
for (const auto& action_config : config.action_configs()) {
safepower_agent_proto::Action action_to_register = action_config.action();
// The config will map actions to redfish (uris and body)
// or dbus (path, method, interface, value)
GenericRedfishPowerOperationConfig redfish_config;
redfish_config.body = nlohmann::json::parse(
action_config.redfish().json_body(), /* cb */ nullptr,
/* allow_exceptions */ false);
if (redfish_config.body.is_discarded()) {
LOG(ERROR) << "Failed to parse json body: "
<< action_config.redfish().json_body();
return absl::InvalidArgumentError("Failed to parse json body: " +
action_config.redfish().json_body());
}
redfish_config.uri = action_config.redfish().uri();
if (action_config.redfish().port() > 65535) {
LOG(ERROR) << "Port number is invalid: "
<< action_config.redfish().port();
return absl::InvalidArgumentError(absl::StrCat(
"Port number is invalid: ", action_config.redfish().port()));
}
redfish_config.port = action_config.redfish().port();
redfish_config.ip = action_config.redfish().ip();
LOG(INFO) << "registering " << action_config.action_name() << " "
<< redfish_config.ip << ":" << redfish_config.port << " "
<< redfish_config.uri << " " << redfish_config.body;
if (action_config.redfish().call_timeout_seconds() > 0) {
redfish_config.call_timeout =
absl::Seconds(action_config.redfish().call_timeout_seconds());
}
absl::Status action_status;
if (action_config.disruption_timeout_seconds() > 0) {
action_status = action_context_manager->RegisterAction(
action_to_register,
DisruptiveRedfishPowerOperation(
std::move(redfish_config),
absl::Seconds(action_config.disruption_timeout_seconds())));
} else {
action_status = action_context_manager->RegisterAction(
action_to_register, absl::bind_front(GenericRedfishPowerOperation,
std::move(redfish_config)));
}
if (!action_status.ok()) {
LOG(ERROR) << "Unable to register actions :" << action_status;
return action_status;
}
}
LOG(INFO) << "All actions registered";
return absl::OkStatus();
}
} // namespace safepower_agent