| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/sys/appmgr/storage_watchdog.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/inspect/cpp/vmo/types.h> |
| #include <lib/sync/completion.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include <src/lib/files/directory.h> |
| #include <src/lib/files/file.h> |
| #include <src/lib/files/path.h> |
| |
| #include "gtest/gtest.h" |
| #include "src/lib/testing/loop_fixture/real_loop_fixture.h" |
| #include "src/storage/memfs/scoped_memfs.h" |
| |
| namespace { |
| |
| namespace fio = fuchsia_io; |
| |
| const char* kTmpData = "abcdefghijklmnopqrstuvwxyz1234567890"; |
| |
| class StorageWatchdogTest : public ::testing::Test { |
| public: |
| StorageWatchdogTest() : loop_(async::Loop(&kAsyncLoopConfigAttachToCurrentThread)) {} |
| |
| void SetUp() override { |
| testing::Test::SetUp(); |
| |
| ASSERT_EQ(ZX_OK, loop_.StartThread()); |
| |
| zx::status<ScopedMemfs> memfs = |
| ScopedMemfs::CreateMountedAt(loop_.dispatcher(), "/hippo_storage"); |
| ASSERT_TRUE(memfs.is_ok()); |
| memfs_ = std::make_unique<ScopedMemfs>(std::move(*memfs)); |
| } |
| |
| void TearDown() override { |
| memfs_->set_cleanup_timeout(zx::sec(5)); |
| memfs_.reset(); |
| } |
| |
| private: |
| async::Loop loop_; |
| std::unique_ptr<ScopedMemfs> memfs_; |
| }; |
| |
| class TestStorageWatchdog : public StorageWatchdog { |
| public: |
| TestStorageWatchdog(std::string path_to_watch, std::string path_to_clean) |
| : StorageWatchdog(inspect::Node(), path_to_watch, path_to_clean) {} |
| |
| zx_status_t GetFilesystemInfo(zx_handle_t directory, fio::wire::FilesystemInfo* out_info) { |
| if (directory == ZX_HANDLE_INVALID) { |
| return ZX_ERR_BAD_HANDLE; |
| } |
| *out_info = info; |
| return ZX_OK; |
| } |
| |
| fio::wire::FilesystemInfo info = {}; |
| }; |
| |
| TEST_F(StorageWatchdogTest, Basic) { |
| const std::string kRootPath = "/hippo_storage/cache"; |
| const std::string kRealmPath = files::JoinPath(kRootPath, "r/sys"); |
| const std::string kNestedRealmPath = files::JoinPath(kRootPath, "r/sys/r/test"); |
| const std::string kV1Path = files::JoinPath(kRealmPath, "fuchsia.com:cobalt:0#meta:cobalt.cmx"); |
| const std::string kV1NestedPath = |
| files::JoinPath(kNestedRealmPath, "fuchsia.com:cobalt-unittest:0#meta:cobalt-unittest.cmx"); |
| const std::string kV2IdPath = files::JoinPath( |
| kRootPath, "e5ef2bbe9dd2b7cee87beac5e06cece13fe6f9c154b1f00abec155e6c6c9fa62"); |
| const std::string kV2MonikerBasePath = files::JoinPath(kRootPath, "network:0"); |
| const std::string kV2MonikerPath = files::JoinPath(kV2MonikerBasePath, "data"); |
| const std::string kV2NestedMonikerBasePath = |
| files::JoinPath(kRootPath, "network:0/children/netstack:0"); |
| const std::string kV2NestedMonikerPath = files::JoinPath(kV2NestedMonikerBasePath, "data"); |
| |
| TestStorageWatchdog watchdog = TestStorageWatchdog("/hippo_storage", "/hippo_storage/cache"); |
| watchdog.info.used_bytes = 0; |
| watchdog.info.total_bytes = 20 * 1024; |
| auto usage = watchdog.GetStorageUsage(); |
| EXPECT_LE(usage.percent(), StorageWatchdog::kCachePurgeThresholdPct); |
| |
| for (size_t i = 0; i < 10; ++i) { |
| auto filename = std::to_string(i); |
| for (const std::string& path : |
| {kV1Path, kV1NestedPath, kV2IdPath, kV2MonikerPath, kV2NestedMonikerPath}) { |
| ASSERT_TRUE(files::CreateDirectory(path)); |
| ASSERT_TRUE(files::WriteFile(files::JoinPath(path, filename), kTmpData, strlen(kTmpData))); |
| } |
| } |
| |
| watchdog.info.used_bytes = watchdog.info.total_bytes - 128; |
| |
| // Confirm that storage pressure is high, clear the cache, check that things |
| // were actually deleted (but the directories themselves were preserved). |
| usage = watchdog.GetStorageUsage(); |
| EXPECT_GT(usage.percent(), StorageWatchdog::kCachePurgeThresholdPct); |
| watchdog.PurgeCache(); |
| |
| // For each case, check: |
| // - The contents of the storage dir are cleared. |
| // - The storage dir itself is not deleted. |
| |
| // V1 |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kV1Path, &files)); |
| EXPECT_EQ(1ul, files.size()); |
| EXPECT_EQ(files[0], "."); |
| } |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kRealmPath, &files)); |
| EXPECT_EQ(3ul, files.size()); |
| std::sort(files.begin(), files.end()); |
| EXPECT_EQ(files[0], "."); |
| EXPECT_EQ(files[1], "fuchsia.com:cobalt:0#meta:cobalt.cmx"); |
| EXPECT_EQ(files[2], "r"); |
| } |
| |
| // V1 nested |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kV1NestedPath, &files)); |
| EXPECT_EQ(1ul, files.size()); |
| EXPECT_EQ(files[0], "."); |
| } |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kNestedRealmPath, &files)); |
| EXPECT_EQ(2ul, files.size()); |
| std::sort(files.begin(), files.end()); |
| EXPECT_EQ(files[0], "."); |
| EXPECT_EQ(files[1], "fuchsia.com:cobalt-unittest:0#meta:cobalt-unittest.cmx"); |
| } |
| |
| // V2 instance id |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kV2IdPath, &files)); |
| EXPECT_EQ(1ul, files.size()); |
| EXPECT_EQ(files[0], "."); |
| } |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kRootPath, &files)); |
| EXPECT_EQ(4ul, files.size()); |
| std::sort(files.begin(), files.end()); |
| EXPECT_EQ(files[0], "."); |
| EXPECT_EQ(files[1], "e5ef2bbe9dd2b7cee87beac5e06cece13fe6f9c154b1f00abec155e6c6c9fa62"); |
| EXPECT_EQ(files[2], "network:0"); |
| EXPECT_EQ(files[3], "r"); |
| } |
| |
| // V2 moniker |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kV2MonikerPath, &files)); |
| EXPECT_EQ(1ul, files.size()); |
| EXPECT_EQ(files[0], "."); |
| } |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kV2MonikerBasePath, &files)); |
| EXPECT_EQ(3ul, files.size()); |
| std::sort(files.begin(), files.end()); |
| EXPECT_EQ(files[0], "."); |
| EXPECT_EQ(files[1], "children"); |
| EXPECT_EQ(files[2], "data"); |
| } |
| |
| // V2 nested moniker |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kV2NestedMonikerPath, &files)); |
| EXPECT_EQ(1ul, files.size()); |
| EXPECT_EQ(files[0], "."); |
| } |
| { |
| std::vector<std::string> files = {}; |
| EXPECT_TRUE(files::ReadDirContents(kV2NestedMonikerBasePath, &files)); |
| EXPECT_EQ(2ul, files.size()); |
| std::sort(files.begin(), files.end()); |
| EXPECT_EQ(files[0], "."); |
| EXPECT_EQ(files[1], "data"); |
| } |
| } |
| |
| } // namespace |