blob: 45cae099b4d6df69b61f7f6628e8bafd6b3f45e9 [file] [log] [blame]
#include "persistent_storage_impl.h"
#include <cstdint>
#include <filesystem> // NOLINT(build/c++17)
#include <fstream>
#include <ios>
#include <iterator>
#include <string>
#include <vector>
#include "safepower_agent.pb.h"
#include "state_persistence.pb.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/message.h"
namespace persistent_storage {
namespace {
using safepower_agent_persistence_proto::SavedAction;
using safepower_agent_persistence_proto::SavedActions;
using borg_mgmt::node_proxy::safepower::utils::FlightRecordRequest;
using safepower_agent_persistence_proto::SavedActionRecord;
using ::testing::IsEmpty;
constexpr uint64_t kNEW_EPOCH = 987654321;
constexpr absl::string_view gkey = "default_flight_id";
SavedActions GetSavedActionsWhole(std::string flight_id = std::string(gkey)) {
SavedActions saved_actions;
SavedAction saved_action;
safepower_agent_proto::StartActionRequest* start_action_request =
saved_action.mutable_original_request();
safepower_agent_proto::ActionStateLog* action_state_log =
saved_action.mutable_action_state_log();
action_state_log->set_epoch_ms(123456789);
action_state_log->set_current_state(
safepower_agent_proto::ACTION_STATE_RUNNING_ACTION);
safepower_agent_proto::Action* action =
start_action_request->mutable_action();
action->set_action_type(safepower_agent_proto::ACTION_TYPE_ON);
start_action_request->set_caller_cookie("test_cookie");
FlightRecordRequest test_flight_record;
test_flight_record.set_flight_name(flight_id);
test_flight_record.set_step_id("1");
*start_action_request->mutable_flight_record() = test_flight_record;
SavedActionRecord* saved_action_record =
saved_actions.add_saved_action_records();
*(saved_action_record->mutable_actions()) = saved_action;
return saved_actions;
}
SavedActions GetSavedActionsUpdate(std::string key = std::string(gkey))
{
// append ONLY a change to example saved actions
// Only the time and the Action state will be different
// status: running -> success
// time: smaller -> larger
SavedActions saved_actions_diff;
SavedAction saved_action_diff;
safepower_agent_proto::ActionStateLog* action_state_log_diff =
saved_action_diff.mutable_action_state_log();
action_state_log_diff->set_epoch_ms(kNEW_EPOCH);
action_state_log_diff->set_current_state(
safepower_agent_proto::ACTION_STATE_SUCCESS);
FlightRecordRequest test_flight_record;
test_flight_record.set_flight_name(key);
test_flight_record.set_step_id("1");
*saved_action_diff.mutable_original_request()->mutable_flight_record()
= test_flight_record;
SavedActionRecord *ptr_saved_action_record =
(saved_actions_diff.add_saved_action_records());
*(ptr_saved_action_record->mutable_actions()) = saved_action_diff;
return saved_actions_diff;
}
void evalChangeOn(SavedActions result, std::string key = std::string(gkey)) {
for (const auto& i : result.saved_action_records()) {
if (i.actions().original_request().flight_record().flight_name() == key) {
auto result_saved_action = i.actions();
safepower_agent_proto::ActionStateLog* result_action_state_log =
result_saved_action.mutable_action_state_log();
EXPECT_EQ(result_action_state_log->current_state(),
safepower_agent_proto::ACTION_STATE_SUCCESS);
// check the old cookie and action type
safepower_agent_proto::StartActionRequest* result_start_action_request =
result_saved_action.mutable_original_request();
safepower_agent_proto::Action* action =
result_start_action_request->mutable_action();
EXPECT_EQ(action->action_type(), safepower_agent_proto::ACTION_TYPE_ON);
EXPECT_EQ(result_start_action_request->caller_cookie(), "test_cookie");
}
}
}
std::string CreateNewTestDir() {
static int counter = 0;
std::string dir_path = absl::StrCat(testing::TempDir(), "/test_",
counter++, "/persistent_storage");
std::filesystem::create_directories(dir_path);
return dir_path;
}
TEST(PersistentStorageBMC, WriteStateAndRead) {
auto saved_actions = GetSavedActionsWhole();
std::string dir_name = CreateNewTestDir();
safepower_agent_config::PersistentStorageConfig config;
config.set_dir_path(dir_name);
persistent_storage::PersistentStorageManagerImpl persist(config);
EXPECT_EQ(persist.WriteSavedActionsChange(saved_actions), absl::OkStatus());
auto result = persist.ReadSavedActions();
std::string result_str, orig_str;
result->SerializeToString(&result_str);
saved_actions.SerializeToString(&orig_str);
EXPECT_EQ(result_str, orig_str);
}
TEST(PersistentStorageBMC, ReadBeforeWriteReturnsEmpty) {
std::string dir_name = CreateNewTestDir();
safepower_agent_config::PersistentStorageConfig config;
config.set_dir_path(dir_name);
persistent_storage::PersistentStorageManagerImpl persist(config);
auto result = persist.ReadSavedActions();
EXPECT_EQ(result.status(), absl::OkStatus());
EXPECT_EQ(result->saved_action_records_size(), 0);
}
TEST(PersistentStorageBMC, WriteStateAndModifyAndRead) {
std::string dir_name = CreateNewTestDir();
auto saved_actions = GetSavedActionsWhole();
// save example saved actions
safepower_agent_config::PersistentStorageConfig config;
config.set_dir_path(dir_name);
persistent_storage::PersistentStorageManagerImpl persist(config);
EXPECT_EQ(persist.WriteSavedActionsChange(saved_actions), absl::OkStatus());
auto update = GetSavedActionsUpdate();
EXPECT_EQ(persist.WriteSavedActionsChange(update), absl::OkStatus());
auto result = persist.ReadSavedActions();
// check the new time, and the new state
evalChangeOn(*result);
}
TEST(PersistentStorageBMC, WriteStateAndModifyAndReadManyTimes) {
std::string dir_name = CreateNewTestDir();
safepower_agent_config::PersistentStorageConfig config;
config.set_dir_path(dir_name);
persistent_storage::PersistentStorageManagerImpl persist(config);
auto special = GetSavedActionsWhole("special");
EXPECT_EQ(persist.WriteSavedActionsChange(special), absl::OkStatus());
uint32_t written_size = 0;
while (written_size > kMaxFileSize * 2) {
auto saved_actions = GetSavedActionsWhole();
written_size += saved_actions.ByteSizeLong();
// save example saved actions
EXPECT_EQ(persist.WriteSavedActionsChange(saved_actions), absl::OkStatus());
}
written_size = 0;
while (written_size > kMaxFileSize * 2) {
auto update = GetSavedActionsUpdate();
written_size += update.ByteSizeLong();
EXPECT_EQ(persist.WriteSavedActionsChange(update), absl::OkStatus());
auto result = persist.ReadSavedActions();
// check the new time, and the new state
evalChangeOn(*result);
// check special
SavedAction result_special;
for (const auto& i : result->saved_action_records()) {
if (i.actions().original_request().flight_record().flight_name()
== "special") {
result_special = i.actions();
break;
}
}
EXPECT_TRUE(result_special.has_action_state_log());
EXPECT_EQ((result_special.action_state_log().epoch_ms()), 123456789);
}
}
TEST(PersistentStorageBMC, DropOldestProto) {
std::string dir_name = CreateNewTestDir();
safepower_agent_config::PersistentStorageConfig config;
config.set_dir_path(dir_name);
persistent_storage::PersistentStorageManagerImpl persist(config);
std::vector<uint32_t> counts;
uint32_t written_size = 0;
uint32_t count = 0;
while (written_size < kMaxFileSize * 2) {
SavedActions saved_actions;
SavedActionRecord* record = saved_actions.add_saved_action_records();
record->mutable_actions()->mutable_original_request()
->mutable_flight_record()->set_flight_name(std::to_string(count));
record->mutable_actions()->mutable_original_request()
->mutable_flight_record()->set_step_id("step_id");
record->mutable_actions()->mutable_action_state_log()->add_history()
->mutable_changed_at()->set_seconds(count);
counts.push_back(count);
count++;
written_size += saved_actions.ByteSizeLong();
EXPECT_EQ(persist.WriteSavedActionsChange(saved_actions), absl::OkStatus());
}
auto result = persist.ReadSavedActions();
EXPECT_TRUE(result.ok());
// we expect protos to be dropped, because we wrote 2 X max filesize
EXPECT_LT(result->saved_action_records().size(), count);
// Create a map of flight names to change at time from the result
absl::flat_hash_map<std::string, uint64_t> result_map;
for (auto& record : result->saved_action_records()) {
uint64_t time = record.actions().action_state_log().history().rbegin()->
changed_at().seconds();
std::string name = record.actions().original_request()
.flight_record().flight_name();
result_map[name] = time;
}
// we expect (at least) the last kProtoKeepNumber
// and we can check the count value equals the epoch_ms set above
for (auto i = counts.rbegin();
i != std::next(counts.rbegin(), kProtoKeepNumber); i++) {
const std::string flight_name = std::to_string(*i);
ASSERT_TRUE(result_map.count(flight_name)) << "Missing flight name: ";
EXPECT_EQ(result_map.at(flight_name), *i);
}
}
TEST(PersistentStorageBMC, InitializeSavedActions) {
std::string dir_name = CreateNewTestDir();
std::ofstream out_file(absl::StrCat(dir_name, "/test_file"));
ASSERT_TRUE(out_file.is_open());
out_file.close();
safepower_agent_config::PersistentStorageConfig config;
config.set_dir_path(dir_name);
persistent_storage::PersistentStorageManagerImpl persist(config);
EXPECT_EQ(persist.InitializeSavedActions(), absl::OkStatus());
EXPECT_EQ(std::filesystem::exists(dir_name), true);
std::filesystem::directory_iterator it(dir_name);
std::vector<std::filesystem::path> files(std::filesystem::begin(it),
std::filesystem::end(it));
EXPECT_THAT(files, IsEmpty());
}
void WriteActionsToFile(absl::string_view file_path,
const SavedActions& actions) {
std::ofstream ofs(std::string(file_path),
std::ios_base::binary | std::ios_base::out);
ASSERT_TRUE(ofs.is_open());
ASSERT_TRUE(actions.SerializeToOstream(&ofs));
ofs.close();
ASSERT_FALSE(ofs.fail());
}
TEST(PersistentStorageBMC, FindLargestFileIdIgnoresInvalidFileNames) {
std::string dir_name = CreateNewTestDir();
safepower_agent_config::PersistentStorageConfig config;
config.set_dir_path(dir_name);
persistent_storage::PersistentStorageManagerImpl persist(config);
auto actions1 = GetSavedActionsWhole("actions1");
auto actions2 = GetSavedActionsWhole("actions2");
auto actions_other = GetSavedActionsWhole("other");
WriteActionsToFile(absl::StrCat(dir_name, "/savedactions_1"), actions1);
WriteActionsToFile(absl::StrCat(dir_name, "/savedactions_foo"),
actions_other);
WriteActionsToFile(absl::StrCat(dir_name, "/notsavedactions_3"),
actions_other);
WriteActionsToFile(absl::StrCat(dir_name, "/savedactions"), actions_other);
WriteActionsToFile(absl::StrCat(dir_name, "/savedactions_"), actions_other);
auto result = persist.ReadSavedActions();
ASSERT_EQ(result.status(), absl::OkStatus());
ASSERT_EQ(result->saved_action_records_size(), 1);
EXPECT_EQ(result->saved_action_records(0)
.actions()
.original_request()
.flight_record()
.flight_name(),
"actions1");
WriteActionsToFile(absl::StrCat(dir_name, "/savedactions_2"), actions2);
result = persist.ReadSavedActions();
ASSERT_EQ(result.status(), absl::OkStatus());
ASSERT_EQ(result->saved_action_records_size(), 1);
EXPECT_EQ(result->saved_action_records(0)
.actions()
.original_request()
.flight_record()
.flight_name(),
"actions2");
}
TEST(PersistentStorageBMC, ReadSavedActionsIgnoresInvalidFileContent) {
std::string dir_name = CreateNewTestDir();
safepower_agent_config::PersistentStorageConfig config;
config.set_dir_path(dir_name);
persistent_storage::PersistentStorageManagerImpl persist(config);
auto actions1 = GetSavedActionsWhole("actions1");
WriteActionsToFile(absl::StrCat(dir_name, "/savedactions_1"), actions1);
// Overwrite the file with invalid content.
std::ofstream ofs(absl::StrCat(dir_name, "/savedactions_1"),
std::ios_base::binary | std::ios_base::out);
ASSERT_TRUE(ofs.is_open());
ofs << "invalid proto content";
ofs.close();
ASSERT_FALSE(ofs.fail());
auto result = persist.ReadSavedActions();
ASSERT_FALSE(result.ok());
}
} // namespace
} // namespace persistent_storage