blob: 950e8669acf97f537cbaf336479a9f3bbc7b419b [file] [log] [blame]
#include "bmc/http_connection.h"
#include <chrono> // NOLINT(build/c++11)
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "bmc/daemon_context_bmc.h"
#include "bmc/mock_server.h"
#include "bmc/redfish.h"
#include "bmc/state_monitor_bmc.h"
#include "safepower_agent.pb.h"
#include "safepower_agent_config.pb.h"
#include "state_updater.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/time/time.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
namespace safepower_agent {
namespace {
using mock_server_test::TestBoostServer;
using ::testing::StartsWith;
TEST(http, NoConnection) {
// DO NOT start the test sever
DaemonContextBMC context; // only use DaemonContextBMC::Get()
auto my_connection = std::make_shared<HttpConnection>();
bool callback_ran = false;
my_connection->PerformConnection(
(http::verb::post), "/redfish/v1/Systems/system/Actions/Power.Reset",
[&callback_ran](absl::StatusOr<nlohmann::json> jres) {
ASSERT_FALSE(jres.ok());
ASSERT_EQ(jres.status(),
absl::UnavailableError(absl::StrCat(
"Failed to connect to BMC: ", "Connection refused")));
callback_ran = true;
},
{});
DaemonContextBMC::Get().get_io_context().run_for(std::chrono::seconds(1));
EXPECT_TRUE(callback_ran);
}
TEST(http, BadAddress) {
DaemonContextBMC context; // only use DaemonContextBMC::Get()
mock_server_test::TestBoostServer my_server;
my_server.Start(DaemonContextBMC::Get().get_io_context());
auto my_connection = std::make_shared<HttpConnection>(absl::ZeroDuration());
bool callback_ran = false;
my_connection->PerformConnection(
(http::verb::post), "/redfish/v1/Systems/system/Actions/Power.Reset",
[&callback_ran](absl::StatusOr<nlohmann::json> jres) {
ASSERT_TRUE(absl::IsUnavailable(jres.status()));
ASSERT_THAT(jres.status().message(),
StartsWith("Failed to connect to BMC: "));
callback_ran = true;
},
{}, "240.0.0.0", 80);
DaemonContextBMC::Get().get_io_context().run_for(std::chrono::seconds(1));
EXPECT_TRUE(callback_ran);
}
TEST(http, WriteTimeOut) {
DaemonContextBMC context; // only use DaemonContextBMC::Get()
TestBoostServer my_server;
my_server.Start(DaemonContextBMC::Get().get_io_context());
auto my_connection =
std::make_shared<HttpConnection>(absl::Seconds(2), absl::ZeroDuration());
bool callback_ran = false;
my_connection->PerformConnection(
(http::verb::post),
"/redfish/v1/Systems/system/Actions/Power.Reset_never_write",
[&callback_ran](absl::StatusOr<nlohmann::json> jres) {
ASSERT_FALSE(jres.ok());
ASSERT_EQ(jres.status(),
absl::UnknownError("Callback error handler: The socket was "
"closed due to a timeout"));
callback_ran = true;
},
{});
DaemonContextBMC::Get().get_io_context().run_for(std::chrono::seconds(1));
EXPECT_TRUE(callback_ran);
}
TEST(http, MalFormedJson) {
DaemonContextBMC context; // only use DaemonContextBMC::Get()
TestBoostServer my_server;
my_server.Start(DaemonContextBMC::Get().get_io_context());
auto my_connection = std::make_shared<HttpConnection>();
bool callback_ran = false;
my_connection->PerformConnection(
http::verb::post,
"/redfish/v1/Systems/system/Actions/Power.Reset_malformed",
[&callback_ran](absl::StatusOr<nlohmann::json> jres) {
ASSERT_FALSE(jres.ok());
ASSERT_EQ(jres.status(), absl::InvalidArgumentError(absl::StrCat(
"Failed to parse JSON from BMC: ",
mock_server_test::malformedJson)));
callback_ran = true;
},
{});
DaemonContextBMC::Get().get_io_context().run_for(std::chrono::seconds(1));
EXPECT_TRUE(callback_ran);
}
TEST(http, NotFound) {
DaemonContextBMC context; // only use DaemonContextBMC::Get()
TestBoostServer my_server;
my_server.Start(DaemonContextBMC::Get().get_io_context());
auto my_connection = std::make_shared<HttpConnection>();
bool callback_ran = false;
my_connection->PerformConnection(
http::verb::post,
"/redfish/v1/Systems/system/Actions/Power.Reset_NotFound",
[&callback_ran](absl::StatusOr<nlohmann::json> jres) {
ASSERT_FALSE(jres.ok());
ASSERT_EQ(jres.status(), absl::UnavailableError(
"HTTP request failed with status: 404"));
callback_ran = true;
},
{});
DaemonContextBMC::Get().get_io_context().run_for(std::chrono::seconds(1));
EXPECT_TRUE(callback_ran);
}
TEST(http, GoodTest) {
DaemonContextBMC context; // only use DaemonContextBMC::Get()
auto my_server = std::make_shared<TestBoostServer>();
my_server->Start(DaemonContextBMC::Get().get_io_context());
auto my_connection = std::make_shared<HttpConnection>();
bool callback_ran = false;
my_connection->PerformConnection(
http::verb::post, "/redfish/v1/Systems/system/Actions/Power.Reset",
[&callback_ran](absl::StatusOr<nlohmann::json> jres) {
ASSERT_TRUE(jres.ok());
callback_ran = true;
nlohmann::json good = nlohmann::json::parse(mock_server_test::goodJson);
ASSERT_EQ(jres.value(), good);
},
{});
DaemonContextBMC::Get().get_io_context().run_for(std::chrono::seconds(1));
EXPECT_TRUE(callback_ran);
}
// redfish Tests
// redfish and http test both use mock server
// but they can not run at the same time
TEST(http, GoodGet) {
DaemonContextBMC context; // only use DaemonContextBMC::Get()
TestBoostServer my_server;
my_server.Start(DaemonContextBMC::Get().get_io_context());
bool callback_ran = false;
Redfish::Get("/redfish/v1/Systems/system_get",
[&callback_ran](absl::StatusOr<nlohmann::json> jres) {
ASSERT_TRUE(jres.ok());
nlohmann::json good =
nlohmann::json::parse(mock_server_test::goodSystemGet);
ASSERT_EQ(jres.value(), good);
callback_ran = true;
});
DaemonContextBMC::Get().get_io_context().run_for(std::chrono::seconds(1));
EXPECT_TRUE(callback_ran);
}
TEST(http, BadGet) {
DaemonContextBMC context; // only use DaemonContextBMC::Get()
TestBoostServer my_server;
my_server.Start(DaemonContextBMC::Get().get_io_context());
bool callback_ran = false;
Redfish::Get("/redfish/v1/Systems/system_NotFound",
[&callback_ran](absl::StatusOr<nlohmann::json> jres) {
ASSERT_FALSE(jres.ok());
ASSERT_EQ(jres.status(),
absl::UnavailableError(
"HTTP request failed with status: 404"));
callback_ran = true;
});
DaemonContextBMC::Get().get_io_context().run_for(std::chrono::seconds(1));
EXPECT_TRUE(callback_ran);
}
using safepower_agent_config::GpowerdConfig;
TEST(StateMonitorBMC, GoodBuild) {
DaemonContextBMC context; // only use DaemonContextBMC::Get()
TestBoostServer my_server;
my_server.Start(DaemonContextBMC::Get().get_io_context());
GpowerdConfig gpower_config;
auto state_monitor_config = gpower_config.add_state_monitor_config();
state_monitor_config->set_state_name("test_state");
state_monitor_config->mutable_state_gathering_info()
->set_collection_interval_ms(100);
state_monitor_config->mutable_state_gathering_info()
->mutable_redfish()
->set_uri("/redfish/v1/Systems/system/_testingPowerState");
// if the config file has a key, it should also have the corresponding state
std::vector<
std::pair<std::string, safepower_agent_proto::PowerStateSpecifier>>
json_key_to_state_name = {
{"PowerState_On", safepower_agent_proto::POWER_STATE_ON},
{"PowerState_Off", safepower_agent_proto::POWER_STATE_OFF},
{"PowerState_Paused", safepower_agent_proto::POWER_STATE_PAUSED},
{"PowerState_PoweringOn",
safepower_agent_proto::POWER_STATE_POWERING_ON},
{"PowerState_PoweringOff",
safepower_agent_proto::POWER_STATE_POWERING_OFF},
{"PowerState_UNSPECIFIED_1",
safepower_agent_proto::POWER_STATE_UNSPECIFIED},
{"PowerState_UNSPECIFIED_2",
safepower_agent_proto::POWER_STATE_UNSPECIFIED},
{"", safepower_agent_proto::POWER_STATE_UNSPECIFIED},
{"b", safepower_agent_proto::POWER_STATE_UNSPECIFIED}};
for (auto str_state_pair : json_key_to_state_name) {
state_monitor_config->mutable_state_gathering_info()
->mutable_redfish()
->set_json_key(str_state_pair.first);
state_monitor_config->set_state_name(str_state_pair.first);
state_monitor_config->mutable_system_component()->set_node_entity_tag("cn");
state_monitor_config->mutable_system_component()->set_name("PowerState");
state_monitor_config->mutable_state_gathering_info()
->mutable_prototype()
->mutable_power_state()
->set_state(safepower_agent_proto::POWER_STATE_POWERING_OFF);
// build a new system state
safepower_agent_proto::SystemState initialState;
// set default power state
safepower_agent_proto::PowerState ps;
ps.set_state(safepower_agent_proto::POWER_STATE_POWERING_OFF);
safepower_agent_proto::ComponentState cs;
*(cs.mutable_power_state()) = ps;
safepower_agent_proto::NodeState ns;
ns.mutable_component_state()->insert({"PowerState", cs});
initialState.mutable_node_state()->insert({"cn", ns});
auto reactor =
std::make_shared<StateUpdater<safepower_agent_proto::SystemState>>(
std::move(initialState), true);
// monitor new systemd state, with specific key
StateMonitorBMC state_monitor(reactor);
ASSERT_EQ(state_monitor.BuildFromConfig(gpower_config), absl::OkStatus());
DaemonContextBMC::Get().get_io_context().run_for(
std::chrono::milliseconds(300));
// check the state monitor updates its reactor with the correct state
EXPECT_EQ(reactor->state()
.node_state()
.at("cn")
.component_state()
.at("PowerState")
.power_state()
.state(),
str_state_pair.second);
ASSERT_EQ(DaemonContextBMC::Get().scheduler().CancelAll(),
absl::OkStatus());
DaemonContextBMC::Get().get_io_context().run_for(
std::chrono::milliseconds(300));
}
DaemonContextBMC::Get().get_io_context().run_for(
std::chrono::milliseconds(1000));
}
} // namespace
} // namespace safepower_agent