blob: 9701206cc14b8b253f6cfef753d84b100d417e53 [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 <fidl/fuchsia.driver.test/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/driver_test_realm/realm_builder/cpp/lib.h>
#include <lib/fdio/fd.h>
#include <vector>
#include <bind/fuchsia/platform/cpp/bind.h>
#include <zxtest/zxtest.h>
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/fvm_test_instance.h"
#include "src/storage/lib/block_client/cpp/remote_block_device.h"
namespace fvm {
namespace {
// Shared constants for all resize tests.
constexpr uint64_t kTestBlockSize = 512;
constexpr uint64_t kSliceSize = 1 << 20;
constexpr uint64_t kDataSizeInBlocks = 10;
constexpr uint64_t kDataSize = kTestBlockSize * kDataSizeInBlocks;
constexpr char kPartitionName[] = "partition-name";
constexpr uuid::Uuid kPartitionUniqueGuid = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
constexpr uuid::Uuid kPartitionTypeGuid = {0xAA, 0xFF, 0xBB, 0x00, 0x33, 0x44, 0x88, 0x99,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
constexpr uint64_t kPartitionSliceCount = 1;
void CheckWrite(fidl::UnownedClientEnd<fuchsia_hardware_block::Block> device, size_t off,
std::span<uint8_t> buf) {
for (unsigned char& i : buf) {
i = static_cast<uint8_t>(rand());
}
ASSERT_OK(block_client::SingleWriteBytes(device, buf.data(), buf.size(), off));
}
void CheckRead(fidl::UnownedClientEnd<fuchsia_hardware_block::Block> device, size_t off,
std::span<const uint8_t> in) {
std::vector<uint8_t> out(in.size());
ASSERT_OK(block_client::SingleReadBytes(device, out.data(), out.size(), off));
ASSERT_EQ(memcmp(in.data(), out.data(), in.size()), 0);
}
void CheckWriteReadBlock(fidl::UnownedClientEnd<fuchsia_hardware_block::Block> device, size_t block,
size_t count) {
const fidl::WireResult result = fidl::WireCall(device)->GetInfo();
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
const fuchsia_hardware_block::wire::BlockInfo& block_info = response.value()->info;
size_t len = block_info.block_size * count;
size_t off = block_info.block_size * block;
std::vector<uint8_t> in(len);
ASSERT_NO_FATAL_FAILURE(CheckWrite(device, off, in));
ASSERT_NO_FATAL_FAILURE(CheckRead(device, off, in));
}
class FvmResizeTest : public zxtest::TestWithParam<FvmImplementation> {
protected:
void SetUp() override {
instance_ = CreateFvmInstance(GetParam());
instance_->SetUp();
}
void TearDown() override { instance_->TearDown(); }
void RestartFvmWithNewDiskSize(uint64_t block_count) {
instance_->RestartFvmWithNewDiskSize(kTestBlockSize, block_count);
}
zx::result<std::unique_ptr<fvm::BlockConnector>> AllocatePartition(
const fvm::AllocatePartitionRequest& request) {
return instance_->AllocatePartition(request);
}
zx::result<std::unique_ptr<fvm::BlockConnector>> OpenPartition(std::string_view label) {
return instance_->OpenPartition(label);
}
fuchsia_hardware_block_volume::wire::VolumeManagerInfo GetFvmInfo() {
return instance_->GetFvmInfo();
}
void CreateGrowableFvm(uint64_t initial_block_count, uint64_t max_block_count) {
instance_->CreateRamdisk(kTestBlockSize, initial_block_count);
ASSERT_OK(fs_management::FvmInitPreallocated(instance_->GetRamdiskPartition(),
initial_block_count * kTestBlockSize,
max_block_count * kTestBlockSize, kSliceSize));
instance_->StartFvm();
}
static void ExtendVolume(fidl::UnownedClientEnd<fuchsia_hardware_block_volume::Volume> volume,
uint64_t start_slice, uint64_t slice_count) {
const fidl::WireResult result = fidl::WireCall(volume)->Extend(start_slice, slice_count);
ASSERT_OK(result.status());
const fidl::WireResponse response = result.value();
ASSERT_OK(response.status);
}
private:
std::unique_ptr<fvm::FvmInstance> instance_;
};
TEST_P(FvmResizeTest, PreallocatedMetadataGrowsCorrectly) {
constexpr uint64_t kInitialBlockCount = (50 * kSliceSize) / kTestBlockSize;
constexpr uint64_t kMaxBlockCount = (4 << 10) * kSliceSize / kTestBlockSize;
Header expected =
Header::FromDiskSize(fvm::kMaxUsablePartitions, kMaxBlockCount * kTestBlockSize, kSliceSize);
ASSERT_NO_FATAL_FAILURE(CreateGrowableFvm(kInitialBlockCount, kMaxBlockCount));
zx::result vp = AllocatePartition({.slice_count = kPartitionSliceCount,
.type = kPartitionTypeGuid,
.guid = kPartitionUniqueGuid,
.name = kPartitionName});
ASSERT_OK(vp);
{
auto info = GetFvmInfo();
ASSERT_EQ(kSliceSize, info.slice_size);
ASSERT_EQ(kPartitionSliceCount, info.assigned_slice_count);
// The disk is smaller than our eventual target so it reports less possible slices.
ASSERT_LT(info.slice_count, expected.pslice_count);
}
std::vector<uint8_t> buf(kDataSize);
ASSERT_NO_FATAL_FAILURE(CheckWrite(vp->as_block(), 0, buf));
vp = {};
ASSERT_NO_FATAL_FAILURE(RestartFvmWithNewDiskSize(kMaxBlockCount));
{
auto info = GetFvmInfo();
// Other info should be the same
ASSERT_EQ(kSliceSize, info.slice_size);
ASSERT_EQ(kPartitionSliceCount, info.assigned_slice_count);
// The new total possible slice count should be our expected slice count.
ASSERT_EQ(info.slice_count, expected.pslice_count);
}
vp = OpenPartition(kPartitionName);
ASSERT_OK(vp);
// The original data we wrote is still there
ASSERT_NO_FATAL_FAILURE(CheckRead(vp->as_block(), 0, buf));
// Now we can extend to fill the rest of the available space, beyond our initial size.
ASSERT_NO_FATAL_FAILURE(ExtendVolume(vp->as_volume(), kPartitionSliceCount,
expected.pslice_count - kPartitionSliceCount));
size_t block_offset = (expected.pslice_count - 1) * kSliceSize / kBlockSize;
ASSERT_NO_FATAL_FAILURE(CheckWriteReadBlock(vp->as_block(), block_offset, kDataSizeInBlocks));
}
TEST_P(FvmResizeTest, PreallocatedMetadataGrowsAsMuchAsPossible) {
constexpr uint64_t kInitialBlockCount = (50 * kSliceSize) / kTestBlockSize;
constexpr uint64_t kMaxBlockCount = (1 << 10) * kSliceSize / kTestBlockSize;
// Compute the expected header information. This is the header computed for the original slice
// size, expanded by as many slices as possible.
Header expected =
Header::FromDiskSize(kMaxUsablePartitions, kMaxBlockCount * kTestBlockSize, kSliceSize);
expected.SetSliceCount(expected.GetAllocationTableAllocatedEntryCount());
ASSERT_NO_FATAL_FAILURE(CreateGrowableFvm(kInitialBlockCount, kMaxBlockCount));
zx::result vp = AllocatePartition({.slice_count = kPartitionSliceCount,
.type = kPartitionTypeGuid,
.guid = kPartitionUniqueGuid,
.name = kPartitionName});
ASSERT_OK(vp);
{
auto info = GetFvmInfo();
ASSERT_EQ(kSliceSize, info.slice_size);
ASSERT_EQ(kPartitionSliceCount, info.assigned_slice_count);
// The disk is smaller than our eventual target so it reports less possible slices.
ASSERT_LT(info.slice_count, expected.pslice_count);
}
std::vector<uint8_t> buf(kDataSize);
ASSERT_NO_FATAL_FAILURE(CheckWrite(vp->as_block(), 0, buf));
vp = {};
// This defines a ramdisk size much larger than our header could handle so the resize will max
// out the slices in the header.
constexpr uint64_t kLargeBlockCount = (2 << 10) * kSliceSize / kTestBlockSize;
ASSERT_NO_FATAL_FAILURE(RestartFvmWithNewDiskSize(kLargeBlockCount));
{
auto info = GetFvmInfo();
// Other info should be the same
ASSERT_EQ(kSliceSize, info.slice_size);
ASSERT_EQ(kPartitionSliceCount, info.assigned_slice_count);
// The new total possible slice count should be our expected slice count, despite the larger
// disk space available.
ASSERT_EQ(info.slice_count, expected.pslice_count);
// In fact, it should be the maximum number of slices possible for the size of the allocation
// table, which is also reported.
ASSERT_EQ(info.slice_count, info.maximum_slice_count);
}
vp = OpenPartition(kPartitionName);
ASSERT_OK(vp);
// The original data we wrote is still there
ASSERT_NO_FATAL_FAILURE(CheckRead(vp->as_block(), 0, buf));
// Now we can extend to fill the rest of the available space, beyond our initial size.
ASSERT_NO_FATAL_FAILURE(ExtendVolume(vp->as_volume(), kPartitionSliceCount,
expected.pslice_count - kPartitionSliceCount));
size_t block_offset = (expected.pslice_count - 1) * kSliceSize / kBlockSize;
ASSERT_NO_FATAL_FAILURE(CheckWriteReadBlock(vp->as_block(), block_offset, kDataSizeInBlocks));
}
TEST_P(FvmResizeTest, PreallocatedMetadataRemainsValidInPartialGrowths) {
constexpr uint64_t kInitialBlockCount = (50 * kSliceSize) / kTestBlockSize;
constexpr uint64_t kMidBlockCount = (4 << 10) * kSliceSize / kTestBlockSize;
constexpr uint64_t kMaxBlockCount = (8 << 10) * kSliceSize / kTestBlockSize;
Header expected_mid =
Header::FromDiskSize(kMaxUsablePartitions, kMidBlockCount * kTestBlockSize, kSliceSize);
Header expected_max =
Header::FromDiskSize(kMaxUsablePartitions, kMaxBlockCount * kTestBlockSize, kSliceSize);
ASSERT_NO_FATAL_FAILURE(CreateGrowableFvm(kInitialBlockCount, kMaxBlockCount));
zx::result vp = AllocatePartition({.slice_count = kPartitionSliceCount,
.type = kPartitionTypeGuid,
.guid = kPartitionUniqueGuid,
.name = kPartitionName});
ASSERT_OK(vp);
{
auto info = GetFvmInfo();
ASSERT_EQ(kSliceSize, info.slice_size);
ASSERT_EQ(kPartitionSliceCount, info.assigned_slice_count);
// The disk is smaller than our eventual target so it reports less possible slices.
ASSERT_LT(info.slice_count, expected_mid.pslice_count);
ASSERT_LT(info.slice_count, expected_max.pslice_count);
}
std::vector<uint8_t> buf(kDataSize);
ASSERT_NO_FATAL_FAILURE(CheckWrite(vp->as_block(), 0, buf));
vp = {};
ASSERT_NO_FATAL_FAILURE(RestartFvmWithNewDiskSize(kMidBlockCount));
{
auto info = GetFvmInfo();
ASSERT_EQ(kSliceSize, info.slice_size);
ASSERT_EQ(kPartitionSliceCount, info.assigned_slice_count);
// The disk is now the midpoint size, so it has the midpoint slice amount.
ASSERT_EQ(info.slice_count, expected_mid.pslice_count);
ASSERT_LT(info.slice_count, expected_max.pslice_count);
}
vp = OpenPartition(kPartitionName);
ASSERT_OK(vp);
// The original data we wrote is still there.
ASSERT_NO_FATAL_FAILURE(CheckRead(vp->as_block(), 0, buf));
vp = {};
ASSERT_NO_FATAL_FAILURE(RestartFvmWithNewDiskSize(kMaxBlockCount));
{
auto info = GetFvmInfo();
ASSERT_EQ(kSliceSize, info.slice_size);
ASSERT_EQ(kPartitionSliceCount, info.assigned_slice_count);
// The disk is now the maximum size, so it has the maximum slice amount.
ASSERT_GT(info.slice_count, expected_mid.pslice_count);
ASSERT_EQ(info.slice_count, expected_max.pslice_count);
}
vp = OpenPartition(kPartitionName);
ASSERT_OK(vp);
// The original data we wrote is still there.
ASSERT_NO_FATAL_FAILURE(CheckRead(vp->as_block(), 0, buf));
// Now we can extend to fill the rest of the available space, beyond our initial size.
ASSERT_NO_FATAL_FAILURE(ExtendVolume(vp->as_volume(), kPartitionSliceCount,
expected_max.pslice_count - kPartitionSliceCount));
size_t block_offset = (expected_max.pslice_count - 1) * kSliceSize / kBlockSize;
ASSERT_NO_FATAL_FAILURE(CheckWriteReadBlock(vp->as_block(), block_offset, kDataSizeInBlocks));
}
INSTANTIATE_TEST_SUITE_P(FvmResizeTest, FvmResizeTest,
zxtest::Values(fvm::FvmImplementation::kDriver,
fvm::FvmImplementation::kComponent),
[](const auto& info) {
switch (info.param) {
case fvm::FvmImplementation::kDriver:
return "Driver";
case fvm::FvmImplementation::kComponent:
return "Component";
}
});
} // namespace
} // namespace fvm