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