Introduce ApexSessionManager and add unit tests for it am: cd8f47160e am: a409070293 am: 51c5fc0484 am: bf06a57ee7 am: ea50d33cd2
Original change: https://android-review.googlesource.com/c/platform/system/apex/+/2694868
Change-Id: Ibc7710b793821cfe6881cb504cb55e9ca893e02d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/apexd/apexd_session.cpp b/apexd/apexd_session.cpp
index c71689c..4ce289e 100644
--- a/apexd/apexd_session.cpp
+++ b/apexd/apexd_session.cpp
@@ -16,11 +16,7 @@
#include "apexd_session.h"
-#include "apexd_utils.h"
-#include "string_log.h"
-
-#include "session_state.pb.h"
-
+#include <android-base/errors.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <dirent.h>
@@ -31,6 +27,10 @@
#include <optional>
#include <utility>
+#include "apexd_utils.h"
+#include "session_state.pb.h"
+#include "string_log.h"
+
using android::base::Error;
using android::base::Result;
using android::base::StringPrintf;
@@ -50,6 +50,21 @@
static constexpr const char* kStateFileName = "state";
+static Result<SessionState> ParseSessionState(const std::string& session_dir) {
+ auto path = StringPrintf("%s/%s", session_dir.c_str(), kStateFileName);
+ SessionState state;
+ std::fstream state_file(path, std::ios::in | std::ios::binary);
+ if (!state_file) {
+ return Error() << "Failed to open " << path;
+ }
+
+ if (!state.ParseFromIstream(&state_file)) {
+ return Error() << "Failed to parse " << path;
+ }
+
+ return std::move(state);
+}
+
} // namespace
ApexSession::ApexSession(SessionState state, std::string session_dir)
@@ -87,18 +102,11 @@
Result<ApexSession> ApexSession::GetSessionFromDir(
const std::string& session_dir) {
- auto path = StringPrintf("%s/%s", session_dir.c_str(), kStateFileName);
- SessionState state;
- std::fstream state_file(path, std::ios::in | std::ios::binary);
- if (!state_file) {
- return Error() << "Failed to open " << path;
+ auto state = ParseSessionState(session_dir);
+ if (!state.ok()) {
+ return state.error();
}
-
- if (!state.ParseFromIstream(&state_file)) {
- return Error() << "Failed to parse " << path;
- }
-
- return ApexSession(state, std::move(session_dir));
+ return ApexSession(*state, session_dir);
}
Result<ApexSession> ApexSession::GetSession(int session_id) {
@@ -197,7 +205,7 @@
return state_.apex_names();
}
-const std::string& ApexSession::GetSesionDir() const { return session_dir_; }
+const std::string& ApexSession::GetSessionDir() const { return session_dir_; }
void ApexSession::SetBuildFingerprint(const std::string& fingerprint) {
*(state_.mutable_expected_build_fingerprint()) = fingerprint;
@@ -259,7 +267,7 @@
std::ostream& operator<<(std::ostream& out, const ApexSession& session) {
return out << "[id = " << session.GetId()
<< "; state = " << SessionState::State_Name(session.GetState())
- << "; session_dir = " << session.GetSesionDir() << "]";
+ << "; session_dir = " << session.GetSessionDir() << "]";
}
void ApexSession::DeleteFinalizedSessions() {
@@ -275,5 +283,93 @@
}
}
+ApexSessionManager::ApexSessionManager(std::string sessions_base_dir)
+ : sessions_base_dir_(std::move(sessions_base_dir)) {}
+
+ApexSessionManager::ApexSessionManager(ApexSessionManager&& other) noexcept
+ : sessions_base_dir_(std::move(other.sessions_base_dir_)) {}
+
+ApexSessionManager& ApexSessionManager::operator=(
+ ApexSessionManager&& other) noexcept {
+ sessions_base_dir_ = std::move(other.sessions_base_dir_);
+ return *this;
+}
+
+std::unique_ptr<ApexSessionManager> ApexSessionManager::Create(
+ std::string sessions_base_dir) {
+ return std::unique_ptr<ApexSessionManager>(
+ new ApexSessionManager(std::move(sessions_base_dir)));
+}
+
+Result<ApexSession> ApexSessionManager::CreateSession(int session_id) {
+ SessionState state;
+ // Create session directory
+ std::string session_dir =
+ sessions_base_dir_ + "/" + std::to_string(session_id);
+ OR_RETURN(CreateDirIfNeeded(session_dir, 0700));
+ state.set_id(session_id);
+
+ return ApexSession(std::move(state), std::move(session_dir));
+}
+
+Result<ApexSession> ApexSessionManager::GetSession(int session_id) const {
+ auto session_dir =
+ StringPrintf("%s/%d", sessions_base_dir_.c_str(), session_id);
+
+ auto state = OR_RETURN(ParseSessionState(session_dir));
+ return ApexSession(std::move(state), std::move(session_dir));
+}
+
+std::vector<ApexSession> ApexSessionManager::GetSessions() const {
+ std::vector<ApexSession> sessions;
+
+ auto walk_status = WalkDir(sessions_base_dir_, [&](const auto& entry) {
+ if (!entry.is_directory()) {
+ return;
+ }
+
+ std::string session_dir = entry.path();
+ auto state = ParseSessionState(session_dir);
+ if (!state.ok()) {
+ LOG(WARNING) << state.error();
+ return;
+ }
+
+ ApexSession session(std::move(*state), std::move(session_dir));
+ sessions.push_back(std::move(session));
+ });
+
+ if (!walk_status.ok()) {
+ LOG(WARNING) << walk_status.error();
+ return std::move(sessions);
+ }
+
+ return std::move(sessions);
+}
+
+std::vector<ApexSession> ApexSessionManager::GetSessionsInState(
+ const SessionState::State& state) const {
+ std::vector<ApexSession> sessions = GetSessions();
+ sessions.erase(std::remove_if(sessions.begin(), sessions.end(),
+ [&](const ApexSession& s) {
+ return s.GetState() != state;
+ }),
+ sessions.end());
+
+ return sessions;
+}
+
+Result<void> ApexSessionManager::MigrateFromOldSessionsDir(
+ const std::string& old_sessions_base_dir) {
+ if (old_sessions_base_dir == sessions_base_dir_) {
+ LOG(INFO)
+ << old_sessions_base_dir
+ << " is the same as the current session directory. Nothing to migrate";
+ return {};
+ }
+
+ return MoveDir(old_sessions_base_dir, sessions_base_dir_);
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_session.h b/apexd/apexd_session.h
index 08db00e..95ebbf1 100644
--- a/apexd/apexd_session.h
+++ b/apexd/apexd_session.h
@@ -28,6 +28,7 @@
namespace android {
namespace apex {
+// TODO(b/288309411): remove static functions in this class.
class ApexSession {
public:
// Returns top-level directory to store sessions metadata in.
@@ -58,7 +59,7 @@
bool IsRollback() const;
int GetRollbackId() const;
const google::protobuf::RepeatedPtrField<std::string> GetApexNames() const;
- const std::string& GetSesionDir() const;
+ const std::string& GetSessionDir() const;
void SetChildSessionIds(const std::vector<int>& child_session_ids);
void SetBuildFingerprint(const std::string& fingerprint);
@@ -75,6 +76,8 @@
android::base::Result<void> DeleteSession() const;
static void DeleteFinalizedSessions();
+ friend class ApexSessionManager;
+
private:
ApexSession(::apex::proto::SessionState state, std::string session_dir);
::apex::proto::SessionState state_;
@@ -84,6 +87,31 @@
const std::string& session_dir);
};
+class ApexSessionManager {
+ public:
+ ApexSessionManager(ApexSessionManager&&) noexcept;
+ ApexSessionManager& operator=(ApexSessionManager&&) noexcept;
+
+ static std::unique_ptr<ApexSessionManager> Create(
+ std::string sessions_base_dir);
+
+ android::base::Result<ApexSession> CreateSession(int session_id);
+ android::base::Result<ApexSession> GetSession(int session_id) const;
+ std::vector<ApexSession> GetSessions() const;
+ std::vector<ApexSession> GetSessionsInState(
+ const ::apex::proto::SessionState::State& state) const;
+
+ android::base::Result<void> MigrateFromOldSessionsDir(
+ const std::string& old_sessions_base_dir);
+
+ private:
+ ApexSessionManager(std::string sessions_base_dir);
+ ApexSessionManager(const ApexSessionManager&) = delete;
+ ApexSessionManager& operator=(const ApexSessionManager&) = delete;
+
+ std::string sessions_base_dir_;
+};
+
std::ostream& operator<<(std::ostream& out, const ApexSession& session);
} // namespace apex
diff --git a/apexd/apexd_session_test.cpp b/apexd/apexd_session_test.cpp
index 7ee1eb1..86ef298 100644
--- a/apexd/apexd_session_test.cpp
+++ b/apexd/apexd_session_test.cpp
@@ -14,20 +14,22 @@
* limitations under the License.
*/
-#include <filesystem>
-#include <fstream>
-#include <string>
-
-#include <errno.h>
+#include "apexd_session.h"
#include <android-base/file.h>
+#include <android-base/result-gmock.h>
#include <android-base/result.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <errno.h>
#include <gtest/gtest.h>
-#include "apexd_session.h"
+#include <algorithm>
+#include <filesystem>
+#include <fstream>
+#include <string>
+
#include "apexd_test_utils.h"
#include "apexd_utils.h"
#include "session_state.pb.h"
@@ -39,7 +41,9 @@
using android::apex::testing::IsOk;
using android::base::Join;
using android::base::make_scope_guard;
+using android::base::testing::Ok;
using ::apex::proto::SessionState;
+using ::testing::Not;
// TODO(b/170329726): add unit tests for apexd_sessions.h
@@ -122,6 +126,216 @@
ASSERT_EQ(SessionState::ACTIVATION_FAILED, migrated_session_2->GetState());
}
+TEST(ApexSessionManagerTest, CreateSession) {
+ TemporaryDir td;
+ auto manager = ApexSessionManager::Create(std::string(td.path));
+
+ auto session = manager->CreateSession(239);
+ ASSERT_RESULT_OK(session);
+ ASSERT_EQ(239, session->GetId());
+ std::string session_dir = std::string(td.path) + "/239";
+ ASSERT_EQ(session_dir, session->GetSessionDir());
+}
+
+TEST(ApexSessionManagerTest, GetSessionsNoSessionReturnsError) {
+ TemporaryDir td;
+ auto manager = ApexSessionManager::Create(std::string(td.path));
+
+ ASSERT_THAT(manager->GetSession(37), Not(Ok()));
+}
+
+TEST(ApexSessionManagerTest, GetSessionsReturnsErrorSessionNotCommitted) {
+ TemporaryDir td;
+ auto manager = ApexSessionManager::Create(std::string(td.path));
+
+ auto session = manager->CreateSession(73);
+ ASSERT_RESULT_OK(session);
+ ASSERT_THAT(manager->GetSession(73), Not(Ok()));
+}
+
+TEST(ApexSessionManagerTest, CreateCommitGetSession) {
+ TemporaryDir td;
+ auto manager = ApexSessionManager::Create(std::string(td.path));
+
+ auto session = manager->CreateSession(23);
+ ASSERT_RESULT_OK(session);
+ session->SetErrorMessage("error");
+ ASSERT_RESULT_OK(session->UpdateStateAndCommit(SessionState::STAGED));
+
+ auto same_session = manager->GetSession(23);
+ ASSERT_RESULT_OK(same_session);
+ ASSERT_EQ(23, same_session->GetId());
+ ASSERT_EQ("error", same_session->GetErrorMessage());
+ ASSERT_EQ(SessionState::STAGED, same_session->GetState());
+}
+
+TEST(ApexSessionManagerTest, GetSessionsNoSessionsCommitted) {
+ TemporaryDir td;
+ auto manager = ApexSessionManager::Create(std::string(td.path));
+
+ ASSERT_RESULT_OK(manager->CreateSession(3));
+
+ auto sessions = manager->GetSessions();
+ ASSERT_EQ(0u, sessions.size());
+}
+
+TEST(ApexSessionManager, GetSessionsCommittedSessions) {
+ TemporaryDir td;
+ auto manager = ApexSessionManager::Create(std::string(td.path));
+
+ auto session1 = manager->CreateSession(1543);
+ ASSERT_RESULT_OK(session1);
+ ASSERT_RESULT_OK(session1->UpdateStateAndCommit(SessionState::ACTIVATED));
+
+ auto session2 = manager->CreateSession(179);
+ ASSERT_RESULT_OK(session2);
+ ASSERT_RESULT_OK(session2->UpdateStateAndCommit(SessionState::SUCCESS));
+
+ // This sessions is not committed, it won't be returned in GetSessions.
+ ASSERT_RESULT_OK(manager->CreateSession(101));
+
+ auto sessions = manager->GetSessions();
+ std::sort(
+ sessions.begin(), sessions.end(),
+ [](const auto& s1, const auto& s2) { return s1.GetId() < s2.GetId(); });
+
+ ASSERT_EQ(2u, sessions.size());
+
+ ASSERT_EQ(179, sessions[0].GetId());
+ ASSERT_EQ(SessionState::SUCCESS, sessions[0].GetState());
+
+ ASSERT_EQ(1543, sessions[1].GetId());
+ ASSERT_EQ(SessionState::ACTIVATED, sessions[1].GetState());
+}
+
+TEST(ApexSessionManager, GetSessionsInState) {
+ TemporaryDir td;
+ auto manager = ApexSessionManager::Create(std::string(td.path));
+
+ auto session1 = manager->CreateSession(43);
+ ASSERT_RESULT_OK(session1);
+ ASSERT_RESULT_OK(session1->UpdateStateAndCommit(SessionState::ACTIVATED));
+
+ auto session2 = manager->CreateSession(41);
+ ASSERT_RESULT_OK(session2);
+ ASSERT_RESULT_OK(session2->UpdateStateAndCommit(SessionState::SUCCESS));
+
+ auto session3 = manager->CreateSession(23);
+ ASSERT_RESULT_OK(session3);
+ ASSERT_RESULT_OK(session3->UpdateStateAndCommit(SessionState::SUCCESS));
+
+ auto sessions = manager->GetSessionsInState(SessionState::SUCCESS);
+ std::sort(
+ sessions.begin(), sessions.end(),
+ [](const auto& s1, const auto& s2) { return s1.GetId() < s2.GetId(); });
+
+ ASSERT_EQ(2u, sessions.size());
+
+ ASSERT_EQ(23, sessions[0].GetId());
+ ASSERT_EQ(SessionState::SUCCESS, sessions[0].GetState());
+
+ ASSERT_EQ(41, sessions[1].GetId());
+ ASSERT_EQ(SessionState::SUCCESS, sessions[1].GetState());
+}
+
+TEST(ApexSessionManager, MigrateFromOldSessionsDir) {
+ TemporaryDir td;
+ auto old_manager = ApexSessionManager::Create(std::string(td.path));
+
+ auto session1 = old_manager->CreateSession(239);
+ ASSERT_RESULT_OK(session1);
+ ASSERT_RESULT_OK(session1->UpdateStateAndCommit(SessionState::STAGED));
+
+ auto session2 = old_manager->CreateSession(13);
+ ASSERT_RESULT_OK(session2);
+ ASSERT_RESULT_OK(session2->UpdateStateAndCommit(SessionState::SUCCESS));
+
+ auto session3 = old_manager->CreateSession(31);
+ ASSERT_RESULT_OK(session3);
+ ASSERT_RESULT_OK(session3->UpdateStateAndCommit(SessionState::ACTIVATED));
+
+ TemporaryDir td2;
+ auto new_manager = ApexSessionManager::Create(std::string(td2.path));
+
+ ASSERT_RESULT_OK(
+ new_manager->MigrateFromOldSessionsDir(std::string(td.path)));
+
+ auto sessions = new_manager->GetSessions();
+ std::sort(
+ sessions.begin(), sessions.end(),
+ [](const auto& s1, const auto& s2) { return s1.GetId() < s2.GetId(); });
+
+ ASSERT_EQ(3u, sessions.size());
+
+ ASSERT_EQ(13, sessions[0].GetId());
+ ASSERT_EQ(SessionState::SUCCESS, sessions[0].GetState());
+
+ ASSERT_EQ(31, sessions[1].GetId());
+ ASSERT_EQ(SessionState::ACTIVATED, sessions[1].GetState());
+
+ ASSERT_EQ(239, sessions[2].GetId());
+ ASSERT_EQ(SessionState::STAGED, sessions[2].GetState());
+
+ // Check that old manager directory doesn't have anything
+ auto old_sessions = old_manager->GetSessions();
+ ASSERT_TRUE(old_sessions.empty());
+}
+
+TEST(ApexSessionManager, MigrateFromOldSessionsDirSameDir) {
+ TemporaryDir td;
+ auto old_manager = ApexSessionManager::Create(std::string(td.path));
+
+ auto session1 = old_manager->CreateSession(239);
+ ASSERT_RESULT_OK(session1);
+ ASSERT_RESULT_OK(session1->UpdateStateAndCommit(SessionState::STAGED));
+
+ auto session2 = old_manager->CreateSession(13);
+ ASSERT_RESULT_OK(session2);
+ ASSERT_RESULT_OK(session2->UpdateStateAndCommit(SessionState::SUCCESS));
+
+ auto session3 = old_manager->CreateSession(31);
+ ASSERT_RESULT_OK(session3);
+ ASSERT_RESULT_OK(session3->UpdateStateAndCommit(SessionState::ACTIVATED));
+
+ auto new_manager = ApexSessionManager::Create(std::string(td.path));
+
+ ASSERT_RESULT_OK(
+ new_manager->MigrateFromOldSessionsDir(std::string(td.path)));
+
+ auto sessions = new_manager->GetSessions();
+ std::sort(
+ sessions.begin(), sessions.end(),
+ [](const auto& s1, const auto& s2) { return s1.GetId() < s2.GetId(); });
+
+ ASSERT_EQ(3u, sessions.size());
+
+ ASSERT_EQ(13, sessions[0].GetId());
+ ASSERT_EQ(SessionState::SUCCESS, sessions[0].GetState());
+
+ ASSERT_EQ(31, sessions[1].GetId());
+ ASSERT_EQ(SessionState::ACTIVATED, sessions[1].GetState());
+
+ ASSERT_EQ(239, sessions[2].GetId());
+ ASSERT_EQ(SessionState::STAGED, sessions[2].GetState());
+
+ // Directory is the same, so using old_manager should also work.
+ auto old_sessions = old_manager->GetSessions();
+ std::sort(
+ old_sessions.begin(), old_sessions.end(),
+ [](const auto& s1, const auto& s2) { return s1.GetId() < s2.GetId(); });
+
+ ASSERT_EQ(3u, old_sessions.size());
+
+ ASSERT_EQ(13, old_sessions[0].GetId());
+ ASSERT_EQ(SessionState::SUCCESS, old_sessions[0].GetState());
+
+ ASSERT_EQ(31, old_sessions[1].GetId());
+ ASSERT_EQ(SessionState::ACTIVATED, old_sessions[1].GetState());
+
+ ASSERT_EQ(239, old_sessions[2].GetId());
+ ASSERT_EQ(SessionState::STAGED, old_sessions[2].GetState());
+}
+
} // namespace
} // namespace apex
} // namespace android