| // Copyright 2020 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 <errno.h> |
| #include <fcntl.h> |
| #include <fuchsia/io/llcpp/fidl.h> |
| #include <sys/stat.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <atomic> |
| #include <thread> |
| |
| #include <fbl/auto_call.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/storage/blobfs/common.h" |
| #include "src/storage/blobfs/test/integration/blobfs_fixtures.h" |
| #include "src/storage/blobfs/test/integration/load_generator.h" |
| #include "src/storage/fvm/format.h" |
| |
| namespace blobfs { |
| namespace { |
| |
| using FragmentationTest = ParameterizedBlobfsTest; |
| // The following test attempts to fragment the underlying blobfs partition |
| // assuming a trivial linear allocator. A more intelligent allocator may require |
| // modifications to this test. |
| TEST_P(FragmentationTest, Fragmentation) { |
| // Keep generating blobs until we run out of space, in a pattern of large, |
| // small, large, small, large. |
| // |
| // At the end of the test, we'll free the small blobs, and observe if it is |
| // possible to allocate a larger blob. With a simple allocator and no |
| // defragmentation, this would result in a NO_SPACE error. |
| constexpr size_t kSmallSize = (1 << 16); |
| constexpr size_t kLargeSize = (1 << 17); |
| |
| fbl::Vector<fbl::String> small_blobs; |
| |
| bool do_small_blob = true; |
| bool capture_large_blob_storage_space_usage = true; |
| size_t large_blob_storage_space_usage = 0; |
| size_t count = 0; |
| while (true) { |
| std::unique_ptr<BlobInfo> info = |
| GenerateRandomBlob(fs().mount_path(), do_small_blob ? kSmallSize : kLargeSize); |
| fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR)); |
| ASSERT_TRUE(fd) << "Failed to create blob"; |
| if (capture_large_blob_storage_space_usage && !do_small_blob) { |
| // Record how much space was used by blobfs before writing a large blob. |
| auto fs_info = fs().GetFsInfo(); |
| ASSERT_TRUE(fs_info.is_ok()); |
| large_blob_storage_space_usage = fs_info->used_bytes; |
| } |
| ASSERT_EQ(0, ftruncate(fd.get(), info->size_data)); |
| if (StreamAll(write, fd.get(), info->data.get(), info->size_data) < 0) { |
| ASSERT_EQ(ENOSPC, errno) << "Blobfs expected to run out of space"; |
| break; |
| } |
| if (capture_large_blob_storage_space_usage && !do_small_blob) { |
| // Determine how much space was required to store the large by blob by comparing blobfs' |
| // space usage before and after writing the blob. |
| auto fs_info = fs().GetFsInfo(); |
| ASSERT_TRUE(fs_info.is_ok()); |
| large_blob_storage_space_usage = fs_info->used_bytes - large_blob_storage_space_usage; |
| capture_large_blob_storage_space_usage = false; |
| } |
| if (do_small_blob) { |
| small_blobs.push_back(fbl::String(info->path)); |
| } |
| |
| do_small_blob = !do_small_blob; |
| |
| if (++count % 50 == 0) { |
| fprintf(stderr, "Allocated %lu blobs\n", count); |
| } |
| } |
| |
| // We have filled up the disk with both small and large blobs. |
| // Observe that we cannot add another large blob. |
| std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), kLargeSize); |
| |
| // ... and we don't have space (as we try allocating). |
| fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR)); |
| ASSERT_TRUE(fd); |
| ASSERT_EQ(0, ftruncate(fd.get(), info->size_data)); |
| ASSERT_NE(0, StreamAll(write, fd.get(), info->data.get(), info->size_data)); |
| ASSERT_EQ(ENOSPC, errno) << "Blobfs expected to be out of space"; |
| fd.reset(); |
| |
| // Unlink all small blobs -- except for the last one, since we may have free |
| // trailing space at the end. |
| for (size_t i = 0; i < small_blobs.size() - 1; i++) { |
| ASSERT_EQ(0, unlink(small_blobs[i].c_str())) << "Unlinking old blob"; |
| } |
| |
| // This asserts an assumption of our test: Freeing these blobs should provide |
| // enough space. |
| ASSERT_GT(kSmallSize * (small_blobs.size() - 1), kLargeSize); |
| |
| // Validate that we have enough space (before we try allocating)... |
| auto fs_info_or = fs().GetFsInfo(); |
| ASSERT_TRUE(fs_info_or.is_ok()); |
| ASSERT_GE(fs_info_or->total_bytes - fs_info_or->used_bytes, large_blob_storage_space_usage); |
| |
| fd.reset(open(info->path, O_CREAT | O_RDWR)); |
| // Now that blobfs supports extents, verify that we can still allocate a large |
| // blob, even if it is fragmented. |
| ASSERT_EQ(0, ftruncate(fd.get(), info->size_data)); |
| |
| // Sanity check that we can write and read the fragmented blob. |
| ASSERT_EQ(0, StreamAll(write, fd.get(), info->data.get(), info->size_data)); |
| std::unique_ptr<char[]> buf(new char[info->size_data]); |
| ASSERT_EQ(0, lseek(fd.get(), 0, SEEK_SET)); |
| ASSERT_EQ(0, StreamAll(read, fd.get(), buf.get(), info->size_data)); |
| ASSERT_EQ(memcmp(info->data.get(), buf.get(), info->size_data), 0); |
| |
| // Sanity check that we can re-open and unlink the fragmented blob. |
| fd.reset(open(info->path, O_RDONLY)); |
| ASSERT_TRUE(fd); |
| ASSERT_EQ(0, unlink(info->path)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(/*no prefix*/, FragmentationTest, |
| testing::Values(BlobfsDefaultTestParam(), BlobfsWithFvmTestParam(), |
| BlobfsWithCompactLayoutTestParam()), |
| testing::PrintToStringParamName()); |
| |
| } // namespace |
| } // namespace blobfs |