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