From 2374bd48ede3ca267a7843d9709986c0c701b5af Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 27 Sep 2021 17:25:13 -0700 Subject: [PATCH] Add a test for bug 198265278. This test uses dependency injection to cause a failure in QuerySnapshotStatus. We expect ProcessUpdateState to return MergeFailed. The test also checks that if the merge is attempted again, it can succeed. Some duplicated code has also been factored out into the test harness. Bug: 198265278 Test: vts_libsnapshot_test gtest Change-Id: I6ccb434afa0e5ebf6781b2cec5277e3b7c210b77 --- .../include/libsnapshot/snapshot.h | 1 + .../include_test/libsnapshot/test_helpers.h | 44 +++++++++ fs_mgr/libsnapshot/snapshot_test.cpp | 98 +++++++++++++------ 3 files changed, 113 insertions(+), 30 deletions(-) diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index 55f4ed7ca..a49b0261c 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -408,6 +408,7 @@ class SnapshotManager final : public ISnapshotManager { FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow); FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow); FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery); + FRIEND_TEST(SnapshotUpdateTest, QueryStatusError); FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow); FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate); friend class SnapshotTest; diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h index 1f57bbc97..c3b40dca4 100644 --- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h +++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h @@ -100,6 +100,9 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { return IDeviceInfo::OpenImageManager("ota/test"); } android::dm::IDeviceMapper& GetDeviceMapper() override { + if (dm_) { + return *dm_; + } return android::dm::DeviceMapper::Instance(); } @@ -111,6 +114,8 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { } void set_recovery(bool value) { recovery_ = value; } void set_first_stage_init(bool value) { first_stage_init_ = value; } + void set_dm(android::dm::IDeviceMapper* dm) { dm_ = dm; } + MergeStatus merge_status() const { return merge_status_; } private: @@ -120,6 +125,45 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { bool recovery_ = false; bool first_stage_init_ = false; std::unordered_set unbootable_slots_; + android::dm::IDeviceMapper* dm_ = nullptr; +}; + +class DeviceMapperWrapper : public android::dm::IDeviceMapper { + using DmDeviceState = android::dm::DmDeviceState; + using DmTable = android::dm::DmTable; + + public: + DeviceMapperWrapper() : impl_(android::dm::DeviceMapper::Instance()) {} + explicit DeviceMapperWrapper(android::dm::IDeviceMapper& impl) : impl_(impl) {} + + virtual bool CreateDevice(const std::string& name, const DmTable& table, std::string* path, + const std::chrono::milliseconds& timeout_ms) override { + return impl_.CreateDevice(name, table, path, timeout_ms); + } + virtual DmDeviceState GetState(const std::string& name) const override { + return impl_.GetState(name); + } + virtual bool LoadTableAndActivate(const std::string& name, const DmTable& table) { + return impl_.LoadTableAndActivate(name, table); + } + virtual bool GetTableInfo(const std::string& name, std::vector* table) { + return impl_.GetTableInfo(name, table); + } + virtual bool GetTableStatus(const std::string& name, std::vector* table) { + return impl_.GetTableStatus(name, table); + } + virtual bool GetDmDevicePathByName(const std::string& name, std::string* path) { + return impl_.GetDmDevicePathByName(name, path); + } + virtual bool GetDeviceString(const std::string& name, std::string* dev) { + return impl_.GetDeviceString(name, dev); + } + virtual bool DeleteDeviceIfExists(const std::string& name) { + return impl_.DeleteDeviceIfExists(name); + } + + private: + android::dm::IDeviceMapper& impl_; }; class SnapshotTestPropertyFetcher : public android::fs_mgr::testing::MockPropertyFetcher { diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp index 43c7fe2b3..d78ba0a83 100644 --- a/fs_mgr/libsnapshot/snapshot_test.cpp +++ b/fs_mgr/libsnapshot/snapshot_test.cpp @@ -56,6 +56,7 @@ namespace snapshot { using android::base::unique_fd; using android::dm::DeviceMapper; using android::dm::DmDeviceState; +using android::dm::IDeviceMapper; using android::fiemap::FiemapStatus; using android::fiemap::IImageManager; using android::fs_mgr::BlockDeviceInfo; @@ -911,6 +912,11 @@ class SnapshotUpdateTest : public SnapshotTest { ASSERT_TRUE(hash.has_value()); hashes_[name] = *hash; } + + // OTA client blindly unmaps all partitions that are possibly mapped. + for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { + ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); + } } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); @@ -925,6 +931,14 @@ class SnapshotUpdateTest : public SnapshotTest { MountMetadata(); for (const auto& suffix : {"_a", "_b"}) { test_device->set_slot_suffix(suffix); + + // Cheat our way out of merge failed states. + if (sm->ProcessUpdateState() == UpdateState::MergeFailed) { + ASSERT_TRUE(AcquireLock()); + ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::None)); + lock_ = {}; + } + EXPECT_TRUE(sm->CancelUpdate()) << suffix; } EXPECT_TRUE(UnmapAll()); @@ -1097,11 +1111,6 @@ class SnapshotUpdateTest : public SnapshotTest { // Also test UnmapUpdateSnapshot unmaps everything. // Also test first stage mount and merge after this. TEST_F(SnapshotUpdateTest, FullUpdateFlow) { - // OTA client blindly unmaps all partitions that are possibly mapped. - for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { - ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); - } - // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs // fit in super, but not |prd|. constexpr uint64_t partition_size = 3788_KiB; @@ -1189,11 +1198,6 @@ TEST_F(SnapshotUpdateTest, DuplicateOps) { GTEST_SKIP() << "Compression-only test"; } - // OTA client blindly unmaps all partitions that are possibly mapped. - for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { - ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); - } - // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); @@ -1239,11 +1243,6 @@ TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) { GTEST_SKIP() << "Skipping Virtual A/B Compression test"; } - // OTA client blindly unmaps all partitions that are possibly mapped. - for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { - ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); - } - auto old_sys_size = GetSize(sys_); auto old_prd_size = GetSize(prd_); @@ -1630,11 +1629,6 @@ TEST_F(SnapshotUpdateTest, MergeCannotRemoveCow) { ASSERT_NE(nullptr, metadata); ASSERT_TRUE(UpdatePartitionTable(*opener_, "super", *metadata.get(), 0)); - // OTA client blindly unmaps all partitions that are possibly mapped. - for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { - ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); - } - // Add operations for sys. The whole device is written. AddOperation(sys_); @@ -2074,11 +2068,6 @@ TEST_F(SnapshotUpdateTest, LowSpace) { } TEST_F(SnapshotUpdateTest, AddPartition) { - // OTA client blindly unmaps all partitions that are possibly mapped. - for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { - ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); - } - group_->add_partition_names("dlkm"); auto dlkm = manifest_.add_partitions(); @@ -2249,6 +2238,60 @@ TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) { ASSERT_TRUE(sm->BeginUpdate()); } +TEST_F(SnapshotUpdateTest, QueryStatusError) { + // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs + // fit in super, but not |prd|. + constexpr uint64_t partition_size = 3788_KiB; + SetSize(sys_, partition_size); + + AddOperationForPartitions({sys_}); + + // Execute the update. + ASSERT_TRUE(sm->BeginUpdate()); + ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); + ASSERT_TRUE(WriteSnapshotAndHash("sys_b")); + ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); + ASSERT_TRUE(UnmapAll()); + + class DmStatusFailure final : public DeviceMapperWrapper { + public: + bool GetTableStatus(const std::string& name, std::vector* table) override { + if (!DeviceMapperWrapper::GetTableStatus(name, table)) { + return false; + } + if (name == "sys_b" && !table->empty()) { + auto& info = table->at(0); + if (DeviceMapper::GetTargetType(info.spec) == "snapshot-merge") { + info.data = "Merge failed"; + } + } + return true; + } + }; + DmStatusFailure wrapper; + + // After reboot, init does first stage mount. + auto info = new TestDeviceInfo(fake_super, "_b"); + info->set_dm(&wrapper); + + auto init = NewManagerForFirstStageMount(info); + ASSERT_NE(init, nullptr); + + ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + + // Initiate the merge and wait for it to be completed. + ASSERT_TRUE(init->InitiateMerge()); + ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState()); + + // Simulate a reboot that tries the merge again, with the non-failing dm. + ASSERT_TRUE(UnmapAll()); + init = NewManagerForFirstStageMount("_b"); + ASSERT_NE(init, nullptr); + ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); + ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState()); +} + class FlashAfterUpdateTest : public SnapshotUpdateTest, public WithParamInterface> { public: @@ -2265,11 +2308,6 @@ class FlashAfterUpdateTest : public SnapshotUpdateTest, }; TEST_P(FlashAfterUpdateTest, FlashSlotAfterUpdate) { - // OTA client blindly unmaps all partitions that are possibly mapped. - for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { - ASSERT_TRUE(sm->UnmapUpdateSnapshot(name)); - } - // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));