blob: 9a403f2e5a8340de5472e9c7cbde3628259e5e47 [file] [log] [blame] [edit]
#include "bmc/register_actions_bmc.h"
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <ostream>
#include <string>
#include <utility>
#include "action_context.h"
#include "bmc/daemon_context_bmc.h"
#include "disruption_manager.h"
#include "parse_text_proto.h"
#include "safepower_agent.pb.h"
#include "safepower_agent_config.pb.h"
#include "state_merge.h"
#include "state_updater.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "test_util.h"
namespace safepower_agent {
namespace {
bool isASanEnabled() { return std::getenv("ASAN_OPTIONS") != nullptr; }
TEST(RegisterActionsTest, RegisterAction) {
if (isASanEnabled()) {
LOG(ERROR) << "b/349393089 absl::map insert fails asan" << std::endl;
GTEST_SKIP() << "Skipping test in ASan environment.";
// TODO b/349393089 absl::map insert fails asan
// Error message //https://paste.googleplex.com/6557456877748224
}
safepower_agent_proto::SystemState initialState;
auto reactor =
std::make_shared<StateUpdater<safepower_agent_proto::SystemState>>(
std::move(initialState), true);
auto action_context_manager =
std::make_unique<safepower_agent::ActionContextManager>(
reactor,
std::make_shared<StateUpdater<safepower_agent_proto::SystemState>>());
ASSERT_OK_AND_ASSIGN(
auto safepower_agent_config,
ParseTextProto<safepower_agent_config::SafePowerAgentConfig>(R"pb(
action_configs {
action {
action_type: ACTION_TYPE_CYCLE
action_severity: ACTION_SEVERITY_FORCE
}
redfish {
uri: "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
json_body: "{\"ResetType\":\"ForceRestart\"}"
}
}
action_configs {
action {
action_type: ACTION_TYPE_CYCLE
action_severity: ACTION_SEVERITY_GRACEFUL
}
redfish {
uri: "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
json_body: "{\"ResetType\":\"GracefulRestart\"}"
}
}
)pb"));
ASSERT_OK(RegisterActionFromConfig(action_context_manager.get(),
safepower_agent_config));
safepower_agent_proto::GetSupportedActionsResponse response;
action_context_manager->GetSupportedActions(response);
EXPECT_EQ(response.actions_size(), 3);
bool has_noop_action = false;
bool has_force_restart_action = false;
bool has_graceful_restart_action = false;
for (const auto& action : response.actions()) {
if (action.action_type() == safepower_agent_proto::ACTION_TYPE_CYCLE &&
action.action_severity() ==
safepower_agent_proto::ACTION_SEVERITY_FORCE) {
has_force_restart_action = true;
} else if (action.action_type() ==
safepower_agent_proto::ACTION_TYPE_CYCLE &&
action.action_severity() ==
safepower_agent_proto::ACTION_SEVERITY_GRACEFUL) {
has_graceful_restart_action = true;
} else if (action.action_type() ==
safepower_agent_proto::ACTION_TYPE_UNSPECIFIED) {
has_noop_action = true;
}
}
EXPECT_TRUE(has_noop_action) << response.DebugString();
EXPECT_TRUE(has_force_restart_action) << response.DebugString();
EXPECT_TRUE(has_graceful_restart_action) << response.DebugString();
}
absl::StatusOr<std::string> CreatePersistenceDir() {
std::string tmpl =
absl::StrCat(testing::TempDir(), "/safepower_agent_test_XXXXXX");
char* tmp = mkdtemp(tmpl.data());
if (tmp == nullptr) {
return absl::InternalError(absl::StrCat(
"Failed to create temporary directory:", std::strerror(errno)));
}
return tmpl;
}
static void SetDefaultOfflineNodeEntities() {
auto offline_node_entities = ParseTextProto<
production_msv::node_entities_proto::OfflineNodeEntityInformation>(
R"pb(
entity_tag: "host"
resolved_config {
machine_name: "test"
entities {
key: "host"
value { tag: "host" hostname: "test" prod_reachable: true }
}
}
)pb");
ASSERT_OK(offline_node_entities);
DaemonContextBMC::Get().set_offline_node_entities(*offline_node_entities);
}
static safepower_agent_proto::SystemState InitialSystemState() {
auto initial_state = ParseTextProto<safepower_agent_proto::SystemState>(R"pb(
node_state {
key: "host"
value { boot_state { boot_counter: 1 } }
}
)pb");
CHECK_OK(initial_state);
return *initial_state;
}
TEST(RegisterActionsTest, RegisteredDisruptiveActionCausesDisruption) {
if (isASanEnabled()) {
LOG(ERROR) << "b/349393089 absl::map insert fails asan" << std::endl;
GTEST_SKIP() << "Skipping test in ASan environment.";
// TODO b/349393089 absl::map insert fails asan
// Error message //https://paste.googleplex.com/6557456877748224
}
ASSERT_OK_AND_ASSIGN(std::string persistence_dir, CreatePersistenceDir());
LOG(INFO) << "Created temp dir: " << persistence_dir;
DaemonContextBMC daemon_context;
SetDefaultOfflineNodeEntities();
ASSERT_OK_AND_ASSIGN(
auto safepower_agent_config,
ParseTextProto<safepower_agent_config::SafePowerAgentConfig>(R"pb(
action_configs {
action {
action_type: ACTION_TYPE_CYCLE
action_severity: ACTION_SEVERITY_FORCE
}
redfish {
uri: "/redfish/v1/Systems/system/Actions/ComputerSystem.Reset"
json_body: "{\"ResetType\":\"ForceRestart\"}"
call_timeout_seconds: 5
ip: "127.0.0.1"
port: 80
}
disruption_timeout_seconds: 10
}
)pb"));
safepower_agent_config.mutable_persistent_storage_config()->set_dir_path(
persistence_dir);
daemon_context.set_config(safepower_agent_config);
safepower_agent_proto::SystemState initialState = InitialSystemState();
auto reactor =
std::make_shared<StateUpdater<safepower_agent_proto::SystemState>>(
std::move(initialState), true);
auto action_context_manager =
std::make_unique<safepower_agent::ActionContextManager>(
reactor,
std::make_shared<StateUpdater<safepower_agent_proto::SystemState>>());
ASSERT_OK(RegisterActionFromConfig(action_context_manager.get(),
safepower_agent_config));
safepower_agent_proto::StartActionRequest request;
*request.mutable_action() = safepower_agent_config.action_configs(0).action();
request.mutable_flight_record()->set_flight_name("test_flight_id");
request.mutable_flight_record()->set_step_id("1");
bool callback_called = false;
DisruptionManager::CallbackHandle start_handle =
daemon_context.disruption_manager().OnDisruptionStart(
[&] { callback_called = true; });
ASSERT_OK(action_context_manager->StartAction(request));
daemon_context.get_io_context().run_for(
absl::ToChronoSeconds(absl::Seconds(1)));
EXPECT_TRUE(callback_called);
}
TEST(RegisterActionsTest, NoOpActionRunsAndDoesNothing) {
ASSERT_OK_AND_ASSIGN(std::string persistence_dir, CreatePersistenceDir());
LOG(INFO) << "Created temp dir: " << persistence_dir;
DaemonContextBMC daemon_context;
SetDefaultOfflineNodeEntities();
safepower_agent_config::SafePowerAgentConfig safepower_agent_config;
safepower_agent_config.mutable_persistent_storage_config()->set_dir_path(
persistence_dir);
daemon_context.set_config(safepower_agent_config);
safepower_agent_proto::SystemState initialState = InitialSystemState();
auto reactor =
std::make_shared<StateUpdater<safepower_agent_proto::SystemState>>(
std::move(initialState), true);
auto action_context_manager =
std::make_unique<safepower_agent::ActionContextManager>(
reactor,
std::make_shared<StateUpdater<safepower_agent_proto::SystemState>>());
ASSERT_OK(RegisterActionFromConfig(action_context_manager.get(),
safepower_agent_config));
safepower_agent_proto::StartActionRequest request;
request.mutable_flight_record()->set_flight_name("test_flight_id");
request.mutable_flight_record()->set_step_id("1");
ASSERT_OK_AND_ASSIGN(auto action_context,
action_context_manager->StartAction(request));
struct Listener
: public StateUpdater<safepower_agent_proto::ActionStateLog>::Listener {
public:
void UpdateState(
const safepower_agent_proto::ActionStateLog& prev,
const safepower_agent_proto::ActionStateLog& update) override {
LOG(INFO) << "UpdateState: " << update;
last_state = MergeState(prev, update);
}
void Done() override { LOG(INFO) << "Done"; }
safepower_agent_proto::ActionStateLog last_state;
} listener;
listener.Listen(action_context->action_state_updater());
daemon_context.get_io_context().run_for(
absl::ToChronoSeconds(absl::Seconds(1)));
EXPECT_EQ(listener.last_state.current_state(),
safepower_agent_proto::ACTION_STATE_SUCCESS);
}
} // namespace
} // namespace safepower_agent