Serialize and deserialize instance database

Serialize the instance database that is basically a set of
instance groups. Load from json to build the same database.
Added unit tests.

Bug: 269235573
Test: atest -c --host cvd_db_test
Change-Id: Ie6aa70ea9d28df61b6681e700ef672099c0404c9
diff --git a/host/commands/cvd/selector/instance_database.h b/host/commands/cvd/selector/instance_database.h
index 00be774..74ad25a 100644
--- a/host/commands/cvd/selector/instance_database.h
+++ b/host/commands/cvd/selector/instance_database.h
@@ -55,6 +55,9 @@
   Result<ConstRef<LocalInstanceGroup>> AddInstanceGroup(
       const AddInstanceGroupParam& param);
 
+  Json::Value Serialize() const;
+  Result<void> LoadFromJson(const Json::Value&);
+
   /**
    * Adds instance to the group.
    *
@@ -79,6 +82,7 @@
    *  RemoveInstanceGroup(group)
    */
   bool RemoveInstanceGroup(const LocalInstanceGroup& group);
+  bool RemoveInstanceGroup(const std::string& group_name);
   void Clear();
 
   Result<Set<ConstRef<LocalInstanceGroup>>> FindGroups(
@@ -138,9 +142,13 @@
 
   Result<LocalInstanceGroup*> FindMutableGroup(const std::string& group_name);
 
+  Result<void> LoadGroupFromJson(const Json::Value& group_json);
+
   std::vector<std::unique_ptr<LocalInstanceGroup>> local_instance_groups_;
   Map<FieldName, ConstGroupHandler> group_handlers_;
   Map<FieldName, ConstInstanceHandler> instance_handlers_;
+
+  static constexpr const char kJsonGroups[] = "Groups";
 };
 
 }  // namespace selector
diff --git a/host/commands/cvd/selector/instance_database_impl.cpp b/host/commands/cvd/selector/instance_database_impl.cpp
index d9f6538..af19585 100644
--- a/host/commands/cvd/selector/instance_database_impl.cpp
+++ b/host/commands/cvd/selector/instance_database_impl.cpp
@@ -133,6 +133,15 @@
   return group_ptr;
 }
 
+bool InstanceDatabase::RemoveInstanceGroup(const std::string& group_name) {
+  auto group_result = FindGroup({kGroupNameField, group_name});
+  if (!group_result.ok()) {
+    return false;
+  }
+  const LocalInstanceGroup& group = group_result->Get();
+  return RemoveInstanceGroup(group);
+}
+
 bool InstanceDatabase::RemoveInstanceGroup(const LocalInstanceGroup& group) {
   auto itr = FindIterator(group);
   // *itr is the reference to the unique pointer object
@@ -247,5 +256,75 @@
       collector, local_instance_groups_);
 }
 
+Json::Value InstanceDatabase::Serialize() const {
+  Json::Value instance_db_json;
+  int i = 0;
+  Json::Value group_array;
+  for (const auto& local_instance_group : local_instance_groups_) {
+    group_array[i] = local_instance_group->Serialize();
+    ++i;
+  }
+  instance_db_json[kJsonGroups] = group_array;
+  return instance_db_json;
+}
+
+Result<void> InstanceDatabase::LoadGroupFromJson(
+    const Json::Value& group_json) {
+  const std::string group_name =
+      group_json[LocalInstanceGroup::kJsonGroupName].asString();
+  const std::string home_dir =
+      group_json[LocalInstanceGroup::kJsonHomeDir].asString();
+  const std::string host_artifacts_path =
+      group_json[LocalInstanceGroup::kJsonHostArtifactPath].asString();
+  const std::string product_out_path =
+      group_json[LocalInstanceGroup::kJsonProductOutPath].asString();
+  const std::string build_id_value =
+      group_json[LocalInstanceGroup::kJsonBuildId].asString();
+  std::optional<std::string> build_id;
+  if (build_id_value != LocalInstanceGroup::kJsonUnknownBuildId) {
+    build_id = build_id_value;
+  }
+  const auto new_group_ref =
+      CF_EXPECT(AddInstanceGroup({.group_name = group_name,
+                                  .home_dir = home_dir,
+                                  .host_artifacts_path = host_artifacts_path,
+                                  .product_out_path = product_out_path}));
+  if (build_id) {
+    CF_EXPECT(SetBuildId(group_name, *build_id));
+  }
+  const Json::Value& instances_json_array =
+      group_json[LocalInstanceGroup::kJsonInstances];
+  for (int i = 0; i < instances_json_array.size(); i++) {
+    const Json::Value& instance_json = instances_json_array[i];
+    const std::string instance_name =
+        instance_json[LocalInstance::kJsonInstanceName].asString();
+    const std::string instance_id =
+        instance_json[LocalInstance::kJsonInstanceId].asString();
+    int id;
+    auto parse_result =
+        android::base::ParseInt(instance_id, std::addressof(id));
+    if (!parse_result) {
+      CF_EXPECT(parse_result == true,
+                "Invalid instance ID in instance json: " << instance_id);
+      RemoveInstanceGroup(new_group_ref.Get());
+    }
+    auto add_instance_result = AddInstance(group_name, id, instance_name);
+    if (!add_instance_result.ok()) {
+      RemoveInstanceGroup(new_group_ref.Get());
+      CF_EXPECT(add_instance_result.ok(), add_instance_result.error().Trace());
+    }
+  }
+  return {};
+}
+
+Result<void> InstanceDatabase::LoadFromJson(const Json::Value& db_json) {
+  const Json::Value& group_array = db_json[kJsonGroups];
+  int n_groups = group_array.size();
+  for (int i = 0; i < n_groups; i++) {
+    CF_EXPECT(LoadGroupFromJson(group_array[i]));
+  }
+  return {};
+}
+
 }  // namespace selector
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/selector/instance_group_record.h b/host/commands/cvd/selector/instance_group_record.h
index eb2402e..a302092 100644
--- a/host/commands/cvd/selector/instance_group_record.h
+++ b/host/commands/cvd/selector/instance_group_record.h
@@ -90,6 +90,7 @@
   Set<std::unique_ptr<LocalInstance>> CopyInstances(
       const Set<std::unique_ptr<LocalInstance>>& src_instances);
   Json::Value Serialize(const std::unique_ptr<LocalInstance>& instance) const;
+
   std::string home_dir_;
   std::string host_artifacts_path_;
   std::string product_out_path_;
diff --git a/host/commands/cvd/selector/instance_record.h b/host/commands/cvd/selector/instance_record.h
index 3a83bf4..f6b86b5 100644
--- a/host/commands/cvd/selector/instance_record.h
+++ b/host/commands/cvd/selector/instance_record.h
@@ -33,6 +33,7 @@
  */
 class LocalInstance {
   friend class LocalInstanceGroup;
+  friend class InstanceDatabase;
 
  public:
   /* names:
diff --git a/host/commands/cvd/unittests/selector/instance_database_helper.h b/host/commands/cvd/unittests/selector/instance_database_helper.h
index 119c6e4..6e7b862 100644
--- a/host/commands/cvd/unittests/selector/instance_database_helper.h
+++ b/host/commands/cvd/unittests/selector/instance_database_helper.h
@@ -99,5 +99,7 @@
   InstanceDatabase db_;
 };
 
+using CvdInstanceDatabaseJsonTest = CvdInstanceDatabaseTest;
+
 }  // namespace selector
 }  // namespace cuttlefish
diff --git a/host/commands/cvd/unittests/selector/instance_database_test.cpp b/host/commands/cvd/unittests/selector/instance_database_test.cpp
index b96ace7..1d128c9 100644
--- a/host/commands/cvd/unittests/selector/instance_database_test.cpp
+++ b/host/commands/cvd/unittests/selector/instance_database_test.cpp
@@ -14,11 +14,13 @@
 // limitations under the License.
 
 #include <algorithm>
+#include <iostream>
 #include <unordered_set>
 
 #include <gtest/gtest.h>
 
 #include "common/libs/utils/files.h"
+#include "common/libs/utils/json.h"
 #include "host/commands/cvd/selector/instance_database.h"
 #include "host/commands/cvd/selector/selector_constants.h"
 #include "host/commands/cvd/unittests/selector/instance_database_helper.h"
@@ -426,5 +428,48 @@
   ASSERT_TRUE(result_tv.ok()) << result_tv.error().Trace();
 }
 
+TEST_F(CvdInstanceDatabaseJsonTest, DumpLoadDumpCompare) {
+  // starting set up
+  if (!SetUpOk() || !AddGroups({"miau"})) {
+    GTEST_SKIP() << Error().msg;
+  }
+  auto& db = GetDb();
+  std::vector<InstanceDatabase::InstanceInfo> miau_group_instance_id_name_pairs{
+      {1, "8"}, {10, "tv_instance"}};
+  auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"});
+  if (!miau_group.ok()) {
+    GTEST_SKIP() << "miau group was not found";
+  }
+  auto add_result = db.AddInstances("miau", miau_group_instance_id_name_pairs);
+  if (!add_result.ok()) {
+    GTEST_SKIP() << "Adding instances are not being tested in this test case.";
+  }
+
+  /*
+   * Dumping to json, clearing up the DB, loading from the json,
+   *
+   */
+  auto serialized_db = db.Serialize();
+  if (!db.RemoveInstanceGroup("miau")) {
+    // not testing RemoveInstanceGroup
+    GTEST_SKIP() << "miau had to be added.";
+  }
+  auto json_parsing = ParseJson(serialized_db.toStyledString());
+  ASSERT_TRUE(json_parsing.ok()) << serialized_db << std::endl
+                                 << " is not a valid json.";
+  auto load_result = db.LoadFromJson(serialized_db);
+  ASSERT_TRUE(load_result.ok()) << load_result.error().Trace();
+  {
+    // re-look up the group and the instances
+    auto miau_group = db.FindGroup({kHomeField, Workspace() + "/" + "miau"});
+    ASSERT_TRUE(miau_group.ok()) << miau_group.error().Trace();
+    auto result_8 = db.FindInstance({kInstanceNameField, "8"});
+    auto result_tv = db.FindInstance({kInstanceNameField, "tv_instance"});
+
+    ASSERT_TRUE(result_8.ok()) << result_8.error().Trace();
+    ASSERT_TRUE(result_tv.ok()) << result_tv.error().Trace();
+  }
+}
+
 }  // namespace selector
 }  // namespace cuttlefish