blob: 00e11a4eee29709d3d28838cdedc286a57ea2b6c [file] [log] [blame]
// 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