blob: e14475a046b46d5e9617a61b2a2bbb3f201696d6 [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.hardware.block.volume/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/driver-integration-test/fixture.h>
#include <lib/fdio/cpp/caller.h>
#include <sys/types.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#include <zxtest/zxtest.h>
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/test_support.h"
namespace fvm {
namespace {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kSliceSize = 1 << 20;
using driver_integration_test::IsolatedDevmgr;
// using Partition = fuchsia_hardware_block_partition::Partition;
using Volume = fuchsia_hardware_block_volume::Volume;
using VolumeManager = fuchsia_hardware_block_volume::VolumeManager;
class FvmVolumeManagerApiTest : public zxtest::Test {
public:
static void SetUpTestSuite() {
IsolatedDevmgr::Args args;
args.disable_block_watcher = true;
devmgr_ = std::make_unique<IsolatedDevmgr>();
ASSERT_OK(IsolatedDevmgr::Create(&args, devmgr_.get()));
}
static void TearDownTestSuite() { devmgr_.reset(); }
protected:
static std::unique_ptr<IsolatedDevmgr> devmgr_;
};
std::unique_ptr<IsolatedDevmgr> FvmVolumeManagerApiTest::devmgr_ = nullptr;
TEST_F(FvmVolumeManagerApiTest, GetInfoNonPreallocatedMetadata) {
constexpr uint64_t kBlockCount = (50 * kSliceSize) / kBlockSize;
std::unique_ptr<RamdiskRef> ramdisk =
RamdiskRef::Create(devmgr_->devfs_root(), kBlockSize, kBlockCount);
ASSERT_TRUE(ramdisk);
std::unique_ptr<FvmAdapter> fvm =
FvmAdapter::Create(devmgr_->devfs_root(), kBlockSize, kBlockCount, kSliceSize, ramdisk.get());
ASSERT_TRUE(fvm);
const fvm::Header kExpectedFormat =
fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, kBlockSize * kBlockCount, kSliceSize);
fdio_cpp::UnownedFdioCaller caller(fvm->device()->devfs_root_fd());
zx::result channel =
component::ConnectAt<VolumeManager>(caller.directory(), fvm->device()->path());
ASSERT_OK(channel.status_value());
fidl::WireResult result = fidl::WireCall(channel.value())->GetInfo();
ASSERT_OK(result.status(), "Transport layer error");
ASSERT_OK(result.value().status, "Service returned error.");
// Check API returns the correct information for a non preallocated FVM.
EXPECT_EQ(kExpectedFormat.slice_size, result.value().info->slice_size);
// Less or equal, because the metadata size is rounded to the nearest block boundary.
EXPECT_LE(result.value().info->slice_count, result.value().info->maximum_slice_count);
EXPECT_EQ(kExpectedFormat.GetMaxAllocationTableEntriesForDiskSize(kBlockSize * kBlockCount),
result.value().info->slice_count);
EXPECT_EQ(kExpectedFormat.GetAllocationTableAllocatedEntryCount(),
result.value().info->maximum_slice_count);
}
TEST_F(FvmVolumeManagerApiTest, GetInfoWithPreallocatedMetadata) {
constexpr uint64_t kBlockCount = (50 * kSliceSize) / kBlockSize;
constexpr uint64_t kMaxBlockCount = 1024 * kSliceSize / kBlockSize;
std::unique_ptr<RamdiskRef> ramdisk =
RamdiskRef::Create(devmgr_->devfs_root(), kBlockSize, kBlockCount);
ASSERT_TRUE(ramdisk);
std::unique_ptr<FvmAdapter> fvm = FvmAdapter::CreateGrowable(
devmgr_->devfs_root(), kBlockSize, kBlockCount, kMaxBlockCount, kSliceSize, ramdisk.get());
ASSERT_TRUE(fvm);
const fvm::Header kExpectedFormat = fvm::Header::FromGrowableDiskSize(
fvm::kMaxUsablePartitions, kBlockSize * kBlockCount, kBlockSize * kMaxBlockCount, kSliceSize);
fdio_cpp::UnownedFdioCaller caller(fvm->device()->devfs_root_fd());
zx::result channel =
component::ConnectAt<VolumeManager>(caller.directory(), fvm->device()->path());
ASSERT_OK(channel.status_value());
fidl::WireResult result = fidl::WireCall(channel.value())->GetInfo();
ASSERT_OK(result.status(), "Transport layer error");
ASSERT_OK(result.value().status, "Service returned error.");
// Check API returns the correct information for a preallocated FVM.
EXPECT_EQ(kExpectedFormat.slice_size, result.value().info->slice_size);
// Less than because we picked sizes that enforce a difference.
EXPECT_LT(result.value().info->slice_count, result.value().info->maximum_slice_count);
EXPECT_EQ(kExpectedFormat.pslice_count, result.value().info->slice_count);
EXPECT_EQ(kExpectedFormat.GetAllocationTableAllocatedEntryCount(),
result.value().info->maximum_slice_count);
EXPECT_EQ(0, result.value().info->assigned_slice_count);
}
// Tests that the maximum extents apply to partition growth properly. This also tests the
// basics of the GetVolumeInfo() call.
TEST_F(FvmVolumeManagerApiTest, PartitionLimit) {
constexpr uint64_t kBlockCount = (50 * kSliceSize) / kBlockSize;
constexpr uint64_t kMaxBlockCount = 1024 * kSliceSize / kBlockSize;
std::unique_ptr<RamdiskRef> ramdisk =
RamdiskRef::Create(devmgr_->devfs_root(), kBlockSize, kBlockCount);
ASSERT_TRUE(ramdisk);
std::unique_ptr<FvmAdapter> fvm = FvmAdapter::CreateGrowable(
devmgr_->devfs_root(), kBlockSize, kBlockCount, kMaxBlockCount, kSliceSize, ramdisk.get());
ASSERT_TRUE(fvm);
const fvm::Header kExpectedFormat = fvm::Header::FromGrowableDiskSize(
fvm::kMaxUsablePartitions, kBlockSize * kBlockCount, kBlockSize * kMaxBlockCount, kSliceSize);
// Type GUID for partition.
fuchsia_hardware_block_partition::wire::Guid type_guid;
std::fill(std::begin(type_guid.value), std::end(type_guid.value), 0x11);
// Instance GUID for partition.
fuchsia_hardware_block_partition::wire::Guid guid;
std::fill(std::begin(guid.value), std::end(guid.value), 0x12);
fdio_cpp::UnownedFdioCaller caller(fvm->device()->devfs_root_fd());
zx::result channel =
component::ConnectAt<VolumeManager>(caller.directory(), fvm->device()->path());
ASSERT_OK(channel.status_value());
// The partition hasn't been created yet, the result should be "not found".
fidl::WireResult<VolumeManager::GetPartitionLimit> unfound_result =
fidl::WireCall(channel.value())->GetPartitionLimit(guid);
ASSERT_OK(unfound_result.status(), "Transport layer error");
ASSERT_EQ(unfound_result.value().status, ZX_ERR_NOT_FOUND);
// Create the partition inside FVM with one slice.
const char kPartitionName[] = "mypart";
fidl::WireResult<VolumeManager::AllocatePartition> alloc_result =
fidl::WireCall(channel.value())->AllocatePartition(1, type_guid, guid, kPartitionName, 0);
ASSERT_OK(alloc_result.status(), "Transport layer error");
ASSERT_OK(alloc_result.value().status, "Service returned error.");
// Find the partition we just created. Should be "<ramdisk-path>/fvm/<name>-p-1/block"
std::string device_name(ramdisk->path());
device_name.append("/fvm/");
device_name.append(kPartitionName);
device_name.append("-p-1/block");
zx::result file_channel =
device_watcher::RecursiveWaitForFile(devmgr_->devfs_root().get(), device_name.c_str());
ASSERT_OK(file_channel.status_value());
fidl::ClientEnd<Volume> client_end(std::move(file_channel.value()));
// Query the volume to check its information.
fidl::WireResult<Volume::GetVolumeInfo> get_info = fidl::WireCall(client_end)->GetVolumeInfo();
ASSERT_OK(get_info.status(), "Transport error");
ASSERT_OK(get_info.value().status, "Expected GetVolumeInfo() call to succeed.");
EXPECT_EQ(kSliceSize, get_info.value().manager->slice_size);
EXPECT_EQ(kExpectedFormat.pslice_count, get_info.value().manager->slice_count);
EXPECT_EQ(1u, get_info.value().manager->assigned_slice_count);
EXPECT_EQ(1u, get_info.value().volume->partition_slice_count);
EXPECT_EQ(0u, get_info.value().volume->slice_limit);
// That partition's initial limit should be 0 (no limit).
fidl::WireResult<VolumeManager::GetPartitionLimit> get_result =
fidl::WireCall(channel.value())->GetPartitionLimit(guid);
ASSERT_OK(get_result.status(), "Transport layer error");
ASSERT_OK(get_result.value().status, "Service returned error.");
EXPECT_EQ(get_result.value().slice_count, 0, "Expected 0 limit on init.");
// Set the limit to two slices.
fidl::WireResult<VolumeManager::SetPartitionLimit> set_result =
fidl::WireCall(channel.value())->SetPartitionLimit(guid, 2);
ASSERT_OK(set_result.status(), "Transport layer error");
// Validate the new value can be retrieved.
fidl::WireResult<VolumeManager::GetPartitionLimit> get_result2 =
fidl::WireCall(channel.value())->GetPartitionLimit(guid);
ASSERT_OK(get_result2.status(), "Transport layer error");
ASSERT_OK(get_result2.value().status, "Service returned error.");
EXPECT_EQ(get_result2.value().slice_count, 2, "Expected the limit we set.");
// Try to expand it by one slice. Since the initial size was one slice and the limit is two, this
// should succeed.
fidl::WireResult<Volume::Extend> good_extend = fidl::WireCall(client_end)->Extend(100, 1);
ASSERT_OK(good_extend.status(), "Transport error");
ASSERT_OK(good_extend.value().status, "Expected Expand() call to succeed.");
// Query the volume to check its information.
fidl::WireResult<Volume::GetVolumeInfo> get_info2 = fidl::WireCall(client_end)->GetVolumeInfo();
ASSERT_OK(get_info2.status(), "Transport error");
ASSERT_OK(get_info2.value().status, "Expected GetVolumeInfo() call to succeed.");
EXPECT_EQ(kSliceSize, get_info2.value().manager->slice_size);
EXPECT_EQ(kExpectedFormat.pslice_count, get_info2.value().manager->slice_count);
EXPECT_EQ(2u, get_info2.value().manager->assigned_slice_count);
EXPECT_EQ(2u, get_info2.value().volume->partition_slice_count);
EXPECT_EQ(2u, get_info2.value().volume->slice_limit);
// Adding a third slice should fail since it's already at the max size.
fidl::WireResult<Volume::Extend> bad_extend = fidl::WireCall(client_end)->Extend(200, 1);
ASSERT_OK(bad_extend.status(), "Transport error");
ASSERT_EQ(bad_extend.value().status, ZX_ERR_NO_SPACE, "Expected Expand() call to fail.");
// Delete and re-create the partition. It should have no limit.
fidl::WireResult<Volume::Destroy> destroy_result = fidl::WireCall(client_end)->Destroy();
ASSERT_OK(destroy_result.status(), "Transport layer error");
ASSERT_OK(destroy_result.value().status, "Can't destroy partition.");
fidl::WireResult<VolumeManager::AllocatePartition> alloc2_result =
fidl::WireCall(channel.value())
->AllocatePartition(1, type_guid, guid,
/*kPartitionName*/ "thepart", 0);
ASSERT_OK(alloc2_result.status(), "Transport layer error");
ASSERT_OK(alloc2_result.value().status, "Service returned error.");
// That partition's initial limit should be 0 (no limit).
fidl::WireResult<VolumeManager::GetPartitionLimit> last_get_result =
fidl::WireCall(channel.value())->GetPartitionLimit(guid);
ASSERT_OK(last_get_result.status(), "Transport layer error");
ASSERT_OK(last_get_result.value().status, "Service returned error.");
EXPECT_EQ(last_get_result.value().slice_count, 0, "Expected 0 limit on new partition.");
}
TEST_F(FvmVolumeManagerApiTest, SetPartitionName) {
constexpr uint64_t kBlockCount = (50 * kSliceSize) / kBlockSize;
constexpr uint64_t kMaxBlockCount = 1024 * kSliceSize / kBlockSize;
std::unique_ptr<RamdiskRef> ramdisk =
RamdiskRef::Create(devmgr_->devfs_root(), kBlockSize, kBlockCount);
ASSERT_TRUE(ramdisk);
std::unique_ptr<FvmAdapter> fvm = FvmAdapter::CreateGrowable(
devmgr_->devfs_root(), kBlockSize, kBlockCount, kMaxBlockCount, kSliceSize, ramdisk.get());
ASSERT_TRUE(fvm);
// Type GUID for partition.
fuchsia_hardware_block_partition::wire::Guid type_guid;
std::fill(std::begin(type_guid.value), std::end(type_guid.value), 0x11);
// Instance GUID for partition.
fuchsia_hardware_block_partition::wire::Guid guid;
std::fill(std::begin(guid.value), std::end(guid.value), 0x12);
fdio_cpp::UnownedFdioCaller caller(fvm->device()->devfs_root_fd());
zx::result channel =
component::ConnectAt<VolumeManager>(caller.directory(), fvm->device()->path());
ASSERT_OK(channel.status_value());
// Create the partition inside FVM with one slice.
const char kPartitionName[] = "mypart";
auto alloc_result =
fidl::WireCall(channel.value())->AllocatePartition(1, type_guid, guid, kPartitionName, 0);
ASSERT_OK(alloc_result.status(), "Transport layer error");
ASSERT_OK(alloc_result.value().status, "Service returned error.");
constexpr std::string_view kNewPartitionName = "new-name";
auto set_partition_name_result =
fidl::WireCall(channel.value())
->SetPartitionName(guid, fidl::StringView::FromExternal(kNewPartitionName));
ASSERT_OK(set_partition_name_result.status(), "Transport layer error");
ASSERT_FALSE(set_partition_name_result->is_error(), "Service returned error.");
// Find the partition we just created. It will still have the original path:
// "<ramdisk-path>/fvm/mypart-p-1/block"
std::string device_name(ramdisk->path());
device_name.append("/fvm/");
device_name.append(kPartitionName);
device_name.append("-p-1/block");
zx::result file_channel =
device_watcher::RecursiveWaitForFile(devmgr_->devfs_root().get(), device_name.c_str());
ASSERT_OK(file_channel.status_value());
fidl::ClientEnd<Volume> client_end(std::move(file_channel.value()));
{
auto get_name_result = fidl::WireCall(client_end)->GetName();
ASSERT_OK(get_name_result.status(), "Transport layer error");
ASSERT_OK(get_name_result.value().status, "Service returned error.");
ASSERT_EQ(get_name_result.value().name.get(), kNewPartitionName);
}
// Make sure that the change was persisted.
ASSERT_OK(fvm->Rebind({}));
// This time, the path should include the new name.
device_name = ramdisk->path();
device_name.append("/fvm/");
device_name.append(kNewPartitionName);
device_name.append("-p-1/block");
file_channel =
device_watcher::RecursiveWaitForFile(devmgr_->devfs_root().get(), device_name.c_str());
ASSERT_OK(file_channel.status_value());
client_end = fidl::ClientEnd<Volume>(std::move(file_channel.value()));
auto get_name_result = fidl::WireCall(client_end)->GetName();
ASSERT_OK(get_name_result.status(), "Transport layer error");
ASSERT_OK(get_name_result.value().status, "Service returned error.");
ASSERT_EQ(get_name_result.value().name.get(), kNewPartitionName);
}
} // namespace
} // namespace fvm