| #include "persistent_storage_impl.h" |
| |
| #include <cstdint> |
| #include <filesystem> // NOLINT(build/c++17) |
| #include <fstream> |
| #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" |
| #include "google/protobuf/text_format.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 TestDir() { |
| return absl::StrCat(testing::TempDir(), "/persistent_storage"); |
| } |
| |
| TEST(PersistentStorageBMC, WriteStateAndRead) { |
| auto saved_actions = GetSavedActionsWhole(); |
| std::string dir_name = TestDir(); |
| std::filesystem::create_directory(dir_name); |
| persistent_storage::PersistentStorageManagerImpl persist; |
| EXPECT_EQ(persist.WriteSavedActionsChange(saved_actions, dir_name), |
| absl::OkStatus()); |
| auto result = persist.ReadSavedActions(dir_name); |
| |
| std::string result_str, orig_str; |
| result->SerializeToString(&result_str); |
| saved_actions.SerializeToString(&orig_str); |
| EXPECT_EQ(result_str, orig_str); |
| } |
| |
| TEST(PersistentStorageBMC, WriteStateAndModifyAndRead) { |
| std::string dir_name = TestDir(); |
| std::filesystem::create_directory(dir_name); |
| auto saved_actions = GetSavedActionsWhole(); |
| // save example saved actions |
| persistent_storage::PersistentStorageManagerImpl persist; |
| EXPECT_EQ(persist.WriteSavedActionsChange(saved_actions, dir_name), |
| absl::OkStatus()); |
| auto update = GetSavedActionsUpdate(); |
| EXPECT_EQ(persist.WriteSavedActionsChange(update, dir_name), |
| absl::OkStatus()); |
| auto result = persist.ReadSavedActions(dir_name); |
| // check the new time, and the new state |
| evalChangeOn(*result); |
| } |
| |
| TEST(PersistentStorageBMC, WriteStateAndModifyAndReadManyTimes) { |
| std::string dir_name = TestDir(); |
| persistent_storage::PersistentStorageManagerImpl persist; |
| auto special = GetSavedActionsWhole("special"); |
| EXPECT_EQ(persist.WriteSavedActionsChange(special, dir_name), |
| 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, dir_name), |
| absl::OkStatus()); |
| } |
| written_size = 0; |
| while (written_size > kMaxFileSize * 2) { |
| auto update = GetSavedActionsUpdate(); |
| written_size += update.ByteSizeLong(); |
| EXPECT_EQ(persist.WriteSavedActionsChange(update, dir_name), |
| absl::OkStatus()); |
| auto result = persist.ReadSavedActions(dir_name); |
| // 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, testDropOldestProto) { |
| std::string dir_name = TestDir(); |
| persistent_storage::PersistentStorageManagerImpl persist; |
| 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, dir_name), |
| absl::OkStatus()); |
| } |
| |
| auto result = persist.ReadSavedActions(dir_name); |
| 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, testInitializeSavedActions) { |
| std::string dir_name = TestDir(); |
| 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()); |
| } |
| |
| } // namespace |
| |
| } // namespace persistent_storage |