Merge "libsnapshot: Fix flaky low-space tests."

This commit is contained in:
David Anderson 2023-05-10 18:29:56 +00:00 committed by Gerrit Code Review
commit 25613816d8
4 changed files with 56 additions and 117 deletions

View file

@ -214,29 +214,6 @@ void SetSize(PartitionUpdate* partition_update, uint64_t size);
// Get partition size from update package metadata.
uint64_t GetSize(PartitionUpdate* partition_update);
// Util class for test cases on low space scenario. These tests assumes image manager
// uses /data as backup device.
class LowSpaceUserdata {
public:
// Set the maximum free space allowed for this test. If /userdata has more space than the given
// number, a file is allocated to consume space.
AssertionResult Init(uint64_t max_free_space);
uint64_t free_space() const;
uint64_t available_space() const;
uint64_t bsize() const;
private:
AssertionResult ReadUserdataStats();
static constexpr const char* kUserDataDevice = "/data";
std::unique_ptr<TemporaryFile> big_file_;
bool initialized_ = false;
uint64_t free_space_ = 0;
uint64_t available_space_ = 0;
uint64_t bsize_ = 0;
};
bool IsVirtualAbEnabled();
#define SKIP_IF_NON_VIRTUAL_AB() \

View file

@ -3133,6 +3133,7 @@ static Return AddRequiredSpace(Return orig,
for (auto&& [name, status] : all_snapshot_status) {
sum += status.cow_file_size();
}
LOG(INFO) << "Calculated needed COW space: " << sum << " bytes";
return Return::NoSpace(sum);
}
@ -3279,7 +3280,10 @@ Return SnapshotManager::CreateUpdateSnapshots(const DeltaArchiveManifest& manife
auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
&all_snapshot_status);
if (!ret.is_ok()) return ret;
if (!ret.is_ok()) {
LOG(ERROR) << "CreateUpdateSnapshotsInternal failed: " << ret.string();
return ret;
}
auto exported_target_metadata = target_metadata->Export();
if (exported_target_metadata == nullptr) {
@ -3496,7 +3500,10 @@ Return SnapshotManager::CreateUpdateSnapshotsInternal(
// Create the backing COW image if necessary.
if (snapshot_status.cow_file_size() > 0) {
auto ret = CreateCowImage(lock, name);
if (!ret.is_ok()) return AddRequiredSpace(ret, *all_snapshot_status);
if (!ret.is_ok()) {
LOG(ERROR) << "CreateCowImage failed: " << ret.string();
return AddRequiredSpace(ret, *all_snapshot_status);
}
}
LOG(INFO) << "Successfully created snapshot for " << name;

View file

@ -19,6 +19,7 @@
#include <signal.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <chrono>
@ -2371,20 +2372,44 @@ TEST_F(SnapshotUpdateTest, Overflow) {
<< "FinishedSnapshotWrites should detect overflow of CoW device.";
}
TEST_F(SnapshotUpdateTest, LowSpace) {
static constexpr auto kMaxFree = 10_MiB;
auto userdata = std::make_unique<LowSpaceUserdata>();
ASSERT_TRUE(userdata->Init(kMaxFree));
// Get max file size and free space.
std::pair<uint64_t, uint64_t> GetBigFileLimit() {
struct statvfs fs;
if (statvfs("/data", &fs) < 0) {
PLOG(ERROR) << "statfs failed";
return {0, 0};
}
auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1);
auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize;
LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free;
return {fs_limit, fs_free};
}
TEST_F(SnapshotUpdateTest, LowSpace) {
// To make the low space test more reliable, we force a large cow estimate.
// However legacy VAB ignores the COW estimate and uses InstallOperations
// to compute the exact size required for dm-snapshot. It's difficult to
// make this work reliably (we'd need to somehow fake an extremely large
// super partition, and we don't have that level of dependency injection).
//
// For now, just skip this test on legacy VAB.
if (!snapuserd_required_) {
GTEST_SKIP() << "Skipping test on legacy VAB";
}
auto fs = GetBigFileLimit();
ASSERT_NE(fs.first, 0);
// Grow all partitions to 10_MiB, total 30_MiB. This requires 30 MiB of CoW space. After
// using the empty space in super (< 1 MiB), it uses 30 MiB of /userdata space.
constexpr uint64_t partition_size = 10_MiB;
SetSize(sys_, partition_size);
SetSize(vnd_, partition_size);
SetSize(prd_, partition_size);
sys_->set_estimate_cow_size(partition_size);
vnd_->set_estimate_cow_size(partition_size);
prd_->set_estimate_cow_size(partition_size);
sys_->set_estimate_cow_size(fs.first);
vnd_->set_estimate_cow_size(fs.first);
prd_->set_estimate_cow_size(fs.first);
AddOperationForPartitions();
@ -2393,8 +2418,12 @@ TEST_F(SnapshotUpdateTest, LowSpace) {
auto res = sm->CreateUpdateSnapshots(manifest_);
ASSERT_FALSE(res);
ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code());
ASSERT_GE(res.required_size(), 14_MiB);
ASSERT_LT(res.required_size(), 40_MiB);
// It's hard to predict exactly how much free space is needed, since /data
// is writable and the test is not the only process running. Divide by two
// as a rough lower bound, and adjust this in the future as necessary.
auto expected_delta = fs.first - fs.second;
ASSERT_GE(res.required_size(), expected_delta / 2);
}
TEST_F(SnapshotUpdateTest, AddPartition) {
@ -2776,6 +2805,7 @@ class ImageManagerTest : public SnapshotTest {
void TearDown() override {
RETURN_IF_NON_VIRTUAL_AB();
CleanUp();
SnapshotTest::TearDown();
}
void CleanUp() {
if (!image_manager_) {
@ -2789,26 +2819,13 @@ class ImageManagerTest : public SnapshotTest {
};
TEST_F(ImageManagerTest, CreateImageNoSpace) {
bool at_least_one_failure = false;
for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) {
auto userdata = std::make_unique<LowSpaceUserdata>();
ASSERT_TRUE(userdata->Init(size));
auto fs = GetBigFileLimit();
ASSERT_NE(fs.first, 0);
uint64_t to_allocate = userdata->free_space() + userdata->bsize();
auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
IImageManager::CREATE_IMAGE_DEFAULT);
if (!res) {
at_least_one_failure = true;
} else {
ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
}
CleanUp();
}
ASSERT_TRUE(at_least_one_failure)
<< "We should have failed to allocate at least one over-sized image";
auto res = image_manager_->CreateBackingImage(kImageName, fs.first,
IImageManager::CREATE_IMAGE_DEFAULT);
ASSERT_FALSE(res);
ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
}
bool Mkdir(const std::string& path) {

View file

@ -214,68 +214,6 @@ uint64_t GetSize(PartitionUpdate* partition_update) {
return partition_update->mutable_new_partition_info()->size();
}
AssertionResult LowSpaceUserdata::Init(uint64_t max_free_space) {
auto res = ReadUserdataStats();
if (!res) return res;
// Try to fill up the disk as much as possible until free_space_ <= max_free_space.
big_file_ = std::make_unique<TemporaryFile>();
if (big_file_->fd == -1) {
return AssertionFailure() << strerror(errno);
}
if (!android::base::StartsWith(big_file_->path, kUserDataDevice)) {
return AssertionFailure() << "Temp file allocated to " << big_file_->path << ", not in "
<< kUserDataDevice;
}
uint64_t next_consume = std::min(std::max(available_space_, max_free_space) - max_free_space,
(uint64_t)std::numeric_limits<off_t>::max());
off_t allocated = 0;
while (next_consume > 0 && free_space_ > max_free_space) {
int status = fallocate(big_file_->fd, 0, allocated, next_consume);
if (status == -1 && errno == ENOSPC) {
next_consume /= 2;
continue;
}
if (status == -1) {
return AssertionFailure() << strerror(errno);
}
allocated += next_consume;
res = ReadUserdataStats();
if (!res) return res;
}
LOG(INFO) << allocated << " bytes allocated to " << big_file_->path;
initialized_ = true;
return AssertionSuccess();
}
AssertionResult LowSpaceUserdata::ReadUserdataStats() {
struct statvfs buf;
if (statvfs(kUserDataDevice, &buf) == -1) {
return AssertionFailure() << strerror(errno);
}
bsize_ = buf.f_bsize;
free_space_ = bsize_ * buf.f_bfree;
available_space_ = bsize_ * buf.f_bavail;
return AssertionSuccess();
}
uint64_t LowSpaceUserdata::free_space() const {
CHECK(initialized_);
return free_space_;
}
uint64_t LowSpaceUserdata::available_space() const {
CHECK(initialized_);
return available_space_;
}
uint64_t LowSpaceUserdata::bsize() const {
CHECK(initialized_);
return bsize_;
}
bool IsVirtualAbEnabled() {
return android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
}