|
11 | 11 | * |
12 | 12 | */ |
13 | 13 |
|
| 14 | +#include "include/interval_set.h" |
14 | 15 | #include "gtest/gtest.h" |
15 | 16 | #include "include/cephfs/libcephfs.h" |
16 | 17 | #include "include/stat.h" |
@@ -38,6 +39,9 @@ class TestMount { |
38 | 39 | ceph_mount_info* cmount = nullptr; |
39 | 40 | char dir_path[64]; |
40 | 41 |
|
| 42 | + const uint64_t BLOCK_SIZE_FACTOR = 1*1024*1024; |
| 43 | + const uint64_t BLOCK_SIZE=4*BLOCK_SIZE_FACTOR; |
| 44 | + |
41 | 45 | public: |
42 | 46 | TestMount( const char* root_dir_name = "dir0") { |
43 | 47 | ceph_create(&cmount, NULL); |
@@ -160,21 +164,59 @@ class TestMount { |
160 | 164 | return r; |
161 | 165 | } |
162 | 166 |
|
163 | | - int write_full(const char* relpath, const string& data) |
| 167 | + int write_full(const char* relpath, const string& data, int64_t offset=0, bool trunc=true) |
164 | 168 | { |
165 | 169 | auto file_path = make_file_path(relpath); |
166 | 170 | int fd = ceph_open(cmount, file_path.c_str(), O_WRONLY | O_CREAT, 0666); |
167 | 171 | if (fd < 0) { |
168 | 172 | return -EACCES; |
169 | 173 | } |
170 | | - int r = ceph_write(cmount, fd, data.c_str(), data.size(), 0); |
| 174 | + int r = ceph_write(cmount, fd, data.c_str(), data.size(), offset); |
171 | 175 | if (r >= 0) { |
172 | | - ceph_truncate(cmount, file_path.c_str(), data.size()); |
| 176 | + if (trunc) { |
| 177 | + ceph_truncate(cmount, file_path.c_str(), data.size()); |
| 178 | + } |
173 | 179 | ceph_fsync(cmount, fd, 0); |
174 | 180 | } |
| 181 | + return r; |
| 182 | + |
175 | 183 | ceph_close(cmount, fd); |
176 | 184 | return r; |
177 | 185 | } |
| 186 | + void generate_random_string_n(uint64_t count, uint64_t block_size, |
| 187 | + const std::function<void(const std::string&)> &f) |
| 188 | + { |
| 189 | + static const char alphabet[] = |
| 190 | + "abcdefghijklmnopqrstuvwxyz" |
| 191 | + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| 192 | + "0123456789"; |
| 193 | + |
| 194 | + std::random_device rd; |
| 195 | + std::default_random_engine rng(rd()); |
| 196 | + std::uniform_int_distribution<> dist(0,sizeof(alphabet)/sizeof(*alphabet)-2); |
| 197 | + |
| 198 | + std::vector<std::string> strs; |
| 199 | + strs.reserve(count); |
| 200 | + std::generate_n(std::back_inserter(strs), strs.capacity(), |
| 201 | + [&] { std::string str; |
| 202 | + str.reserve(block_size); |
| 203 | + std::generate_n(std::back_inserter(str), block_size, |
| 204 | + [&]() { return alphabet[dist(rng)];}); |
| 205 | + return str; }); |
| 206 | + for (auto &str : strs) { |
| 207 | + f(str); |
| 208 | + } |
| 209 | + } |
| 210 | + int write_random(const char* relpath, uint64_t count, uint64_t block_size, int64_t offset=-1, bool trunc=false) |
| 211 | + { |
| 212 | + std::string s; |
| 213 | + generate_random_string_n(count, block_size, [&s](const std::string& str) { |
| 214 | + s.append(str); |
| 215 | + }); |
| 216 | + |
| 217 | + std::cout << "write: [" << offset << "~" << count*block_size << " (trunc:" << trunc << ")]" << std::endl; |
| 218 | + return write_full(relpath, s.c_str(), offset, trunc); |
| 219 | + } |
178 | 220 | string concat_path(string_view path, string_view name) { |
179 | 221 | string s(path); |
180 | 222 | if (s.empty() || s.back() != '/') { |
@@ -342,6 +384,58 @@ class TestMount { |
342 | 384 | return r; |
343 | 385 | } |
344 | 386 |
|
| 387 | + int for_each_file_blockdiff(const char* relpath, |
| 388 | + const char* snap1, |
| 389 | + const char* snap2, |
| 390 | + interval_set<uint64_t> *expected=nullptr) |
| 391 | + { |
| 392 | + auto s1 = make_snap_name(snap1); |
| 393 | + auto s2 = make_snap_name(snap2); |
| 394 | + ceph_file_blockdiff_info info; |
| 395 | + int r = ceph_file_blockdiff_init(cmount, |
| 396 | + dir_path, |
| 397 | + relpath, |
| 398 | + s1.c_str(), |
| 399 | + s2.c_str(), |
| 400 | + &info); |
| 401 | + if (r != 0) { |
| 402 | + std::cerr << " Failed to init file block snapdiff, ret:" << r << std::endl; |
| 403 | + return r; |
| 404 | + } |
| 405 | + |
| 406 | + r = 1; |
| 407 | + while (r > 0) { |
| 408 | + ceph_file_blockdiff_changedblocks blocks; |
| 409 | + r = ceph_file_blockdiff(&info, &blocks); |
| 410 | + if (r < 0) { |
| 411 | + std::cerr << " Failed to get next changed block, ret:" << r << std::endl; |
| 412 | + return r; |
| 413 | + } |
| 414 | + |
| 415 | + int nr_blocks = blocks.num_blocks; |
| 416 | + struct cblock *b = blocks.b; |
| 417 | + while (nr_blocks > 0) { |
| 418 | + std::cout << " == [" << b->offset << "~" << b->len << "] == " << std::endl; |
| 419 | + if (expected) { |
| 420 | + expected->erase(b->offset, b->len); |
| 421 | + } |
| 422 | + ++b; |
| 423 | + --nr_blocks; |
| 424 | + } |
| 425 | + |
| 426 | + ceph_free_file_blockdiff_buffer(&blocks); |
| 427 | + } |
| 428 | + |
| 429 | + ceph_assert(0 == ceph_file_blockdiff_finish(&info)); |
| 430 | + if (r < 0) { |
| 431 | + std::cerr << " Failed to block diff, ret:" << r |
| 432 | + << " " << relpath << ", " << snap1 << " vs. " << snap2 |
| 433 | + << std::endl; |
| 434 | + } |
| 435 | + |
| 436 | + return r; |
| 437 | + } |
| 438 | + |
345 | 439 | int mkdir(const char* relpath) |
346 | 440 | { |
347 | 441 | auto path = make_file_path(relpath); |
@@ -391,9 +485,18 @@ class TestMount { |
391 | 485 | const char* snap1, |
392 | 486 | const char* snap2); |
393 | 487 |
|
| 488 | + void write_blocks(const std::vector<std::tuple<int64_t,uint64_t,bool>> &changes, |
| 489 | + interval_set<uint64_t> *expected=nullptr); |
| 490 | + |
394 | 491 | void prepareSnapDiffLib1Cases(); |
395 | 492 | void prepareSnapDiffLib2Cases(); |
396 | 493 | void prepareSnapDiffLib3Cases(); |
| 494 | + void prepareBlockDiffNoChangeWithUnchangedHead(); |
| 495 | + void prepareBlockDiffNoChangeWithChangedHead(); |
| 496 | + void prepareBlockDiffChangedBlockWithUnchangedHead(interval_set<uint64_t> *expected); |
| 497 | + void prepareBlockDiffChangedBlockWithChangedHead(interval_set<uint64_t> *expected); |
| 498 | + void prepareBlockDiffChangedBlockWithTruncatedBlock(interval_set<uint64_t> *expected); |
| 499 | + void prepareBlockDiffChangedBlockWithCustomObjectSize(interval_set<uint64_t> *expected); |
397 | 500 | void prepareHugeSnapDiff(const std::string& name_prefix_start, |
398 | 501 | const std::string& name_prefix_bulk, |
399 | 502 | const std::string& name_prefix_end, |
@@ -429,6 +532,137 @@ void TestMount::print_snap_diff(const char* relpath, |
429 | 532 | })); |
430 | 533 | }; |
431 | 534 |
|
| 535 | +void TestMount::prepareBlockDiffNoChangeWithUnchangedHead() |
| 536 | +{ |
| 537 | + //************ snap1 ************* |
| 538 | + //************ snap2 ************* |
| 539 | + ASSERT_LE(0, write_random("fileA", 2, 4*1024*1024)); |
| 540 | + ASSERT_EQ(0, mksnap("snap1")); |
| 541 | + ASSERT_EQ(0, mksnap("snap2")); |
| 542 | + /* head is not modified */ |
| 543 | +} |
| 544 | + |
| 545 | +void TestMount::prepareBlockDiffNoChangeWithChangedHead() |
| 546 | +{ |
| 547 | + //************ snap1 ************* |
| 548 | + //************ snap2 ************* |
| 549 | + ASSERT_LE(0, write_random("fileA", 2, 4*1024*1024)); |
| 550 | + ASSERT_EQ(0, mksnap("snap1")); |
| 551 | + ASSERT_EQ(0, mksnap("snap2")); |
| 552 | + |
| 553 | + /* modify head - fill up one object */ |
| 554 | + ASSERT_LE(0, write_random("fileA", 1, 4 * 1024 * 1024, -1, true)); |
| 555 | +} |
| 556 | + |
| 557 | +// make thos helper track file holes |
| 558 | +void TestMount::write_blocks(const std::vector<std::tuple<int64_t,uint64_t,bool>> &changes, |
| 559 | + interval_set<uint64_t> *expected) |
| 560 | +{ |
| 561 | + for (auto &change : changes) { |
| 562 | + int64_t offset = std::get<0>(change); |
| 563 | + uint64_t len = std::get<1>(change); |
| 564 | + bool trunc = std::get<2>(change); |
| 565 | + |
| 566 | + uint64_t count = (len / BLOCK_SIZE); |
| 567 | + uint64_t rem = (len % BLOCK_SIZE); |
| 568 | + if (count) { |
| 569 | + ASSERT_LE(0, write_random("fileA", count, BLOCK_SIZE, offset, trunc)); |
| 570 | + if (expected) { |
| 571 | + expected->union_insert(offset, count*BLOCK_SIZE); |
| 572 | + } |
| 573 | + } |
| 574 | + if (rem) { |
| 575 | + offset += count * BLOCK_SIZE; |
| 576 | + ASSERT_LE(0, write_random("fileA", 1, rem, offset, trunc)); |
| 577 | + if (expected) { |
| 578 | + expected->union_insert(offset, rem); |
| 579 | + } |
| 580 | + } |
| 581 | + } |
| 582 | +} |
| 583 | + |
| 584 | +void TestMount::prepareBlockDiffChangedBlockWithUnchangedHead(interval_set<uint64_t> *expected) |
| 585 | +{ |
| 586 | + //************ snap1 ************* |
| 587 | + ASSERT_LE(0, write_random("fileA", 5, BLOCK_SIZE)); |
| 588 | + ASSERT_EQ(0, mksnap("snap1")); |
| 589 | + |
| 590 | + //************ snap2 ************* |
| 591 | + // overwrite first object w/ truncate |
| 592 | + // partly fill fourth object (creating a hole in previous blocks) |
| 593 | + srand(100); |
| 594 | + std::vector<std::tuple<int64_t,uint64_t,bool>> changes{ |
| 595 | + std::make_tuple(0, BLOCK_SIZE, true), |
| 596 | + std::make_tuple((12*BLOCK_SIZE_FACTOR)+(rand()%100), 0.5*BLOCK_SIZE_FACTOR, false) |
| 597 | + }; |
| 598 | + // we'll prepare expected set ourselves |
| 599 | + write_blocks(changes); |
| 600 | + |
| 601 | + ASSERT_EQ(0, mksnap("snap2")); |
| 602 | + /* head is not modified */ |
| 603 | + auto &l = changes.back(); |
| 604 | + /* blockdiff swallows holes */ |
| 605 | + expected->union_insert(0, std::get<0>(l)+std::get<1>(l)); |
| 606 | +} |
| 607 | + |
| 608 | +void TestMount::prepareBlockDiffChangedBlockWithChangedHead(interval_set<uint64_t> *expected) |
| 609 | +{ |
| 610 | + //************ snap1 ************* |
| 611 | + ASSERT_LE(0, write_random("fileA", 5, 4*1024*1024)); |
| 612 | + ASSERT_EQ(0, mksnap("snap1")); |
| 613 | + |
| 614 | + //************ snap2 ************* |
| 615 | + // overwrite first object w/o truncate |
| 616 | + // partly fill third object (no holes) |
| 617 | + srand(100); |
| 618 | + std::vector<std::tuple<int64_t,uint64_t,bool>> changes{ |
| 619 | + std::make_tuple(0, BLOCK_SIZE, false), |
| 620 | + std::make_tuple((8*BLOCK_SIZE_FACTOR)+(rand()%100), 0.5*BLOCK_SIZE_FACTOR, false) |
| 621 | + }; |
| 622 | + write_blocks(changes, expected); |
| 623 | + ASSERT_EQ(0, mksnap("snap2")); |
| 624 | + |
| 625 | + /* modify head - fill up some objects */ |
| 626 | + ASSERT_LE(0, write_random("fileA", 2, 4 * 1024 * 1024, -1, false)); |
| 627 | +} |
| 628 | + |
| 629 | +void TestMount::prepareBlockDiffChangedBlockWithTruncatedBlock(interval_set<uint64_t> *expected) |
| 630 | +{ |
| 631 | + //************ snap1 ************* |
| 632 | + ASSERT_LE(0, write_random("fileA", 4, 4*1024*1024)); |
| 633 | + ASSERT_EQ(0, mksnap("snap1")); |
| 634 | + |
| 635 | + //************ snap2 ************* |
| 636 | + // write some bytes in few objects |
| 637 | + // extend the file size |
| 638 | + std::vector<std::tuple<int64_t,uint64_t,bool>> changes{ |
| 639 | + std::make_tuple(1*BLOCK_SIZE_FACTOR, 10, false), |
| 640 | + std::make_tuple((5*BLOCK_SIZE_FACTOR), 20, false), |
| 641 | + std::make_tuple((14*BLOCK_SIZE_FACTOR), 10*BLOCK_SIZE_FACTOR, false) |
| 642 | + }; |
| 643 | + write_blocks(changes, expected); |
| 644 | + ASSERT_EQ(0, mksnap("snap2")); |
| 645 | +} |
| 646 | + |
| 647 | +void TestMount::prepareBlockDiffChangedBlockWithCustomObjectSize(interval_set<uint64_t> *expected) |
| 648 | +{ |
| 649 | + //************ snap1 ************* |
| 650 | + auto file_path = make_file_path("fileA"); |
| 651 | + ASSERT_EQ(0, ceph_mknod(cmount, file_path.c_str(), 0666, 0)); |
| 652 | + std::string val = std::to_string(8 * BLOCK_SIZE_FACTOR); |
| 653 | + ASSERT_EQ(0, ceph_setxattr(cmount, file_path.c_str(), "ceph.file.layout.object_size", val.c_str(), |
| 654 | + val.size(), 0)); |
| 655 | + |
| 656 | + ASSERT_LE(0, write_random("fileA", 10, 4 * BLOCK_SIZE_FACTOR)); |
| 657 | + ASSERT_EQ(0, mksnap("snap1")); |
| 658 | + |
| 659 | + std::vector<std::tuple<int64_t,uint64_t,bool>> changes{ |
| 660 | + std::make_tuple(0, 40 * BLOCK_SIZE_FACTOR, false), |
| 661 | + }; |
| 662 | + write_blocks(changes, expected); |
| 663 | + ASSERT_EQ(0, mksnap("snap2")); |
| 664 | +} |
| 665 | + |
432 | 666 | /* The following method creates some files/folders/snapshots layout, |
433 | 667 | described in the sheet below. |
434 | 668 | We're to test SnapDiff readdir API against that structure. |
@@ -456,6 +690,7 @@ void TestMount::print_snap_diff(const char* relpath, |
456 | 690 | # dirD | dirD | |
457 | 691 | # dirD/fileD1 | dirD/fileD1 | |
458 | 692 | */ |
| 693 | + |
459 | 694 | void TestMount::prepareSnapDiffLib1Cases() |
460 | 695 | { |
461 | 696 | //************ snap1 ************* |
@@ -1811,3 +2046,93 @@ TEST(LibCephFS, HugeSnapDiffLargeDelta) |
1811 | 2046 | ASSERT_EQ(0, test_mount.rmsnap("snap1")); |
1812 | 2047 | ASSERT_EQ(0, test_mount.rmsnap("snap2")); |
1813 | 2048 | } |
| 2049 | + |
| 2050 | +TEST(LibCephFS, SnapDiffNoChangeWithUnchangedHead) |
| 2051 | +{ |
| 2052 | + TestMount test_mount; |
| 2053 | + |
| 2054 | + test_mount.prepareBlockDiffNoChangeWithUnchangedHead(); |
| 2055 | + test_mount.for_each_file_blockdiff("fileA", "snap1", "snap2"); |
| 2056 | + |
| 2057 | + std::cout << "------------- closing -------------" << std::endl; |
| 2058 | + ASSERT_EQ(0, test_mount.purge_dir("")); |
| 2059 | + ASSERT_EQ(0, test_mount.rmsnap("snap1")); |
| 2060 | + ASSERT_EQ(0, test_mount.rmsnap("snap2")); |
| 2061 | +} |
| 2062 | + |
| 2063 | +TEST(LibCephFS, SnapDiffNoChangeWithChangedHead) |
| 2064 | +{ |
| 2065 | + TestMount test_mount; |
| 2066 | + |
| 2067 | + test_mount.prepareBlockDiffNoChangeWithChangedHead(); |
| 2068 | + test_mount.for_each_file_blockdiff("fileA", "snap1", "snap2"); |
| 2069 | + |
| 2070 | + std::cout << "------------- closing -------------" << std::endl; |
| 2071 | + ASSERT_EQ(0, test_mount.purge_dir("")); |
| 2072 | + ASSERT_EQ(0, test_mount.rmsnap("snap1")); |
| 2073 | + ASSERT_EQ(0, test_mount.rmsnap("snap2")); |
| 2074 | +} |
| 2075 | + |
| 2076 | +TEST(LibCephFS, SnapDiffChangedBlockWithUnchangedHead) |
| 2077 | +{ |
| 2078 | + TestMount test_mount; |
| 2079 | + |
| 2080 | + interval_set<uint64_t> expected; |
| 2081 | + test_mount.prepareBlockDiffChangedBlockWithUnchangedHead(&expected); |
| 2082 | + std::cout << "expected=" << expected << std::endl; |
| 2083 | + test_mount.for_each_file_blockdiff("fileA", "snap1", "snap2", &expected); |
| 2084 | + ASSERT_TRUE(expected.empty()); |
| 2085 | + |
| 2086 | + std::cout << "------------- closing -------------" << std::endl; |
| 2087 | + ASSERT_EQ(0, test_mount.purge_dir("")); |
| 2088 | + ASSERT_EQ(0, test_mount.rmsnap("snap1")); |
| 2089 | + ASSERT_EQ(0, test_mount.rmsnap("snap2")); |
| 2090 | +} |
| 2091 | + |
| 2092 | +TEST(LibCephFS, SnapDiffChangedBlockWithChangedHead) |
| 2093 | +{ |
| 2094 | + TestMount test_mount; |
| 2095 | + |
| 2096 | + interval_set<uint64_t> expected; |
| 2097 | + test_mount.prepareBlockDiffChangedBlockWithChangedHead(&expected); |
| 2098 | + std::cout << "expected=" << expected << std::endl; |
| 2099 | + test_mount.for_each_file_blockdiff("fileA", "snap1", "snap2", &expected); |
| 2100 | + ASSERT_TRUE(expected.empty()); |
| 2101 | + |
| 2102 | + std::cout << "------------- closing -------------" << std::endl; |
| 2103 | + ASSERT_EQ(0, test_mount.purge_dir("")); |
| 2104 | + ASSERT_EQ(0, test_mount.rmsnap("snap1")); |
| 2105 | + ASSERT_EQ(0, test_mount.rmsnap("snap2")); |
| 2106 | +} |
| 2107 | + |
| 2108 | +TEST(LibCephFS, SnapDiffChangedBlockWithTruncatedBlock) |
| 2109 | +{ |
| 2110 | + TestMount test_mount; |
| 2111 | + |
| 2112 | + interval_set<uint64_t> expected; |
| 2113 | + test_mount.prepareBlockDiffChangedBlockWithTruncatedBlock(&expected); |
| 2114 | + std::cout << "expected=" << expected << std::endl; |
| 2115 | + test_mount.for_each_file_blockdiff("fileA", "snap1", "snap2", &expected); |
| 2116 | + ASSERT_TRUE(expected.empty()); |
| 2117 | + |
| 2118 | + std::cout << "------------- closing -------------" << std::endl; |
| 2119 | + ASSERT_EQ(0, test_mount.purge_dir("")); |
| 2120 | + ASSERT_EQ(0, test_mount.rmsnap("snap1")); |
| 2121 | + ASSERT_EQ(0, test_mount.rmsnap("snap2")); |
| 2122 | +} |
| 2123 | + |
| 2124 | +TEST(LibCephFS, SnapDiffChangedBlockWithCustomObjectSize) |
| 2125 | +{ |
| 2126 | + TestMount test_mount; |
| 2127 | + |
| 2128 | + interval_set<uint64_t> expected; |
| 2129 | + test_mount.prepareBlockDiffChangedBlockWithCustomObjectSize(&expected); |
| 2130 | + std::cout << "expected=" << expected << std::endl; |
| 2131 | + test_mount.for_each_file_blockdiff("fileA", "snap1", "snap2", &expected); |
| 2132 | + ASSERT_TRUE(expected.empty()); |
| 2133 | + |
| 2134 | + std::cout << "------------- closing -------------" << std::endl; |
| 2135 | + ASSERT_EQ(0, test_mount.purge_dir("")); |
| 2136 | + ASSERT_EQ(0, test_mount.rmsnap("snap1")); |
| 2137 | + ASSERT_EQ(0, test_mount.rmsnap("snap2")); |
| 2138 | +} |
0 commit comments