Skip to content

Commit bb3be44

Browse files
committed
test: add file blockdiff tests
Fixes: http://tracker.ceph.com/issues/69791 Signed-off-by: Venky Shankar <[email protected]>
1 parent 72b6c7f commit bb3be44

File tree

1 file changed

+328
-3
lines changed

1 file changed

+328
-3
lines changed

src/test/libcephfs/snapdiff.cc

Lines changed: 328 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*
1212
*/
1313

14+
#include "include/interval_set.h"
1415
#include "gtest/gtest.h"
1516
#include "include/cephfs/libcephfs.h"
1617
#include "include/stat.h"
@@ -38,6 +39,9 @@ class TestMount {
3839
ceph_mount_info* cmount = nullptr;
3940
char dir_path[64];
4041

42+
const uint64_t BLOCK_SIZE_FACTOR = 1*1024*1024;
43+
const uint64_t BLOCK_SIZE=4*BLOCK_SIZE_FACTOR;
44+
4145
public:
4246
TestMount( const char* root_dir_name = "dir0") {
4347
ceph_create(&cmount, NULL);
@@ -160,21 +164,59 @@ class TestMount {
160164
return r;
161165
}
162166

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)
164168
{
165169
auto file_path = make_file_path(relpath);
166170
int fd = ceph_open(cmount, file_path.c_str(), O_WRONLY | O_CREAT, 0666);
167171
if (fd < 0) {
168172
return -EACCES;
169173
}
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);
171175
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+
}
173179
ceph_fsync(cmount, fd, 0);
174180
}
181+
return r;
182+
175183
ceph_close(cmount, fd);
176184
return r;
177185
}
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+
}
178220
string concat_path(string_view path, string_view name) {
179221
string s(path);
180222
if (s.empty() || s.back() != '/') {
@@ -342,6 +384,58 @@ class TestMount {
342384
return r;
343385
}
344386

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+
345439
int mkdir(const char* relpath)
346440
{
347441
auto path = make_file_path(relpath);
@@ -391,9 +485,18 @@ class TestMount {
391485
const char* snap1,
392486
const char* snap2);
393487

488+
void write_blocks(const std::vector<std::tuple<int64_t,uint64_t,bool>> &changes,
489+
interval_set<uint64_t> *expected=nullptr);
490+
394491
void prepareSnapDiffLib1Cases();
395492
void prepareSnapDiffLib2Cases();
396493
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);
397500
void prepareHugeSnapDiff(const std::string& name_prefix_start,
398501
const std::string& name_prefix_bulk,
399502
const std::string& name_prefix_end,
@@ -429,6 +532,137 @@ void TestMount::print_snap_diff(const char* relpath,
429532
}));
430533
};
431534

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+
432666
/* The following method creates some files/folders/snapshots layout,
433667
described in the sheet below.
434668
We're to test SnapDiff readdir API against that structure.
@@ -456,6 +690,7 @@ void TestMount::print_snap_diff(const char* relpath,
456690
# dirD | dirD |
457691
# dirD/fileD1 | dirD/fileD1 |
458692
*/
693+
459694
void TestMount::prepareSnapDiffLib1Cases()
460695
{
461696
//************ snap1 *************
@@ -1811,3 +2046,93 @@ TEST(LibCephFS, HugeSnapDiffLargeDelta)
18112046
ASSERT_EQ(0, test_mount.rmsnap("snap1"));
18122047
ASSERT_EQ(0, test_mount.rmsnap("snap2"));
18132048
}
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

Comments
 (0)