diff options
Diffstat (limited to 'src/test')
31 files changed, 4454 insertions, 744 deletions
diff --git a/src/test/cli-integration/rbd/snap-diff.t b/src/test/cli-integration/rbd/snap-diff.t index 1ca2fb04d..fa564891a 100644 --- a/src/test/cli-integration/rbd/snap-diff.t +++ b/src/test/cli-integration/rbd/snap-diff.t @@ -39,10 +39,14 @@ $ rbd diff --from-snap=snap1 xrbddiff1/xtestdiff1 --format json [] $ rbd snap rollback xrbddiff1/xtestdiff1@snap1 --no-progress + $ rbd diff --from-snap=allzeroes xrbddiff1/xtestdiff1 --format json + [{"offset":0,"length":1048576,"exists":"true"}] $ rbd diff --from-snap=snap1 xrbddiff1/xtestdiff1 --format json [] $ rbd snap rollback xrbddiff1/xtestdiff1@allzeroes --no-progress $ rbd diff --from-snap=allzeroes xrbddiff1/xtestdiff1 --format json + [] + $ rbd diff --from-snap=snap1 xrbddiff1/xtestdiff1 --format json [{"offset":0,"length":1048576,"exists":"false"}] $ ceph osd pool rm xrbddiff1 xrbddiff1 --yes-i-really-really-mean-it pool 'xrbddiff1' removed diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index 8d8d30273..4e9b0cc1d 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -194,9 +194,9 @@ rbd help children usage: rbd children [--pool <pool>] [--namespace <namespace>] - [--image <image>] [--snap <snap>] [--snap-id <snap-id>] - [--all] [--descendants] [--format <format>] - [--pretty-format] + [--image <image>] [--snap <snap>] [--image-id <image-id>] + [--snap-id <snap-id>] [--all] [--descendants] + [--format <format>] [--pretty-format] <image-or-snap-spec> Display children of an image or its snapshot. @@ -211,6 +211,7 @@ --namespace arg namespace name --image arg image name --snap arg snapshot name + --image-id arg image id --snap-id arg snapshot id -a [ --all ] list all children (include trash) --descendants include all descendants diff --git a/src/test/client/CMakeLists.txt b/src/test/client/CMakeLists.txt index 1937bdd0b..3d3e327f3 100644 --- a/src/test/client/CMakeLists.txt +++ b/src/test/client/CMakeLists.txt @@ -3,6 +3,7 @@ if(${WITH_CEPHFS}) main.cc alternate_name.cc ops.cc + commands.cc ) target_link_libraries(ceph_test_client client diff --git a/src/test/client/commands.cc b/src/test/client/commands.cc new file mode 100644 index 000000000..c1fe76331 --- /dev/null +++ b/src/test/client/commands.cc @@ -0,0 +1,48 @@ +#include <errno.h> + +#include <iostream> +#include <string> +#include <vector> +#include <unordered_map> + +#include <fmt/format.h> + +#include "test/client/TestClient.h" + + +TEST_F(TestClient, SingleTargetMdsCommand) { + auto mds_spec = "a"; + auto cmd = "{\"prefix\": \"session ls\", \"format\": \"json\"}"; + bufferlist inbl; + bufferlist outbl; + std::string outs; + std::vector<std::string> cmdv; + C_SaferCond cond; + + cmdv.push_back(cmd); + int r = client->mds_command(mds_spec, cmdv, inbl, &outbl, &outs, &cond); + r = cond.wait(); + + std::cout << "SingleTargetMdsCommand: " << outbl.c_str() << std::endl; + + ASSERT_TRUE(r == 0 || r == -38); +} + +TEST_F(TestClient, MultiTargetMdsCommand) { + auto mds_spec = "*"; + auto cmd = "{\"prefix\": \"session ls\", \"format\": \"json\"}"; + bufferlist inbl; + bufferlist outbl; + std::string outs; + std::vector<std::string> cmdv; + C_SaferCond cond; + + cmdv.push_back(cmd); + std::cout << "MultiTargetMds: " << std::endl; + int r = client->mds_command(mds_spec, cmdv, inbl, &outbl, &outs, &cond); + r = cond.wait(); + + std::cout << "MultiTargetMdsCommand: " << outbl.c_str() << std::endl; + + ASSERT_TRUE(r == 0 || r == -38); +} diff --git a/src/test/common/CMakeLists.txt b/src/test/common/CMakeLists.txt index 1179fbdfb..11e111115 100644 --- a/src/test/common/CMakeLists.txt +++ b/src/test/common/CMakeLists.txt @@ -75,17 +75,6 @@ add_executable(unittest_prioritized_queue target_link_libraries(unittest_prioritized_queue ceph-common) add_ceph_unittest(unittest_prioritized_queue) -if(NOT WIN32) -# unittest_mclock_priority_queue -add_executable(unittest_mclock_priority_queue - test_mclock_priority_queue.cc - ) -add_ceph_unittest(unittest_mclock_priority_queue) -target_link_libraries(unittest_mclock_priority_queue - ceph-common - dmclock::dmclock) -endif(NOT WIN32) - # unittest_str_map add_executable(unittest_str_map test_str_map.cc diff --git a/src/test/common/test_mclock_priority_queue.cc b/src/test/common/test_mclock_priority_queue.cc deleted file mode 100644 index 8e8bcdf38..000000000 --- a/src/test/common/test_mclock_priority_queue.cc +++ /dev/null @@ -1,320 +0,0 @@ -// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- -// vim: ts=8 sw=2 smarttab -/* - * Ceph - scalable distributed file system - * - * Copyright (C) 2017 Red Hat Inc. - * - * This is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software - * Foundation. See file COPYING. - * - */ - -#include <thread> -#include <chrono> -#include <iostream> -#include "gtest/gtest.h" -#include "common/mClockPriorityQueue.h" - - -struct Request { - int value; - Request() : - value(0) - {} - Request(const Request& o) = default; - explicit Request(int value) : - value(value) - {} -}; - - -struct Client { - int client_num; - Client() : - Client(-1) - {} - Client(int client_num) : - client_num(client_num) - {} - friend bool operator<(const Client& r1, const Client& r2) { - return r1.client_num < r2.client_num; - } - friend bool operator==(const Client& r1, const Client& r2) { - return r1.client_num == r2.client_num; - } -}; - - -const crimson::dmclock::ClientInfo* client_info_func(const Client& c) { - static const crimson::dmclock::ClientInfo - the_info(10.0, 10.0, 10.0); - return &the_info; -} - - -TEST(mClockPriorityQueue, Create) -{ - ceph::mClockQueue<Request,Client> q(&client_info_func); -} - - -TEST(mClockPriorityQueue, Sizes) -{ - ceph::mClockQueue<Request,Client> q(&client_info_func); - - ASSERT_TRUE(q.empty()); - ASSERT_EQ(0u, q.get_size_slow()); - - Client c1(1); - Client c2(2); - - q.enqueue_strict(c1, 1, Request(1)); - q.enqueue_strict(c2, 2, Request(2)); - q.enqueue_strict(c1, 2, Request(3)); - q.enqueue(c2, 1, 1u, Request(4)); - q.enqueue(c1, 2, 1u, Request(5)); - q.enqueue_strict(c2, 1, Request(6)); - - ASSERT_FALSE(q.empty()); - ASSERT_EQ(6u, q.get_size_slow()); - - - for (int i = 0; i < 6; ++i) { - (void) q.dequeue(); - } - - ASSERT_TRUE(q.empty()); - ASSERT_EQ(0u, q.get_size_slow()); -} - - -TEST(mClockPriorityQueue, JustStrict) -{ - ceph::mClockQueue<Request,Client> q(&client_info_func); - - Client c1(1); - Client c2(2); - - q.enqueue_strict(c1, 1, Request(1)); - q.enqueue_strict(c2, 2, Request(2)); - q.enqueue_strict(c1, 2, Request(3)); - q.enqueue_strict(c2, 1, Request(4)); - - Request r; - - r = q.dequeue(); - ASSERT_EQ(2, r.value); - r = q.dequeue(); - ASSERT_EQ(3, r.value); - r = q.dequeue(); - ASSERT_EQ(1, r.value); - r = q.dequeue(); - ASSERT_EQ(4, r.value); -} - - -TEST(mClockPriorityQueue, StrictPriorities) -{ - ceph::mClockQueue<Request,Client> q(&client_info_func); - - Client c1(1); - Client c2(2); - - q.enqueue_strict(c1, 1, Request(1)); - q.enqueue_strict(c2, 2, Request(2)); - q.enqueue_strict(c1, 3, Request(3)); - q.enqueue_strict(c2, 4, Request(4)); - - Request r; - - r = q.dequeue(); - ASSERT_EQ(4, r.value); - r = q.dequeue(); - ASSERT_EQ(3, r.value); - r = q.dequeue(); - ASSERT_EQ(2, r.value); - r = q.dequeue(); - ASSERT_EQ(1, r.value); -} - - -TEST(mClockPriorityQueue, JustNotStrict) -{ - ceph::mClockQueue<Request,Client> q(&client_info_func); - - Client c1(1); - Client c2(2); - - // non-strict queue ignores priorites, but will divide between - // clients evenly and maintain orders between clients - q.enqueue(c1, 1, 1u, Request(1)); - q.enqueue(c1, 2, 1u, Request(2)); - q.enqueue(c2, 3, 1u, Request(3)); - q.enqueue(c2, 4, 1u, Request(4)); - - Request r1, r2; - - r1 = q.dequeue(); - ASSERT_TRUE(1 == r1.value || 3 == r1.value); - - r2 = q.dequeue(); - ASSERT_TRUE(1 == r2.value || 3 == r2.value); - - ASSERT_NE(r1.value, r2.value); - - r1 = q.dequeue(); - ASSERT_TRUE(2 == r1.value || 4 == r1.value); - - r2 = q.dequeue(); - ASSERT_TRUE(2 == r2.value || 4 == r2.value); - - ASSERT_NE(r1.value, r2.value); -} - - -TEST(mClockPriorityQueue, EnqueuFront) -{ - ceph::mClockQueue<Request,Client> q(&client_info_func); - - Client c1(1); - Client c2(2); - - // non-strict queue ignores priorites, but will divide between - // clients evenly and maintain orders between clients - q.enqueue(c1, 1, 1u, Request(1)); - q.enqueue(c1, 2, 1u, Request(2)); - q.enqueue(c2, 3, 1u, Request(3)); - q.enqueue(c2, 4, 1u, Request(4)); - q.enqueue_strict(c2, 6, Request(6)); - q.enqueue_strict(c1, 7, Request(7)); - - std::list<Request> reqs; - - for (uint i = 0; i < 4; ++i) { - reqs.emplace_back(q.dequeue()); - } - - for (uint i = 0; i < 4; ++i) { - Request& r = reqs.front(); - if (r.value > 5) { - q.enqueue_strict_front(r.value == 6 ? c2 : 1, r.value, std::move(r)); - } else { - q.enqueue_front(r.value <= 2 ? c1 : c2, r.value, 0, std::move(r)); - } - reqs.pop_front(); - } - - Request r; - - r = q.dequeue(); - ASSERT_EQ(7, r.value); - - r = q.dequeue(); - ASSERT_EQ(6, r.value); - - r = q.dequeue(); - ASSERT_TRUE(1 == r.value || 3 == r.value); - - r = q.dequeue(); - ASSERT_TRUE(1 == r.value || 3 == r.value); - - r = q.dequeue(); - ASSERT_TRUE(2 == r.value || 4 == r.value); - - r = q.dequeue(); - ASSERT_TRUE(2 == r.value || 4 == r.value); -} - - -TEST(mClockPriorityQueue, RemoveByClass) -{ - ceph::mClockQueue<Request,Client> q(&client_info_func); - - Client c1(1); - Client c2(2); - Client c3(3); - - q.enqueue(c1, 1, 1u, Request(1)); - q.enqueue(c2, 1, 1u, Request(2)); - q.enqueue(c3, 1, 1u, Request(4)); - q.enqueue_strict(c1, 2, Request(8)); - q.enqueue_strict(c2, 1, Request(16)); - q.enqueue_strict(c3, 3, Request(32)); - q.enqueue(c3, 1, 1u, Request(64)); - q.enqueue(c2, 1, 1u, Request(128)); - q.enqueue(c1, 1, 1u, Request(256)); - - int out_mask = 2 | 16 | 128; - int in_mask = 1 | 8 | 256; - - std::list<Request> out; - q.remove_by_class(c2, &out); - - ASSERT_EQ(3u, out.size()); - while (!out.empty()) { - ASSERT_TRUE((out.front().value & out_mask) > 0) << - "had value that was not expected after first removal"; - out.pop_front(); - } - - ASSERT_EQ(6u, q.get_size_slow()) << "after removal of three from client c2"; - - q.remove_by_class(c3); - - ASSERT_EQ(3u, q.get_size_slow()) << "after removal of three from client c3"; - while (!q.empty()) { - Request r = q.dequeue(); - ASSERT_TRUE((r.value & in_mask) > 0) << - "had value that was not expected after two removals"; - } -} - - -TEST(mClockPriorityQueue, RemoveByFilter) -{ - ceph::mClockQueue<Request,Client> q(&client_info_func); - - Client c1(1); - Client c2(2); - Client c3(3); - - q.enqueue(c1, 1, 1u, Request(1)); - q.enqueue(c2, 1, 1u, Request(2)); - q.enqueue(c3, 1, 1u, Request(3)); - q.enqueue_strict(c1, 2, Request(4)); - q.enqueue_strict(c2, 1, Request(5)); - q.enqueue_strict(c3, 3, Request(6)); - q.enqueue(c3, 1, 1u, Request(7)); - q.enqueue(c2, 1, 1u, Request(8)); - q.enqueue(c1, 1, 1u, Request(9)); - - std::list<Request> filtered; - - q.remove_by_filter([&](const Request& r) -> bool { - if (r.value & 2) { - filtered.push_back(r); - return true; - } else { - return false; - } - }); - - ASSERT_EQ(4u, filtered.size()) << - "filter should have removed four elements"; - while (!filtered.empty()) { - ASSERT_TRUE((filtered.front().value & 2) > 0) << - "expect this value to have been filtered out"; - filtered.pop_front(); - } - - ASSERT_EQ(5u, q.get_size_slow()) << - "filter should have left five remaining elements"; - while (!q.empty()) { - Request r = q.dequeue(); - ASSERT_TRUE((r.value & 2) == 0) << - "expect this value to have been left in"; - } -} diff --git a/src/test/libcephfs/CMakeLists.txt b/src/test/libcephfs/CMakeLists.txt index 672e6dd8f..09cb7e6de 100644 --- a/src/test/libcephfs/CMakeLists.txt +++ b/src/test/libcephfs/CMakeLists.txt @@ -56,7 +56,8 @@ if(WITH_LIBCEPHFS) add_executable(ceph_test_libcephfs_reclaim reclaim.cc ) - target_link_libraries(ceph_test_libcephfs_reclaim + target_link_libraries(ceph_test_libcephfs_reclaim + ceph-common cephfs ${UNITTEST_LIBS} ${EXTRALIBS} @@ -69,7 +70,8 @@ if(WITH_LIBCEPHFS) add_executable(ceph_test_libcephfs_lazyio lazyio.cc ) - target_link_libraries(ceph_test_libcephfs_lazyio +target_link_libraries(ceph_test_libcephfs_lazyio + ceph-common cephfs librados ${UNITTEST_LIBS} @@ -80,7 +82,6 @@ if(WITH_LIBCEPHFS) DESTINATION ${CMAKE_INSTALL_BINDIR}) add_executable(ceph_test_libcephfs_access - test.cc access.cc ) target_link_libraries(ceph_test_libcephfs_access diff --git a/src/test/libcephfs/access.cc b/src/test/libcephfs/access.cc index 57b1a89fa..1260a23e5 100644 --- a/src/test/libcephfs/access.cc +++ b/src/test/libcephfs/access.cc @@ -74,13 +74,13 @@ int do_mon_command(string s, string *key) return r; } -string get_unique_dir() +string get_unique_dir(string name) { - return string("/ceph_test_libcephfs_access.") + stringify(rand()); + return string("/ceph_test_libcephfs_access.") + name + string(".") + stringify(rand()); } TEST(AccessTest, Foo) { - string dir = get_unique_dir(); + string dir = get_unique_dir("foo"); string user = "libcephfs_foo_test." + stringify(rand()); // admin mount to set up test struct ceph_mount_info *admin; @@ -113,8 +113,8 @@ TEST(AccessTest, Foo) { } TEST(AccessTest, Path) { - string good = get_unique_dir(); - string bad = get_unique_dir(); + string good = get_unique_dir("good"); + string bad = get_unique_dir("bad"); string user = "libcephfs_path_test." + stringify(rand()); struct ceph_mount_info *admin; ASSERT_EQ(0, ceph_create(&admin, NULL)); @@ -199,8 +199,8 @@ TEST(AccessTest, Path) { } TEST(AccessTest, ReadOnly) { - string dir = get_unique_dir(); - string dir2 = get_unique_dir(); + string dir = get_unique_dir("dir"); + string dir2 = get_unique_dir("dir2"); string user = "libcephfs_readonly_test." + stringify(rand()); struct ceph_mount_info *admin; ASSERT_EQ(0, ceph_create(&admin, NULL)); @@ -243,7 +243,7 @@ TEST(AccessTest, ReadOnly) { } TEST(AccessTest, User) { - string dir = get_unique_dir(); + string dir = get_unique_dir("user"); string user = "libcephfs_user_test." + stringify(rand()); // admin mount to set up test diff --git a/src/test/libcephfs/test.cc b/src/test/libcephfs/test.cc index c83ddccf9..57c5eefa6 100644 --- a/src/test/libcephfs/test.cc +++ b/src/test/libcephfs/test.cc @@ -3711,8 +3711,11 @@ TEST(LibCephFS, SnapdirAttrsOnSnapDelete) { struct ceph_statx stx_snap_dir_2; ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir_2, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + // Flaky assertion on Windows, potentially due to timestamp precision. + #ifndef _WIN32 ASSERT_LT(utime_t(stx_snap_dir_1.stx_mtime), utime_t(stx_snap_dir_2.stx_mtime)); ASSERT_LT(utime_t(stx_snap_dir_1.stx_ctime), utime_t(stx_snap_dir_2.stx_ctime)); + #endif ASSERT_LT(stx_snap_dir_1.stx_version, stx_snap_dir_2.stx_version); ASSERT_EQ(0, ceph_rmdir(cmount, dir_path)); @@ -3764,8 +3767,11 @@ TEST(LibCephFS, SnapdirAttrsOnSnapRename) { struct ceph_statx stx_snap_dir_2; ASSERT_EQ(ceph_statx(cmount, snap_dir_path, &stx_snap_dir_2, CEPH_STATX_MTIME|CEPH_STATX_CTIME|CEPH_STATX_VERSION, 0), 0); + // Flaky assertion on Windows, potentially due to timestamp precision. + #ifndef _WIN32 ASSERT_LT(utime_t(stx_snap_dir_1.stx_mtime), utime_t(stx_snap_dir_2.stx_mtime)); ASSERT_LT(utime_t(stx_snap_dir_1.stx_ctime), utime_t(stx_snap_dir_2.stx_ctime)); + #endif ASSERT_LT(stx_snap_dir_1.stx_version, stx_snap_dir_2.stx_version); ASSERT_EQ(0, ceph_rmdir(cmount, snap_path_r)); diff --git a/src/test/librados/snapshots_cxx.cc b/src/test/librados/snapshots_cxx.cc index 8098b2cb7..95dbe5da0 100644 --- a/src/test/librados/snapshots_cxx.cc +++ b/src/test/librados/snapshots_cxx.cc @@ -25,9 +25,9 @@ TEST_F(LibRadosSnapshotsPP, SnapListPP) { bufferlist bl1; bl1.append(buf, sizeof(buf)); ASSERT_EQ(0, ioctx.write("foo", bl1, sizeof(buf), 0)); - ASSERT_FALSE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name)); + ASSERT_EQ(0, cluster.pool_is_in_selfmanaged_snaps_mode(pool_name)); ASSERT_EQ(0, ioctx.snap_create("snap1")); - ASSERT_FALSE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name)); + ASSERT_EQ(0, cluster.pool_is_in_selfmanaged_snaps_mode(pool_name)); std::vector<snap_t> snaps; EXPECT_EQ(0, ioctx.snap_list(&snaps)); EXPECT_EQ(1U, snaps.size()); @@ -35,7 +35,7 @@ TEST_F(LibRadosSnapshotsPP, SnapListPP) { EXPECT_EQ(0, ioctx.snap_lookup("snap1", &rid)); EXPECT_EQ(rid, snaps[0]); EXPECT_EQ(0, ioctx.snap_remove("snap1")); - ASSERT_FALSE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name)); + ASSERT_EQ(0, cluster.pool_is_in_selfmanaged_snaps_mode(pool_name)); } TEST_F(LibRadosSnapshotsPP, SnapRemovePP) { @@ -109,9 +109,9 @@ TEST_F(LibRadosSnapshotsPP, SnapCreateRemovePP) { TEST_F(LibRadosSnapshotsSelfManagedPP, SnapPP) { std::vector<uint64_t> my_snaps; my_snaps.push_back(-2); - ASSERT_FALSE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name)); + ASSERT_EQ(0, cluster.pool_is_in_selfmanaged_snaps_mode(pool_name)); ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back())); - ASSERT_TRUE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name)); + ASSERT_EQ(1, cluster.pool_is_in_selfmanaged_snaps_mode(pool_name)); ::std::reverse(my_snaps.begin(), my_snaps.end()); ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps)); ::std::reverse(my_snaps.begin(), my_snaps.end()); @@ -148,7 +148,7 @@ TEST_F(LibRadosSnapshotsSelfManagedPP, SnapPP) { ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(my_snaps.back())); my_snaps.pop_back(); ioctx.snap_set_read(LIBRADOS_SNAP_HEAD); - ASSERT_TRUE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name)); + ASSERT_EQ(1, cluster.pool_is_in_selfmanaged_snaps_mode(pool_name)); ASSERT_EQ(0, ioctx.remove("foo")); } @@ -509,7 +509,7 @@ TEST_F(LibRadosSnapshotsSelfManagedPP, ReusePurgedSnap) { std::vector<uint64_t> my_snaps; my_snaps.push_back(-2); ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&my_snaps.back())); - ASSERT_TRUE(cluster.get_pool_is_selfmanaged_snaps_mode(pool_name)); + ASSERT_EQ(1, cluster.pool_is_in_selfmanaged_snaps_mode(pool_name)); ::std::reverse(my_snaps.begin(), my_snaps.end()); ASSERT_EQ(0, ioctx.selfmanaged_snap_set_write_ctx(my_snaps[0], my_snaps)); ::std::reverse(my_snaps.begin(), my_snaps.end()); @@ -548,6 +548,52 @@ TEST_F(LibRadosSnapshotsSelfManagedPP, ReusePurgedSnap) { //sleep(600); } +TEST(LibRadosPoolIsInSelfmanagedSnapsMode, NotConnected) { + librados::Rados cluster; + ASSERT_EQ(0, cluster.init(nullptr)); + + EXPECT_EQ(-ENOTCONN, cluster.pool_is_in_selfmanaged_snaps_mode("foo")); +} + +TEST(LibRadosPoolIsInSelfmanagedSnapsMode, FreshInstance) { + librados::Rados cluster1; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster1)); + EXPECT_EQ(0, cluster1.pool_is_in_selfmanaged_snaps_mode(pool_name)); + { + librados::Rados cluster2; + ASSERT_EQ("", connect_cluster_pp(cluster2)); + EXPECT_EQ(0, cluster2.pool_is_in_selfmanaged_snaps_mode(pool_name)); + } + + librados::IoCtx ioctx; + cluster1.ioctx_create(pool_name.c_str(), ioctx); + uint64_t snap_id; + ASSERT_EQ(0, ioctx.selfmanaged_snap_create(&snap_id)); + EXPECT_EQ(1, cluster1.pool_is_in_selfmanaged_snaps_mode(pool_name)); + { + librados::Rados cluster2; + ASSERT_EQ("", connect_cluster_pp(cluster2)); + EXPECT_EQ(1, cluster2.pool_is_in_selfmanaged_snaps_mode(pool_name)); + } + + ASSERT_EQ(0, ioctx.selfmanaged_snap_remove(snap_id)); + EXPECT_EQ(1, cluster1.pool_is_in_selfmanaged_snaps_mode(pool_name)); + { + librados::Rados cluster2; + ASSERT_EQ("", connect_cluster_pp(cluster2)); + EXPECT_EQ(1, cluster2.pool_is_in_selfmanaged_snaps_mode(pool_name)); + } + + ASSERT_EQ(0, cluster1.pool_delete(pool_name.c_str())); + EXPECT_EQ(-ENOENT, cluster1.pool_is_in_selfmanaged_snaps_mode(pool_name)); + { + librados::Rados cluster2; + ASSERT_EQ("", connect_cluster_pp(cluster2)); + EXPECT_EQ(-ENOENT, cluster2.pool_is_in_selfmanaged_snaps_mode(pool_name)); + } +} + // EC testing TEST_F(LibRadosSnapshotsECPP, SnapListPP) { SKIP_IF_CRIMSON(); diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index 87984bd94..d12b51f69 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -19,7 +19,8 @@ set(librbd_test test_Operations.cc test_Trash.cc journal/test_Entries.cc - journal/test_Replay.cc) + journal/test_Replay.cc + journal/test_Stress.cc) add_library(rbd_test STATIC ${librbd_test}) target_link_libraries(rbd_test PRIVATE rbd_test_support diff --git a/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc b/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc index e38ffffdb..2c42d5075 100644 --- a/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc +++ b/src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc @@ -92,6 +92,7 @@ struct DiffRequest<MockTestImageCtx> { static DiffRequest* s_instance; static DiffRequest* create(MockTestImageCtx *image_ctx, uint64_t snap_id_start, uint64_t snap_id_end, + uint64_t start_object_no, uint64_t end_object_no, BitVector<2>* object_diff_state, Context* on_finish) { ceph_assert(s_instance != nullptr); diff --git a/src/test/librbd/io/test_mock_ImageRequest.cc b/src/test/librbd/io/test_mock_ImageRequest.cc index 9d6423d66..6ee67fe5f 100644 --- a/src/test/librbd/io/test_mock_ImageRequest.cc +++ b/src/test/librbd/io/test_mock_ImageRequest.cc @@ -16,12 +16,15 @@ namespace { struct MockTestImageCtx; struct MockTestJournal : public MockJournal { - MOCK_METHOD4(append_write_event, uint64_t(uint64_t, size_t, + MOCK_METHOD3(append_write_event, uint64_t(const io::Extents&, const bufferlist &, bool)); + MOCK_METHOD3(append_write_same_event, uint64_t(const io::Extents&, + const bufferlist &, bool)); MOCK_METHOD5(append_compare_and_write_event, uint64_t(uint64_t, size_t, const bufferlist &, const bufferlist &, bool)); + MOCK_METHOD3(append_discard_event, uint64_t(const io::Extents&, uint32_t, bool)); MOCK_METHOD5(append_io_event_mock, uint64_t(const journal::EventEntry&, uint64_t, size_t, bool, int)); uint64_t append_io_event(journal::EventEntry &&event_entry, @@ -119,9 +122,10 @@ struct TestMockIoImageRequest : public TestMockFixture { } } - void expect_journal_append_io_event(MockTestJournal &mock_journal, uint64_t journal_tid, - uint64_t offset, size_t length) { - EXPECT_CALL(mock_journal, append_io_event_mock(_, offset, length, _, _)) + void expect_journal_append_discard_event(MockTestJournal &mock_journal, + uint64_t journal_tid, + const io::Extents& extents) { + EXPECT_CALL(mock_journal, append_discard_event(extents, _, _)) .WillOnce(Return(journal_tid)); } @@ -386,8 +390,8 @@ TEST_F(TestMockIoImageRequest, PartialDiscardJournalAppendEnabled) { InSequence seq; expect_get_modify_timestamp(mock_image_ctx, false); expect_is_journal_appending(mock_journal, true); - expect_journal_append_io_event(mock_journal, 0, 16, 63); - expect_journal_append_io_event(mock_journal, 1, 84, 100); + expect_journal_append_discard_event(mock_journal, 0, + {{16, 63}, {84, 100}}); expect_object_discard_request(mock_image_ctx, 0, 16, 63, 0); expect_object_discard_request(mock_image_ctx, 0, 84, 100, 0); @@ -419,8 +423,8 @@ TEST_F(TestMockIoImageRequest, TailDiscardJournalAppendEnabled) { InSequence seq; expect_get_modify_timestamp(mock_image_ctx, false); expect_is_journal_appending(mock_journal, true); - expect_journal_append_io_event( - mock_journal, 0, ictx->layout.object_size - 1024, 1024); + expect_journal_append_discard_event( + mock_journal, 0, {{ictx->layout.object_size - 1024, 1024}}); expect_object_discard_request( mock_image_ctx, 0, ictx->layout.object_size - 1024, 1024, 0); @@ -452,7 +456,7 @@ TEST_F(TestMockIoImageRequest, PruneRequiredDiscardJournalAppendEnabled) { InSequence seq; expect_get_modify_timestamp(mock_image_ctx, false); expect_is_journal_appending(mock_journal, true); - EXPECT_CALL(mock_journal, append_io_event_mock(_, _, _, _, _)).Times(0); + EXPECT_CALL(mock_journal, append_discard_event(_, _, _)).Times(0); EXPECT_CALL(*mock_image_ctx.io_object_dispatcher, send(_)).Times(0); C_SaferCond aio_comp_ctx; @@ -482,7 +486,7 @@ TEST_F(TestMockIoImageRequest, LengthModifiedDiscardJournalAppendEnabled) { InSequence seq; expect_get_modify_timestamp(mock_image_ctx, false); expect_is_journal_appending(mock_journal, true); - expect_journal_append_io_event(mock_journal, 0, 32, 32); + expect_journal_append_discard_event(mock_journal, 0, {{32, 32}}); expect_object_discard_request(mock_image_ctx, 0, 32, 32, 0); C_SaferCond aio_comp_ctx; @@ -513,10 +517,9 @@ TEST_F(TestMockIoImageRequest, DiscardGranularityJournalAppendEnabled) { InSequence seq; expect_get_modify_timestamp(mock_image_ctx, false); expect_is_journal_appending(mock_journal, true); - expect_journal_append_io_event(mock_journal, 0, 32, 32); - expect_journal_append_io_event(mock_journal, 1, 96, 64); - expect_journal_append_io_event( - mock_journal, 2, ictx->layout.object_size - 32, 32); + expect_journal_append_discard_event( + mock_journal, 0, + {{32, 32}, {96, 64}, {ictx->layout.object_size - 32, 32}}); expect_object_discard_request(mock_image_ctx, 0, 32, 32, 0); expect_object_discard_request(mock_image_ctx, 0, 96, 64, 0); expect_object_discard_request( diff --git a/src/test/librbd/io/test_mock_ObjectRequest.cc b/src/test/librbd/io/test_mock_ObjectRequest.cc index 0690b7722..97cf63bf4 100644 --- a/src/test/librbd/io/test_mock_ObjectRequest.cc +++ b/src/test/librbd/io/test_mock_ObjectRequest.cc @@ -1784,6 +1784,432 @@ TEST_F(TestMockIoObjectRequest, ListSnaps) { ASSERT_EQ(expected_snapshot_delta, snapshot_delta); } +TEST_F(TestMockIoObjectRequest, ListSnapsGrowFromSizeAtStart) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3, 4}; + + librados::snap_set_t snap_set; + snap_set.seq = 4; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {3}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 512}}; + clone_info.size = 512; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 4; + clone_info.snaps = {4}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 2048}}; + clone_info.size = 2048; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = CEPH_NOSNAP; + clone_info.snaps = {}; + clone_info.overlap = {}; + clone_info.size = 3072; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 2048, 1024, {SPARSE_EXTENT_STATE_DATA, 1024}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {3, 4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{4,4}].insert( + 512, 1536, {SPARSE_EXTENT_STATE_DATA, 1536}); + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 2048, 1024, {SPARSE_EXTENT_STATE_DATA, 1024}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsTruncateFromSizeAtStart) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3, 4}; + + librados::snap_set_t snap_set; + snap_set.seq = 4; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {3}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 512}}; + clone_info.size = 512; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 4; + clone_info.snaps = {4}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 1536}}; + clone_info.size = 2048; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = CEPH_NOSNAP; + clone_info.snaps = {}; + clone_info.overlap = {}; + clone_info.size = 1536; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1536, 512, {SPARSE_EXTENT_STATE_ZEROED, 512}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {3, 4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{4,4}].insert( + 512, 1536, {SPARSE_EXTENT_STATE_DATA, 1536}); + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1536, 512, {SPARSE_EXTENT_STATE_ZEROED, 512}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsTruncateFromBelowSizeAtStart) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3, 4, 5}; + + librados::snap_set_t snap_set; + snap_set.seq = 5; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {3}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 512}}; + clone_info.size = 512; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 4; + clone_info.snaps = {4}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 1536}}; + clone_info.size = 2048; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 5; + clone_info.snaps = {5}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 1024}}; + clone_info.size = 1536; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = CEPH_NOSNAP; + clone_info.snaps = {}; + clone_info.overlap = {}; + clone_info.size = 1024; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1024, 1024, {SPARSE_EXTENT_STATE_ZEROED, 1024}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {3, 4, 5, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{4,4}].insert( + 512, 1536, {SPARSE_EXTENT_STATE_DATA, 1536}); + expected_snapshot_delta[{5,5}].insert( + 1536, 512, {SPARSE_EXTENT_STATE_ZEROED, 512}); + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1024, 512, {SPARSE_EXTENT_STATE_ZEROED, 512}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsTruncateStraddlingSizeAtStart) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3, 4, 5}; + + librados::snap_set_t snap_set; + snap_set.seq = 5; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {3}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 512}}; + clone_info.size = 512; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 4; + clone_info.snaps = {4}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 2048}}; + clone_info.size = 2048; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 5; + clone_info.snaps = {5}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 1536}}; + clone_info.size = 3072; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = CEPH_NOSNAP; + clone_info.snaps = {}; + clone_info.overlap = {}; + clone_info.size = 1536; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1536, 512, {SPARSE_EXTENT_STATE_ZEROED, 512}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {3, 4, 5, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{4,4}].insert( + 512, 1536, {SPARSE_EXTENT_STATE_DATA, 1536}); + expected_snapshot_delta[{5,5}].insert( + 2048, 1024, {SPARSE_EXTENT_STATE_DATA, 1024}); + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1536, 1536, {SPARSE_EXTENT_STATE_ZEROED, 1536}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsTruncateToSizeAtStart) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3, 4, 5}; + + librados::snap_set_t snap_set; + snap_set.seq = 5; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {3}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 512}}; + clone_info.size = 512; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 4; + clone_info.snaps = {4}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 2048}}; + clone_info.size = 2048; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 5; + clone_info.snaps = {5}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 2048}}; + clone_info.size = 3072; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = CEPH_NOSNAP; + clone_info.snaps = {}; + clone_info.overlap = {}; + clone_info.size = 2048; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {3, 4, 5, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{4,4}].insert( + 512, 1536, {SPARSE_EXTENT_STATE_DATA, 1536}); + expected_snapshot_delta[{5,5}].insert( + 2048, 1024, {SPARSE_EXTENT_STATE_DATA, 1024}); + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 2048, 1024, {SPARSE_EXTENT_STATE_ZEROED, 1024}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsTruncateToAboveSizeAtStart) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3, 4, 5}; + + librados::snap_set_t snap_set; + snap_set.seq = 5; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {3}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 512}}; + clone_info.size = 512; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 4; + clone_info.snaps = {4}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 2048}}; + clone_info.size = 2048; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = 5; + clone_info.snaps = {5}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 2560}}; + clone_info.size = 3072; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = CEPH_NOSNAP; + clone_info.snaps = {}; + clone_info.overlap = {}; + clone_info.size = 2560; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 2048, 512, {SPARSE_EXTENT_STATE_DATA, 512}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size}}, + {3, 4, 5, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{4,4}].insert( + 512, 1536, {SPARSE_EXTENT_STATE_DATA, 1536}); + expected_snapshot_delta[{5,5}].insert( + 2048, 1024, {SPARSE_EXTENT_STATE_DATA, 1024}); + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 2560, 512, {SPARSE_EXTENT_STATE_ZEROED, 512}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + TEST_F(TestMockIoObjectRequest, ListSnapsENOENT) { librbd::ImageCtx *ictx; ASSERT_EQ(0, open_image(m_image_name, &ictx)); @@ -1926,7 +2352,7 @@ TEST_F(TestMockIoObjectRequest, ListSnapsWholeObject) { ASSERT_EQ(0, open_image(m_image_name, &ictx)); MockTestImageCtx mock_image_ctx(*ictx); - mock_image_ctx.parent = &mock_image_ctx; + mock_image_ctx.snaps = {3}; InSequence seq; @@ -1937,13 +2363,243 @@ TEST_F(TestMockIoObjectRequest, ListSnapsWholeObject) { clone_info.cloneid = 3; clone_info.snaps = {3}; clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{{0, 1}}; - clone_info.size = 4194304; + clone_info.size = mock_image_ctx.layout.object_size; snap_set.clones.push_back(clone_info); clone_info.cloneid = CEPH_NOSNAP; clone_info.snaps = {}; clone_info.overlap = {}; - clone_info.size = 4194304; + clone_info.size = mock_image_ctx.layout.object_size; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size - 1}}, + {3, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1, mock_image_ctx.layout.object_size - 2, + {SPARSE_EXTENT_STATE_DATA, mock_image_ctx.layout.object_size - 2}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size - 1}}, + {3, CEPH_NOSNAP}, LIST_SNAPS_FLAG_WHOLE_OBJECT, {}, &snapshot_delta, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 0, mock_image_ctx.layout.object_size - 1, + {SPARSE_EXTENT_STATE_DATA, mock_image_ctx.layout.object_size - 1}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsWholeObjectTruncate) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3}; + + InSequence seq; + + librados::snap_set_t snap_set; + snap_set.seq = 3; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {3}; + clone_info.overlap = std::vector<std::pair<uint64_t,uint64_t>>{ + {2, mock_image_ctx.layout.object_size - 4}}; + clone_info.size = mock_image_ctx.layout.object_size; + snap_set.clones.push_back(clone_info); + + clone_info.cloneid = CEPH_NOSNAP; + clone_info.snaps = {}; + clone_info.overlap = {}; + clone_info.size = mock_image_ctx.layout.object_size - 2; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{1, mock_image_ctx.layout.object_size - 2}}, + {3, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1, 1, {SPARSE_EXTENT_STATE_DATA, 1}); + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + mock_image_ctx.layout.object_size - 2, 1, + {SPARSE_EXTENT_STATE_ZEROED, 1}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{1, mock_image_ctx.layout.object_size - 2}}, + {3, CEPH_NOSNAP}, LIST_SNAPS_FLAG_WHOLE_OBJECT, {}, &snapshot_delta, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1, mock_image_ctx.layout.object_size - 2, + {SPARSE_EXTENT_STATE_DATA, mock_image_ctx.layout.object_size - 2}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsWholeObjectRemove) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3}; + + InSequence seq; + + librados::snap_set_t snap_set; + snap_set.seq = 3; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {3}; + clone_info.overlap = {}; + clone_info.size = mock_image_ctx.layout.object_size - 2; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{1, mock_image_ctx.layout.object_size - 2}}, + {3, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1, mock_image_ctx.layout.object_size - 3, + {SPARSE_EXTENT_STATE_ZEROED, mock_image_ctx.layout.object_size - 3}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{1, mock_image_ctx.layout.object_size - 2}}, + {3, CEPH_NOSNAP}, LIST_SNAPS_FLAG_WHOLE_OBJECT, {}, &snapshot_delta, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + SnapshotDelta expected_snapshot_delta; + expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( + 1, mock_image_ctx.layout.object_size - 2, + {SPARSE_EXTENT_STATE_ZEROED, mock_image_ctx.layout.object_size - 2}); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsWholeObjectEndSize) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3}; + + InSequence seq; + + librados::snap_set_t snap_set; + snap_set.seq = 3; + librados::clone_info_t clone_info; + + clone_info.cloneid = CEPH_NOSNAP; + clone_info.snaps = {}; + clone_info.overlap = {}; + // smaller than object extent (i.e. the op) to test end_size handling + clone_info.size = mock_image_ctx.layout.object_size - 2; + snap_set.clones.push_back(clone_info); + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size - 1}}, + {4, CEPH_NOSNAP}, 0, {}, &snapshot_delta, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + EXPECT_TRUE(snapshot_delta.empty()); + } + + expect_list_snaps(mock_image_ctx, snap_set, 0); + + { + SnapshotDelta snapshot_delta; + C_SaferCond ctx; + auto req = MockObjectListSnapsRequest::create( + &mock_image_ctx, 0, {{0, mock_image_ctx.layout.object_size - 1}}, + {4, CEPH_NOSNAP}, LIST_SNAPS_FLAG_WHOLE_OBJECT, {}, &snapshot_delta, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); + + EXPECT_TRUE(snapshot_delta.empty()); + } +} + +TEST_F(TestMockIoObjectRequest, ListSnapsNoSnapsInSnapSet) { + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + + MockTestImageCtx mock_image_ctx(*ictx); + mock_image_ctx.snaps = {3}; + + InSequence seq; + + librados::snap_set_t snap_set; + snap_set.seq = 3; + librados::clone_info_t clone_info; + + clone_info.cloneid = 3; + clone_info.snaps = {}; + clone_info.overlap = {}; + clone_info.size = 0; snap_set.clones.push_back(clone_info); expect_list_snaps(mock_image_ctx, snap_set, 0); @@ -1960,7 +2616,147 @@ TEST_F(TestMockIoObjectRequest, ListSnapsWholeObject) { expected_snapshot_delta[{CEPH_NOSNAP,CEPH_NOSNAP}].insert( 0, mock_image_ctx.layout.object_size - 1, {SPARSE_EXTENT_STATE_DATA, mock_image_ctx.layout.object_size - 1}); - ASSERT_EQ(expected_snapshot_delta, snapshot_delta); + EXPECT_EQ(expected_snapshot_delta, snapshot_delta); +} + +TEST(SparseExtents, Split) { + SparseExtents extents; + extents.insert(50, 100, {SPARSE_EXTENT_STATE_DATA, 100}); + extents.erase(80, 30); + extents.insert(45, 10, {SPARSE_EXTENT_STATE_ZEROED, 10}); + extents.insert(140, 20, {SPARSE_EXTENT_STATE_DNE, 20}); + extents.insert(125, 5, {SPARSE_EXTENT_STATE_ZEROED, 5}); + + SparseExtents expected_extents = { + {45, {10, {SPARSE_EXTENT_STATE_ZEROED, 10}}}, + {55, {25, {SPARSE_EXTENT_STATE_DATA, 25}}}, + {110, {15, {SPARSE_EXTENT_STATE_DATA, 15}}}, + {125, {5, {SPARSE_EXTENT_STATE_ZEROED, 5}}}, + {130, {10, {SPARSE_EXTENT_STATE_DATA, 10}}}, + {140, {20, {SPARSE_EXTENT_STATE_DNE, 20}}} + }; + EXPECT_EQ(expected_extents, extents); +} + +TEST(SparseExtents, Merge) { + SparseExtents extents; + extents.insert(50, 100, {SPARSE_EXTENT_STATE_DATA, 100}); + extents.insert(30, 15, {SPARSE_EXTENT_STATE_ZEROED, 15}); + extents.insert(45, 10, {SPARSE_EXTENT_STATE_DATA, 10}); + extents.insert(200, 40, {SPARSE_EXTENT_STATE_DNE, 40}); + extents.insert(160, 25, {SPARSE_EXTENT_STATE_DNE, 25}); + extents.insert(140, 20, {SPARSE_EXTENT_STATE_DATA, 20}); + extents.insert(25, 5, {SPARSE_EXTENT_STATE_ZEROED, 5}); + extents.insert(185, 15, {SPARSE_EXTENT_STATE_DNE, 15}); + + SparseExtents expected_extents = { + {25, {20, {SPARSE_EXTENT_STATE_ZEROED, 20}}}, + {45, {115, {SPARSE_EXTENT_STATE_DATA, 115}}}, + {160, {80, {SPARSE_EXTENT_STATE_DNE, 80}}} + }; + EXPECT_EQ(expected_extents, extents); +} + +TEST(SparseBufferlist, Split) { + bufferlist bl; + bl.append(std::string(5, '1')); + bl.append(std::string(25, '2')); + bl.append(std::string(30, '3')); + bl.append(std::string(15, '4')); + bl.append(std::string(5, '5')); + bl.append(std::string(10, '6')); + bl.append(std::string(10, '7')); + bufferlist expected_bl1; + expected_bl1.append(std::string(25, '2')); + bufferlist expected_bl2; + expected_bl2.append(std::string(15, '4')); + bufferlist expected_bl3; + expected_bl3.append(std::string(10, '6')); + + SparseBufferlist extents; + extents.insert(50, 100, {SPARSE_EXTENT_STATE_DATA, 100, std::move(bl)}); + extents.erase(80, 30); + extents.insert(45, 10, {SPARSE_EXTENT_STATE_ZEROED, 10}); + extents.insert(140, 20, {SPARSE_EXTENT_STATE_DNE, 20}); + extents.insert(125, 5, {SPARSE_EXTENT_STATE_ZEROED, 5}); + + SparseBufferlist expected_extents = { + {45, {10, {SPARSE_EXTENT_STATE_ZEROED, 10}}}, + {55, {25, {SPARSE_EXTENT_STATE_DATA, 25, std::move(expected_bl1)}}}, + {110, {15, {SPARSE_EXTENT_STATE_DATA, 15, std::move(expected_bl2)}}}, + {125, {5, {SPARSE_EXTENT_STATE_ZEROED, 5}}}, + {130, {10, {SPARSE_EXTENT_STATE_DATA, 10, std::move(expected_bl3)}}}, + {140, {20, {SPARSE_EXTENT_STATE_DNE, 20}}} + }; + EXPECT_EQ(expected_extents, extents); +} + +TEST(SparseBufferlist, SplitData) { + bufferlist bl1; + bl1.append(std::string(100, '1')); + bufferlist bl2; + bl2.append(std::string(15, '2')); + bufferlist bl3; + bl3.append(std::string(40, '3')); + bufferlist bl4; + bl4.append(std::string(10, '4')); + bufferlist expected_bl1 = bl2; + bufferlist expected_bl2; + expected_bl2.append(std::string(35, '1')); + bufferlist expected_bl3 = bl4; + bufferlist expected_bl4; + expected_bl4.append(std::string(30, '1')); + bufferlist expected_bl5; + expected_bl5.append(std::string(5, '3')); + bufferlist expected_bl6; + expected_bl6.append(std::string(15, '3')); + + SparseBufferlist extents; + extents.insert(50, 100, {SPARSE_EXTENT_STATE_DATA, 100, std::move(bl1)}); + extents.insert(40, 15, {SPARSE_EXTENT_STATE_DATA, 15, std::move(bl2)}); + extents.insert(130, 40, {SPARSE_EXTENT_STATE_DATA, 40, std::move(bl3)}); + extents.erase(135, 20); + extents.insert(90, 10, {SPARSE_EXTENT_STATE_DATA, 10, std::move(bl4)}); + + SparseBufferlist expected_extents = { + {40, {15, {SPARSE_EXTENT_STATE_DATA, 15, std::move(expected_bl1)}}}, + {55, {35, {SPARSE_EXTENT_STATE_DATA, 35, std::move(expected_bl2)}}}, + {90, {10, {SPARSE_EXTENT_STATE_DATA, 10, std::move(expected_bl3)}}}, + {100, {30, {SPARSE_EXTENT_STATE_DATA, 30, std::move(expected_bl4)}}}, + {130, {5, {SPARSE_EXTENT_STATE_DATA, 5, std::move(expected_bl5)}}}, + {155, {15, {SPARSE_EXTENT_STATE_DATA, 15, std::move(expected_bl6)}}} + }; + EXPECT_EQ(expected_extents, extents); +} + +TEST(SparseBufferlist, Merge) { + bufferlist bl1; + bl1.append(std::string(100, '1')); + bufferlist bl2; + bl2.append(std::string(10, '2')); + bufferlist bl3; + bl3.append(std::string(20, '3')); + bufferlist expected_bl; + expected_bl.append(std::string(10, '2')); + expected_bl.append(std::string(85, '1')); + expected_bl.append(std::string(20, '3')); + + SparseBufferlist extents; + extents.insert(50, 100, {SPARSE_EXTENT_STATE_DATA, 100, std::move(bl1)}); + extents.insert(30, 15, {SPARSE_EXTENT_STATE_ZEROED, 15}); + extents.insert(45, 10, {SPARSE_EXTENT_STATE_DATA, 10, std::move(bl2)}); + extents.insert(200, 40, {SPARSE_EXTENT_STATE_DNE, 40}); + extents.insert(160, 25, {SPARSE_EXTENT_STATE_DNE, 25}); + extents.insert(140, 20, {SPARSE_EXTENT_STATE_DATA, 20, std::move(bl3)}); + extents.insert(25, 5, {SPARSE_EXTENT_STATE_ZEROED, 5}); + extents.insert(185, 15, {SPARSE_EXTENT_STATE_DNE, 15}); + + SparseBufferlist expected_extents = { + {25, {20, {SPARSE_EXTENT_STATE_ZEROED, 20}}}, + {45, {115, {SPARSE_EXTENT_STATE_DATA, 115, std::move(expected_bl)}}}, + {160, {80, {SPARSE_EXTENT_STATE_DNE, 80}}} + }; + EXPECT_EQ(expected_extents, extents); } } // namespace io diff --git a/src/test/librbd/journal/test_Entries.cc b/src/test/librbd/journal/test_Entries.cc index c392fb9f8..bb4b06c03 100644 --- a/src/test/librbd/journal/test_Entries.cc +++ b/src/test/librbd/journal/test_Entries.cc @@ -196,6 +196,69 @@ TEST_F(TestJournalEntries, AioDiscard) { ASSERT_EQ(234U, aio_discard_event.length); } +TEST_F(TestJournalEntries, AioDiscardWithPrune) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + // The discard path can create multiple image extents (ImageRequest.cc) in the + // case where the discard request needs to be pruned and multiple objects are + // involved in the request. This test ensures that journal event entries are + // queued up for each image extent. + + // Create an image that is multiple objects so that we can force multiple + // image extents on the discard path. + CephContext* cct = reinterpret_cast<CephContext*>(_rados.cct()); + auto object_size = 1ull << cct->_conf.get_val<uint64_t>("rbd_default_order"); + auto image_size = 4 * object_size; + + auto image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_pp(m_rbd, m_ioctx, image_name, image_size)); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(image_name, &ictx)); + + ::journal::Journaler *journaler = create_journaler(ictx); + ASSERT_TRUE(journaler != NULL); + + C_SaferCond cond_ctx; + auto c = librbd::io::AioCompletion::create(&cond_ctx); + c->get(); + // We offset the discard by -4096 bytes and set discard granularity to 8192; + // this should cause two image extents to be formed in + // AbstractImageWriteRequest<I>::send_request(). + api::Io<>::aio_discard(*ictx, c, object_size - 4096, 2 * object_size, 8192, + true); + ASSERT_EQ(0, c->wait_for_complete()); + c->put(); + + for (uint64_t chunk = 0; chunk < 2; chunk++) { + auto offset = object_size; + auto size = object_size; + if (chunk == 1) { + offset = object_size * 2; + size = object_size - 8192; + } + + ::journal::ReplayEntry replay_entry; + if (!journaler->try_pop_front(&replay_entry)) { + ASSERT_TRUE(wait_for_entries_available(ictx)); + ASSERT_TRUE(journaler->try_pop_front(&replay_entry)); + } + + librbd::journal::EventEntry event_entry; + ASSERT_TRUE(get_event_entry(replay_entry, &event_entry)); + + ASSERT_EQ(librbd::journal::EVENT_TYPE_AIO_DISCARD, + event_entry.get_event_type()); + + librbd::journal::AioDiscardEvent aio_discard_event = + boost::get<librbd::journal::AioDiscardEvent>(event_entry.event); + ASSERT_EQ(offset, aio_discard_event.offset); + ASSERT_EQ(size, aio_discard_event.length); + + journaler->committed(replay_entry); + } +} + TEST_F(TestJournalEntries, AioFlush) { REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); diff --git a/src/test/librbd/journal/test_Stress.cc b/src/test/librbd/journal/test_Stress.cc new file mode 100644 index 000000000..d3df9147a --- /dev/null +++ b/src/test/librbd/journal/test_Stress.cc @@ -0,0 +1,121 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librados/test_cxx.h" +#include "test/librbd/test_fixture.h" +#include "test/librbd/test_support.h" +#include "cls/rbd/cls_rbd_types.h" +#include "cls/journal/cls_journal_types.h" +#include "cls/journal/cls_journal_client.h" +#include "journal/Journaler.h" +#include "librbd/ExclusiveLock.h" +#include "librbd/ImageCtx.h" +#include "librbd/ImageState.h" +#include "librbd/ImageWatcher.h" +#include "librbd/internal.h" +#include "librbd/Journal.h" +#include "librbd/Operations.h" +#include "librbd/api/Io.h" +#include "librbd/api/Snapshot.h" +#include "librbd/io/AioCompletion.h" +#include "librbd/io/ImageDispatchSpec.h" +#include "librbd/io/ImageRequest.h" +#include "librbd/io/ReadResult.h" +#include "librbd/journal/Types.h" +#include <boost/scope_exit.hpp> + +void register_test_journal_stress() { +} + +namespace librbd { +namespace journal { + +class TestJournalStress : public TestFixture { +}; + +TEST_F(TestJournalStress, DiscardWithPruneWriteOverlap) { + REQUIRE_FEATURE(RBD_FEATURE_JOURNALING); + + // Overlap discards and writes while discard pruning is occurring. This tests + // the conditions under which https://tracker.ceph.com/issues/63422 occurred. + + // Create an image that is multiple objects so that we can force multiple + // image extents on the discard path. + int order = 22; + auto object_size = uint64_t{1} << order; + auto image_size = 4 * object_size; + + // Write-around cache required for overlapping I/O delays. + std::map<std::string, std::string> config; + config["rbd_cache"] = "true"; + config["rbd_cache_policy"] = "writearound"; + config["rbd_cache_max_dirty"] = std::to_string(image_size); + config["rbd_cache_writethrough_until_flush"] = "false"; + // XXX: Work around https://tracker.ceph.com/issues/63681, which this test + // exposes when run under Valgrind. + config["librados_thread_count"] = "15"; + + librados::Rados rados; + ASSERT_EQ("", connect_cluster_pp(rados, config)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, rados.ioctx_create(_pool_name.c_str(), ioctx)); + + uint64_t features; + ASSERT_TRUE(::get_features(&features)); + auto image_name = get_temp_image_name(); + ASSERT_EQ(0, create_image_full_pp(m_rbd, ioctx, image_name, image_size, + features, false, &order)); + + auto ictx = new librbd::ImageCtx(image_name, "", nullptr, ioctx, false); + ASSERT_EQ(0, ictx->state->open(0)); + BOOST_SCOPE_EXIT(ictx) { + ictx->state->close(); + } BOOST_SCOPE_EXIT_END; + + std::thread write_thread( + [ictx, object_size]() { + std::string payload(object_size, '1'); + + for (auto i = 0; i < 200; i++) { + // Alternate overlaps with the two objects that the discard below + // touches. + for (auto offset = object_size; + offset < object_size * 3; + offset += object_size) { + bufferlist payload_bl; + payload_bl.append(payload); + auto aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_write(*ictx, aio_comp, offset, payload.size(), + std::move(payload_bl), 0, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + } + } + } + ); + + auto discard_exit = false; + std::thread discard_thread( + [ictx, object_size, &discard_exit]() { + while (!discard_exit) { + // We offset the discard by -4096 bytes and set discard granularity to + // 8192; this should cause two image extents to be formed in + // AbstractImageWriteRequest<I>::send_request() on objects 1 and 2, + // overlapping with the writes above. + auto aio_comp = new librbd::io::AioCompletion(); + api::Io<>::aio_discard(*ictx, aio_comp, object_size - 4096, + 2 * object_size, 8192, true); + ASSERT_EQ(0, aio_comp->wait_for_complete()); + aio_comp->release(); + } + } + ); + + write_thread.join(); + discard_exit = true; + discard_thread.join(); +} + +} // namespace journal +} // namespace librbd diff --git a/src/test/librbd/mock/MockObjectMap.h b/src/test/librbd/mock/MockObjectMap.h index 2a1adbcae..5e3235cf0 100644 --- a/src/test/librbd/mock/MockObjectMap.h +++ b/src/test/librbd/mock/MockObjectMap.h @@ -4,19 +4,27 @@ #ifndef CEPH_TEST_LIBRBD_MOCK_OBJECT_MAP_H #define CEPH_TEST_LIBRBD_MOCK_OBJECT_MAP_H +#include "common/bit_vector.hpp" #include "librbd/Utils.h" #include "gmock/gmock.h" namespace librbd { struct MockObjectMap { - MOCK_METHOD1(at, uint8_t(uint64_t)); - uint8_t operator[](uint64_t object_no) { + MOCK_CONST_METHOD1(at, uint8_t(uint64_t)); + uint8_t operator[](uint64_t object_no) const { return at(object_no); } MOCK_CONST_METHOD0(size, uint64_t()); + MOCK_CONST_METHOD0(with, ceph::BitVector<2>()); + template <typename F, typename... Args> + auto with_object_map(F&& f, Args&&... args) const { + const ceph::BitVector<2> object_map = with(); + return std::forward<F>(f)(object_map, std::forward<Args>(args)...); + } + MOCK_METHOD1(open, void(Context *on_finish)); MOCK_METHOD1(close, void(Context *on_finish)); diff --git a/src/test/librbd/object_map/test_mock_DiffRequest.cc b/src/test/librbd/object_map/test_mock_DiffRequest.cc index c25ae4a95..9b88f0d75 100644 --- a/src/test/librbd/object_map/test_mock_DiffRequest.cc +++ b/src/test/librbd/object_map/test_mock_DiffRequest.cc @@ -18,6 +18,8 @@ struct MockTestImageCtx : public MockImageCtx { } }; +void noop(MockTestImageCtx&) {} + } // anonymous namespace } // namespace librbd @@ -26,13 +28,139 @@ struct MockTestImageCtx : public MockImageCtx { using ::testing::_; using ::testing::Invoke; using ::testing::InSequence; +using ::testing::Return; using ::testing::StrEq; using ::testing::WithArg; namespace librbd { namespace object_map { -class TestMockObjectMapDiffRequest : public TestMockFixture { +static constexpr uint8_t from_beginning_table[][2] = { + // to expected + { OBJECT_NONEXISTENT, DIFF_STATE_HOLE }, + { OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED } +}; + +static constexpr uint8_t from_beginning_intermediate_table[][4] = { + // intermediate to diff-iterate expected deep-copy expected + { OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, DIFF_STATE_HOLE, DIFF_STATE_HOLE }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_NONEXISTENT, DIFF_STATE_HOLE, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_NONEXISTENT, DIFF_STATE_HOLE, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, DIFF_STATE_HOLE, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED } +}; + +static constexpr uint8_t from_snap_table[][3] = { + // from to expected + { OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, DIFF_STATE_HOLE }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA }, + { OBJECT_PENDING, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA } +}; + +static constexpr uint8_t from_snap_intermediate_table[][5] = { + // from intermediate to diff-iterate expected deep-copy expected + { OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, DIFF_STATE_HOLE, DIFF_STATE_HOLE }, + { OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS, OBJECT_NONEXISTENT, DIFF_STATE_HOLE, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_PENDING, OBJECT_NONEXISTENT, DIFF_STATE_HOLE, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_PENDING, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_PENDING, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_PENDING, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, DIFF_STATE_HOLE, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS, OBJECT_NONEXISTENT, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_NONEXISTENT, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_PENDING, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS, OBJECT_PENDING, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_PENDING, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_PENDING, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA, DIFF_STATE_DATA }, + { OBJECT_PENDING, OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_PENDING, OBJECT_NONEXISTENT, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_NONEXISTENT, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_PENDING, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_PENDING, OBJECT_PENDING, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_PENDING, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_PENDING, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS_CLEAN, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_PENDING, OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_PENDING, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_PENDING, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_PENDING, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_PENDING, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, OBJECT_NONEXISTENT, DIFF_STATE_HOLE_UPDATED, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, OBJECT_EXISTS, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, OBJECT_PENDING, DIFF_STATE_DATA_UPDATED, DIFF_STATE_DATA_UPDATED }, + { OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, OBJECT_EXISTS_CLEAN, DIFF_STATE_DATA, DIFF_STATE_DATA } +}; + +static constexpr uint8_t shrink_table[][2] = { + // shrunk deep-copy expected + { OBJECT_NONEXISTENT, DIFF_STATE_HOLE }, + { OBJECT_EXISTS, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_PENDING, DIFF_STATE_HOLE_UPDATED }, + { OBJECT_EXISTS_CLEAN, DIFF_STATE_HOLE_UPDATED } +}; + +class TestMockObjectMapDiffRequest : public TestMockFixture, + public ::testing::WithParamInterface<bool> { public: typedef DiffRequest<MockTestImageCtx> MockDiffRequest; @@ -42,6 +170,10 @@ public: ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); } + bool is_diff_iterate() const { + return !GetParam(); + } + void expect_get_flags(MockTestImageCtx& mock_image_ctx, uint64_t snap_id, int32_t flags, int r) { EXPECT_CALL(mock_image_ctx, get_flags(snap_id, _)) @@ -76,418 +208,2077 @@ public: expect_load_map(mock_image_ctx, snap_id, object_map, r, [](){}); } + void expect_with_map(MockTestImageCtx& mock_image_ctx, + const BitVector<2>& object_map) { + EXPECT_CALL(*mock_image_ctx.object_map, with()).WillOnce(Return(object_map)); + } + + template <typename F> + int do_diff(bool want_object_map, F&& f, + uint64_t start_snap_id, uint64_t end_snap_id, + uint64_t start_object_no, uint64_t end_object_no) { + InSequence seq; + + MockTestImageCtx mock_image_ctx(*m_image_ctx); + MockObjectMap mock_object_map; + if (want_object_map) { + mock_image_ctx.object_map = &mock_object_map; + } + mock_image_ctx.snap_id = end_snap_id; + std::forward<F>(f)(mock_image_ctx); + + C_SaferCond ctx; + auto req = new MockDiffRequest(&mock_image_ctx, start_snap_id, + end_snap_id, start_object_no, end_object_no, + &m_diff_state, &ctx); + req->send(); + return ctx.wait(); + } + + template <typename F> + void test_diff_iterate(bool want_object_map, F&& f, + uint64_t start_snap_id, uint64_t end_snap_id, + const BitVector<2>& expected_diff_state) { + // ranged -- run through all ranges (substrings) in expected_diff_state + for (uint64_t i = 0; i < expected_diff_state.size(); i++) { + for (uint64_t j = i + 1; j <= expected_diff_state.size(); j++) { + ASSERT_EQ(0, do_diff(want_object_map, std::forward<F>(f), + start_snap_id, end_snap_id, i, j)); + ASSERT_EQ(j - i, m_diff_state.size()); + for (uint64_t k = 0; k < m_diff_state.size(); k++) { + ASSERT_EQ(expected_diff_state[i + k], m_diff_state[k]); + } + } + } + + // unranged -- equivalent to i=0, j=expected_diff_state.size() range + ASSERT_EQ(0, do_diff(want_object_map, std::forward<F>(f), + start_snap_id, end_snap_id, 0, UINT64_MAX - 1)); + ASSERT_EQ(expected_diff_state, m_diff_state); + } + + template <typename F> + void test_deep_copy(bool want_object_map, F&& f, + uint64_t start_snap_id, uint64_t end_snap_id, + const BitVector<2>& expected_diff_state) { + ASSERT_EQ(0, do_diff(want_object_map, std::forward<F>(f), + start_snap_id, end_snap_id, 0, UINT64_MAX)); + ASSERT_EQ(expected_diff_state, m_diff_state); + } + librbd::ImageCtx* m_image_ctx = nullptr; - BitVector<2> m_object_diff_state; + BitVector<2> m_diff_state; }; -TEST_F(TestMockObjectMapDiffRequest, InvalidStartSnap) { - MockTestImageCtx mock_image_ctx(*m_image_ctx); +TEST_P(TestMockObjectMapDiffRequest, InvalidStartSnap) { + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, noop, CEPH_NOSNAP, CEPH_NOSNAP, 123, 456)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, CEPH_NOSNAP, CEPH_NOSNAP, 123, 456)); + } else { + ASSERT_EQ(-EINVAL, do_diff(false, noop, CEPH_NOSNAP, CEPH_NOSNAP, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, CEPH_NOSNAP, CEPH_NOSNAP, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, InvalidEndSnap) { + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, noop, 2, 1, 123, 456)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, 2, 1, 123, 456)); + } else { + ASSERT_EQ(-EINVAL, do_diff(false, noop, 2, 1, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, 2, 1, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, StartEndSnapEqual) { + BitVector<2> expected_diff_state; - InSequence seq; + if (is_diff_iterate()) { + ASSERT_EQ(0, do_diff(false, noop, 1, 1, 123, 456)); + ASSERT_EQ(expected_diff_state, m_diff_state); + ASSERT_EQ(0, do_diff(true, noop, 1, 1, 123, 456)); + ASSERT_EQ(expected_diff_state, m_diff_state); + } else { + ASSERT_EQ(0, do_diff(false, noop, 1, 1, 0, UINT64_MAX)); + ASSERT_EQ(expected_diff_state, m_diff_state); + ASSERT_EQ(0, do_diff(true, noop, 1, 1, 0, UINT64_MAX)); + ASSERT_EQ(expected_diff_state, m_diff_state); + } +} - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, CEPH_NOSNAP, 0, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(-EINVAL, ctx.wait()); +TEST_P(TestMockObjectMapDiffRequest, InvalidStartObject) { + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, 1, UINT64_MAX, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, 1, UINT64_MAX, UINT64_MAX)); + } else { + ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, 1, 123, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, 1, 123, UINT64_MAX)); + } } -TEST_F(TestMockObjectMapDiffRequest, StartEndSnapEqual) { - MockTestImageCtx mock_image_ctx(*m_image_ctx); +TEST_P(TestMockObjectMapDiffRequest, InvalidEndObject) { + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, 1, 456, 123)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, 1, 456, 123)); + } else { + SUCCEED(); + } +} - InSequence seq; +TEST_P(TestMockObjectMapDiffRequest, StartEndObjectEqual) { + BitVector<2> expected_diff_state; - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 1, 1, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(0, ctx.wait()); - ASSERT_EQ(0U, m_object_diff_state.size()); + if (is_diff_iterate()) { + ASSERT_EQ(0, do_diff(false, noop, 0, 1, 123, 123)); + ASSERT_EQ(expected_diff_state, m_diff_state); + ASSERT_EQ(0, do_diff(true, noop, 0, 1, 123, 123)); + ASSERT_EQ(expected_diff_state, m_diff_state); + } else { + SUCCEED(); + } } -TEST_F(TestMockObjectMapDiffRequest, FastDiffDisabled) { +TEST_P(TestMockObjectMapDiffRequest, FastDiffDisabled) { // negative test -- object-map implicitly enables fast-diff REQUIRE(!is_feature_enabled(RBD_FEATURE_OBJECT_MAP)); - MockTestImageCtx mock_image_ctx(*m_image_ctx); + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, CEPH_NOSNAP, 123, 456)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, CEPH_NOSNAP, 123, 456)); + } else { + ASSERT_EQ(-EINVAL, do_diff(false, noop, 0, CEPH_NOSNAP, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, noop, 0, CEPH_NOSNAP, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = std::size(from_beginning_table); + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; - InSequence seq; + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + for (uint32_t i = 0; i < object_count; i++) { + object_map_1[i] = from_beginning_table[i][0]; + expected_diff_state[i] = from_beginning_table[i][1]; + } - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(-EINVAL, ctx.wait()); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + }; + if (is_diff_iterate()) { + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_with_map(mock_image_ctx, object_map_1); + }; + test_diff_iterate(false, load, 0, 1, expected_diff_state); + test_diff_iterate(true, with, 0, 1, expected_diff_state); + } else { + test_deep_copy(false, load, 0, 1, expected_diff_state); + test_deep_copy(true, load, 0, 1, expected_diff_state); + } } -TEST_F(TestMockObjectMapDiffRequest, FastDiffInvalid) { +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapEmpty) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { + m_image_ctx->size = 0; + m_image_ctx->snap_info = { {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}} }; - InSequence seq; - expect_get_flags(mock_image_ctx, 1U, RBD_FLAG_FAST_DIFF_INVALID, 0); + BitVector<2> object_map_1; + BitVector<2> expected_diff_state; - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(-EINVAL, ctx.wait()); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + }; + if (is_diff_iterate()) { + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_with_map(mock_image_ctx, object_map_1); + }; + test_diff_iterate(false, load, 0, 1, expected_diff_state); + test_diff_iterate(true, with, 0, 1, expected_diff_state); + } else { + test_deep_copy(false, load, 0, 1, expected_diff_state); + test_deep_copy(true, load, 0, 1, expected_diff_state); + } } -TEST_F(TestMockObjectMapDiffRequest, FullDelta) { +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnap) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - uint32_t object_count = 5; + uint32_t object_count = std::size(from_beginning_intermediate_table); m_image_ctx->size = object_count * (1 << m_image_ctx->order); - - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}}, - {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}} }; - InSequence seq; + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> object_map_2; + object_map_2.resize(object_count); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + for (uint32_t i = 0; i < object_count; i++) { + object_map_1[i] = from_beginning_intermediate_table[i][0]; + object_map_2[i] = from_beginning_intermediate_table[i][1]; + if (is_diff_iterate()) { + expected_diff_state[i] = from_beginning_intermediate_table[i][2]; + } else { + expected_diff_state[i] = from_beginning_intermediate_table[i][3]; + } + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_with_map(mock_image_ctx, object_map_2); + }; + test_diff_iterate(false, load, 0, 2, expected_diff_state); + test_diff_iterate(true, with, 0, 2, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + test_deep_copy(false, load, 0, 2, expected_diff_state); + test_deep_copy(true, load, 0, 2, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapGrow) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - expect_get_flags(mock_image_ctx, 1U, 0, 0); + uint32_t object_count_1 = std::size(from_beginning_intermediate_table); + uint32_t object_count_2 = object_count_1 + std::size(from_beginning_table); + m_image_ctx->size = object_count_2 * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, + object_count_2 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; BitVector<2> object_map_1; - object_map_1.resize(object_count); - object_map_1[1] = OBJECT_EXISTS_CLEAN; - expect_load_map(mock_image_ctx, 1U, object_map_1, 0); + object_map_1.resize(object_count_1); + BitVector<2> object_map_2; + object_map_2.resize(object_count_2); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count_2); + for (uint32_t i = 0; i < object_count_1; i++) { + object_map_1[i] = from_beginning_intermediate_table[i][0]; + object_map_2[i] = from_beginning_intermediate_table[i][1]; + if (is_diff_iterate()) { + expected_diff_state[i] = from_beginning_intermediate_table[i][2]; + } else { + expected_diff_state[i] = from_beginning_intermediate_table[i][3]; + } + } + for (uint32_t i = object_count_1; i < object_count_2; i++) { + object_map_2[i] = from_beginning_table[i - object_count_1][0]; + expected_diff_state[i] = from_beginning_table[i - object_count_1][1]; + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_with_map(mock_image_ctx, object_map_2); + }; + test_diff_iterate(false, load, 0, 2, expected_diff_state); + test_diff_iterate(true, with, 0, 2, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + test_deep_copy(false, load, 0, 2, expected_diff_state); + test_deep_copy(true, load, 0, 2, expected_diff_state); + } +} - expect_get_flags(mock_image_ctx, 2U, 0, 0); +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapGrowFromZero) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_2 = std::size(from_beginning_table); + m_image_ctx->size = object_count_2 * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, + object_count_2 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + BitVector<2> object_map_1; BitVector<2> object_map_2; - object_map_2.resize(object_count); - object_map_2[1] = OBJECT_EXISTS_CLEAN; - object_map_2[2] = OBJECT_EXISTS; - object_map_2[3] = OBJECT_EXISTS; - expect_load_map(mock_image_ctx, 2U, object_map_2, 0); + object_map_2.resize(object_count_2); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count_2); + for (uint32_t i = 0; i < object_count_2; i++) { + object_map_2[i] = from_beginning_table[i][0]; + expected_diff_state[i] = from_beginning_table[i][1]; + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_with_map(mock_image_ctx, object_map_2); + }; + test_diff_iterate(false, load, 0, 2, expected_diff_state); + test_diff_iterate(true, with, 0, 2, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + test_deep_copy(false, load, 0, 2, expected_diff_state); + test_deep_copy(true, load, 0, 2, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapShrink) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_2 = std::size(from_beginning_intermediate_table); + uint32_t object_count_1 = object_count_2 + std::size(shrink_table); + m_image_ctx->size = object_count_2 * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, + object_count_2 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_2; + object_map_2.resize(object_count_2); + BitVector<2> expected_diff_state; + if (is_diff_iterate()) { + expected_diff_state.resize(object_count_2); + } else { + expected_diff_state.resize(object_count_1); + } + for (uint32_t i = 0; i < object_count_2; i++) { + object_map_1[i] = from_beginning_intermediate_table[i][0]; + object_map_2[i] = from_beginning_intermediate_table[i][1]; + if (is_diff_iterate()) { + expected_diff_state[i] = from_beginning_intermediate_table[i][2]; + } else { + expected_diff_state[i] = from_beginning_intermediate_table[i][3]; + } + } + for (uint32_t i = object_count_2; i < object_count_1; i++) { + object_map_1[i] = shrink_table[i - object_count_2][0]; + if (!is_diff_iterate()) { + expected_diff_state[i] = shrink_table[i - object_count_2][1]; + } + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_with_map(mock_image_ctx, object_map_2); + }; + test_diff_iterate(false, load, 0, 2, expected_diff_state); + test_diff_iterate(true, with, 0, 2, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + test_deep_copy(false, load, 0, 2, expected_diff_state); + test_deep_copy(true, load, 0, 2, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToSnapIntermediateSnapShrinkToZero) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_1 = std::size(shrink_table); + m_image_ctx->size = 0; + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_2; + BitVector<2> expected_diff_state; + if (!is_diff_iterate()) { + expected_diff_state.resize(object_count_1); + } + for (uint32_t i = 0; i < object_count_1; i++) { + object_map_1[i] = shrink_table[i][0]; + if (!is_diff_iterate()) { + expected_diff_state[i] = shrink_table[i][1]; + } + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_with_map(mock_image_ctx, object_map_2); + }; + test_diff_iterate(false, load, 0, 2, expected_diff_state); + test_diff_iterate(true, with, 0, 2, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + test_deep_copy(false, load, 0, 2, expected_diff_state); + test_deep_copy(true, load, 0, 2, expected_diff_state); + } +} - expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToUnsetSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = std::size(from_beginning_table); + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + for (uint32_t i = 0; i < object_count; i++) { + object_map_1[i] = from_beginning_table[i][0]; + expected_diff_state[i] = from_beginning_table[i][1]; + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + mock_image_ctx.snap_id = 123; + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 0, 1, expected_diff_state); + test_diff_iterate(true, load, 0, 1, expected_diff_state); + } else { + test_deep_copy(false, load, 0, 1, expected_diff_state); + test_deep_copy(true, load, 0, 1, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHead) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = std::size(from_beginning_table); + m_image_ctx->size = object_count * (1 << m_image_ctx->order); BitVector<2> object_map_head; object_map_head.resize(object_count); - object_map_head[1] = OBJECT_EXISTS_CLEAN; - object_map_head[2] = OBJECT_EXISTS_CLEAN; - expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + for (uint32_t i = 0; i < object_count; i++) { + object_map_head[i] = from_beginning_table[i][0]; + expected_diff_state[i] = from_beginning_table[i][1]; + } - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(0, ctx.wait()); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadEmpty) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + m_image_ctx->size = 0; + + BitVector<2> object_map_head; + BitVector<2> expected_diff_state; + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = std::size(from_beginning_intermediate_table); + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> object_map_head; + object_map_head.resize(object_count); BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); - expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; - expected_diff_state[2] = DIFF_STATE_DATA_UPDATED; - expected_diff_state[3] = DIFF_STATE_HOLE_UPDATED; - ASSERT_EQ(expected_diff_state, m_object_diff_state); + for (uint32_t i = 0; i < object_count; i++) { + object_map_1[i] = from_beginning_intermediate_table[i][0]; + object_map_head[i] = from_beginning_intermediate_table[i][1]; + if (is_diff_iterate()) { + expected_diff_state[i] = from_beginning_intermediate_table[i][2]; + } else { + expected_diff_state[i] = from_beginning_intermediate_table[i][3]; + } + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } } -TEST_F(TestMockObjectMapDiffRequest, IntermediateDelta) { +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapGrow) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - uint32_t object_count = 5; + uint32_t object_count_1 = std::size(from_beginning_intermediate_table); + uint32_t object_count_head = object_count_1 + std::size(from_beginning_table); + m_image_ctx->size = object_count_head * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_head; + object_map_head.resize(object_count_head); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count_head); + for (uint32_t i = 0; i < object_count_1; i++) { + object_map_1[i] = from_beginning_intermediate_table[i][0]; + object_map_head[i] = from_beginning_intermediate_table[i][1]; + if (is_diff_iterate()) { + expected_diff_state[i] = from_beginning_intermediate_table[i][2]; + } else { + expected_diff_state[i] = from_beginning_intermediate_table[i][3]; + } + } + for (uint32_t i = object_count_1; i < object_count_head; i++) { + object_map_head[i] = from_beginning_table[i - object_count_1][0]; + expected_diff_state[i] = from_beginning_table[i - object_count_1][1]; + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapGrowFromZero) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_head = std::size(from_beginning_table); + m_image_ctx->size = object_count_head * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + BitVector<2> object_map_head; + object_map_head.resize(object_count_head); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count_head); + for (uint32_t i = 0; i < object_count_head; i++) { + object_map_head[i] = from_beginning_table[i][0]; + expected_diff_state[i] = from_beginning_table[i][1]; + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapShrink) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_head = std::size(from_beginning_intermediate_table); + uint32_t object_count_1 = object_count_head + std::size(shrink_table); + m_image_ctx->size = object_count_head * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_head; + object_map_head.resize(object_count_head); + BitVector<2> expected_diff_state; + if (is_diff_iterate()) { + expected_diff_state.resize(object_count_head); + } else { + expected_diff_state.resize(object_count_1); + } + for (uint32_t i = 0; i < object_count_head; i++) { + object_map_1[i] = from_beginning_intermediate_table[i][0]; + object_map_head[i] = from_beginning_intermediate_table[i][1]; + if (is_diff_iterate()) { + expected_diff_state[i] = from_beginning_intermediate_table[i][2]; + } else { + expected_diff_state[i] = from_beginning_intermediate_table[i][3]; + } + } + for (uint32_t i = object_count_head; i < object_count_1; i++) { + object_map_1[i] = shrink_table[i - object_count_head][0]; + if (!is_diff_iterate()) { + expected_diff_state[i] = shrink_table[i - object_count_head][1]; + } + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToHeadIntermediateSnapShrinkToZero) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_1 = std::size(shrink_table); + m_image_ctx->size = 0; + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_head; + BitVector<2> expected_diff_state; + if (!is_diff_iterate()) { + expected_diff_state.resize(object_count_1); + } + for (uint32_t i = 0; i < object_count_1; i++) { + object_map_1[i] = shrink_table[i][0]; + if (!is_diff_iterate()) { + expected_diff_state[i] = shrink_table[i][1]; + } + } + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromBeginningToUnsetHead) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = std::size(from_beginning_table); m_image_ctx->size = object_count * (1 << m_image_ctx->order); - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + BitVector<2> object_map_head; + object_map_head.resize(object_count); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + for (uint32_t i = 0; i < object_count; i++) { + object_map_head[i] = from_beginning_table[i][0]; + expected_diff_state[i] = from_beginning_table[i][1]; + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + mock_image_ctx.snap_id = 123; + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = std::size(from_snap_table); + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}}, - {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}} }; - InSequence seq; + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> object_map_2; + object_map_2.resize(object_count); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + for (uint32_t i = 0; i < object_count; i++) { + object_map_1[i] = from_snap_table[i][0]; + object_map_2[i] = from_snap_table[i][1]; + expected_diff_state[i] = from_snap_table[i][2]; + } - expect_get_flags(mock_image_ctx, 1U, 0, 0); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, 2, expected_diff_state); + test_diff_iterate(true, load, 1, 2, expected_diff_state); + } else { + test_deep_copy(false, load, 1, 2, expected_diff_state); + test_deep_copy(true, load, 1, 2, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapGrow) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_1 = std::size(from_snap_table); + uint32_t object_count_2 = object_count_1 + std::size(from_beginning_table); + m_image_ctx->size = object_count_2 * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, + object_count_2 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; BitVector<2> object_map_1; - object_map_1.resize(object_count); - object_map_1[1] = OBJECT_EXISTS; - object_map_1[2] = OBJECT_EXISTS_CLEAN; - expect_load_map(mock_image_ctx, 1U, object_map_1, 0); + object_map_1.resize(object_count_1); + BitVector<2> object_map_2; + object_map_2.resize(object_count_2); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count_2); + for (uint32_t i = 0; i < object_count_1; i++) { + object_map_1[i] = from_snap_table[i][0]; + object_map_2[i] = from_snap_table[i][1]; + expected_diff_state[i] = from_snap_table[i][2]; + } + for (uint32_t i = object_count_1; i < object_count_2; i++) { + object_map_2[i] = from_beginning_table[i - object_count_1][0]; + expected_diff_state[i] = from_beginning_table[i - object_count_1][1]; + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, 2, expected_diff_state); + test_diff_iterate(true, load, 1, 2, expected_diff_state); + } else { + test_deep_copy(false, load, 1, 2, expected_diff_state); + test_deep_copy(true, load, 1, 2, expected_diff_state); + } +} - expect_get_flags(mock_image_ctx, 2U, 0, 0); +TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapGrowFromZero) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + uint32_t object_count_2 = std::size(from_beginning_table); + m_image_ctx->size = object_count_2 * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, + object_count_2 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; BitVector<2> object_map_2; - object_map_2.resize(object_count); - object_map_2[1] = OBJECT_EXISTS_CLEAN; - object_map_2[2] = OBJECT_EXISTS; - object_map_2[3] = OBJECT_EXISTS; - expect_load_map(mock_image_ctx, 2U, object_map_2, 0); + object_map_2.resize(object_count_2); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count_2); + for (uint32_t i = 0; i < object_count_2; i++) { + object_map_2[i] = from_beginning_table[i][0]; + expected_diff_state[i] = from_beginning_table[i][1]; + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, 2, expected_diff_state); + test_diff_iterate(true, load, 1, 2, expected_diff_state); + } else { + test_deep_copy(false, load, 1, 2, expected_diff_state); + test_deep_copy(true, load, 1, 2, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapShrink) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 1, 2, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(0, ctx.wait()); + uint32_t object_count_2 = std::size(from_snap_table); + uint32_t object_count_1 = object_count_2 + std::size(shrink_table); + m_image_ctx->size = object_count_2 * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, + object_count_2 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_2; + object_map_2.resize(object_count_2); BitVector<2> expected_diff_state; - expected_diff_state.resize(object_count); - expected_diff_state[1] = DIFF_STATE_DATA; - expected_diff_state[2] = DIFF_STATE_DATA_UPDATED; - expected_diff_state[3] = DIFF_STATE_DATA_UPDATED; - ASSERT_EQ(expected_diff_state, m_object_diff_state); + if (is_diff_iterate()) { + expected_diff_state.resize(object_count_2); + } else { + expected_diff_state.resize(object_count_1); + } + for (uint32_t i = 0; i < object_count_2; i++) { + object_map_1[i] = from_snap_table[i][0]; + object_map_2[i] = from_snap_table[i][1]; + expected_diff_state[i] = from_snap_table[i][2]; + } + for (uint32_t i = object_count_2; i < object_count_1; i++) { + object_map_1[i] = shrink_table[i - object_count_2][0]; + if (!is_diff_iterate()) { + expected_diff_state[i] = shrink_table[i - object_count_2][1]; + } + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, 2, expected_diff_state); + test_diff_iterate(true, load, 1, 2, expected_diff_state); + } else { + test_deep_copy(false, load, 1, 2, expected_diff_state); + test_deep_copy(true, load, 1, 2, expected_diff_state); + } } -TEST_F(TestMockObjectMapDiffRequest, EndDelta) { +TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapShrinkToZero) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - uint32_t object_count = 5; - m_image_ctx->size = object_count * (1 << m_image_ctx->order); + uint32_t object_count_1 = std::size(shrink_table); + m_image_ctx->size = 0; + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}} + }; - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, - {}, {}, {}}}, - {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, - {}, {}, {}}} + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_2; + BitVector<2> expected_diff_state; + if (!is_diff_iterate()) { + expected_diff_state.resize(object_count_1); + } + for (uint32_t i = 0; i < object_count_1; i++) { + object_map_1[i] = shrink_table[i][0]; + if (!is_diff_iterate()) { + expected_diff_state[i] = shrink_table[i][1]; + } + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, 2, expected_diff_state); + test_diff_iterate(true, load, 1, 2, expected_diff_state); + } else { + test_deep_copy(false, load, 1, 2, expected_diff_state); + test_deep_copy(true, load, 1, 2, expected_diff_state); + } +} - InSequence seq; +TEST_P(TestMockObjectMapDiffRequest, FromSnapToSnapIntermediateSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - expect_get_flags(mock_image_ctx, 2U, 0, 0); + uint32_t object_count = std::size(from_snap_intermediate_table); + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {3U, {"snap3", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + BitVector<2> object_map_1; + object_map_1.resize(object_count); BitVector<2> object_map_2; object_map_2.resize(object_count); - object_map_2[1] = OBJECT_EXISTS_CLEAN; - object_map_2[2] = OBJECT_EXISTS; - object_map_2[3] = OBJECT_EXISTS; - expect_load_map(mock_image_ctx, 2U, object_map_2, 0); + BitVector<2> object_map_3; + object_map_3.resize(object_count); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + for (uint32_t i = 0; i < object_count; i++) { + object_map_1[i] = from_snap_intermediate_table[i][0]; + object_map_2[i] = from_snap_intermediate_table[i][1]; + object_map_3[i] = from_snap_intermediate_table[i][2]; + if (is_diff_iterate()) { + expected_diff_state[i] = from_snap_intermediate_table[i][3]; + } else { + expected_diff_state[i] = from_snap_intermediate_table[i][4]; + } + } - expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + expect_get_flags(mock_image_ctx, 3, 0, 0); + expect_load_map(mock_image_ctx, 3, object_map_3, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, 3, expected_diff_state); + test_diff_iterate(true, load, 1, 3, expected_diff_state); + } else { + test_deep_copy(false, load, 1, 3, expected_diff_state); + test_deep_copy(true, load, 1, 3, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromSnapToHead) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + uint32_t object_count = std::size(from_snap_table); + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count); BitVector<2> object_map_head; object_map_head.resize(object_count); - object_map_head[1] = OBJECT_EXISTS_CLEAN; - object_map_head[2] = OBJECT_EXISTS_CLEAN; - expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + for (uint32_t i = 0; i < object_count; i++) { + object_map_1[i] = from_snap_table[i][0]; + object_map_head[i] = from_snap_table[i][1]; + expected_diff_state[i] = from_snap_table[i][2]; + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadGrow) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_1 = std::size(from_snap_table); + uint32_t object_count_head = object_count_1 + std::size(from_beginning_table); + m_image_ctx->size = object_count_head * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_head; + object_map_head.resize(object_count_head); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count_head); + for (uint32_t i = 0; i < object_count_1; i++) { + object_map_1[i] = from_snap_table[i][0]; + object_map_head[i] = from_snap_table[i][1]; + expected_diff_state[i] = from_snap_table[i][2]; + } + for (uint32_t i = object_count_1; i < object_count_head; i++) { + object_map_head[i] = from_beginning_table[i - object_count_1][0]; + expected_diff_state[i] = from_beginning_table[i - object_count_1][1]; + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadGrowFromZero) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_head = std::size(from_beginning_table); + m_image_ctx->size = object_count_head * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, {}, {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + BitVector<2> object_map_head; + object_map_head.resize(object_count_head); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count_head); + for (uint32_t i = 0; i < object_count_head; i++) { + object_map_head[i] = from_beginning_table[i][0]; + expected_diff_state[i] = from_beginning_table[i][1]; + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadShrink) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_head = std::size(from_snap_table); + uint32_t object_count_1 = object_count_head + std::size(shrink_table); + m_image_ctx->size = object_count_head * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_head; + object_map_head.resize(object_count_head); + BitVector<2> expected_diff_state; + if (is_diff_iterate()) { + expected_diff_state.resize(object_count_head); + } else { + expected_diff_state.resize(object_count_1); + } + for (uint32_t i = 0; i < object_count_head; i++) { + object_map_1[i] = from_snap_table[i][0]; + object_map_head[i] = from_snap_table[i][1]; + expected_diff_state[i] = from_snap_table[i][2]; + } + for (uint32_t i = object_count_head; i < object_count_1; i++) { + object_map_1[i] = shrink_table[i - object_count_head][0]; + if (!is_diff_iterate()) { + expected_diff_state[i] = shrink_table[i - object_count_head][1]; + } + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadShrinkToZero) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count_1 = std::size(shrink_table); + m_image_ctx->size = 0; + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, + object_count_1 * (1 << m_image_ctx->order), {}, {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count_1); + BitVector<2> object_map_head; + BitVector<2> expected_diff_state; + if (!is_diff_iterate()) { + expected_diff_state.resize(object_count_1); + } + for (uint32_t i = 0; i < object_count_1; i++) { + object_map_1[i] = shrink_table[i][0]; + if (!is_diff_iterate()) { + expected_diff_state[i] = shrink_table[i][1]; + } + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } +} - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 2, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(0, ctx.wait()); +TEST_P(TestMockObjectMapDiffRequest, FromSnapToHeadIntermediateSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = std::size(from_snap_intermediate_table); + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> object_map_2; + object_map_2.resize(object_count); + BitVector<2> object_map_head; + object_map_head.resize(object_count); BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); - expected_diff_state[1] = DIFF_STATE_DATA; - expected_diff_state[2] = DIFF_STATE_DATA; - expected_diff_state[3] = DIFF_STATE_HOLE_UPDATED; - ASSERT_EQ(expected_diff_state, m_object_diff_state); + for (uint32_t i = 0; i < object_count; i++) { + object_map_1[i] = from_snap_intermediate_table[i][0]; + object_map_2[i] = from_snap_intermediate_table[i][1]; + object_map_head[i] = from_snap_intermediate_table[i][2]; + if (is_diff_iterate()) { + expected_diff_state[i] = from_snap_intermediate_table[i][3]; + } else { + expected_diff_state[i] = from_snap_intermediate_table[i][4]; + } + } + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } } -TEST_F(TestMockObjectMapDiffRequest, StartSnapDNE) { +TEST_P(TestMockObjectMapDiffRequest, StartSnapDNE) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + if (is_diff_iterate()) { + ASSERT_EQ(-ENOENT, do_diff(false, noop, 1, 2, 0, object_count)); + ASSERT_EQ(-ENOENT, do_diff(true, noop, 1, 2, 0, object_count)); + } else { + ASSERT_EQ(-ENOENT, do_diff(false, noop, 1, 2, 0, UINT64_MAX)); + ASSERT_EQ(-ENOENT, do_diff(true, noop, 1, 2, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, EndSnapDNE) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}} }; - InSequence seq; + BitVector<2> object_map_1; + object_map_1.resize(object_count); - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 1, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(-ENOENT, ctx.wait()); + if (is_diff_iterate()) { + ASSERT_EQ(-ENOENT, do_diff(false, noop, 0, 2, 0, object_count)); + ASSERT_EQ(-ENOENT, do_diff(true, noop, 0, 2, 0, object_count)); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + }; + ASSERT_EQ(-ENOENT, do_diff(false, load, 0, 2, 0, UINT64_MAX)); + ASSERT_EQ(-ENOENT, do_diff(true, load, 0, 2, 0, UINT64_MAX)); + } } -TEST_F(TestMockObjectMapDiffRequest, EndSnapDNE) { +TEST_P(TestMockObjectMapDiffRequest, IntermediateSnapDNEFromBeginning) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); - - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}} }; - InSequence seq; + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> object_map_head; + object_map_head.resize(object_count); + object_map_head[1] = OBJECT_EXISTS_CLEAN; + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0, + [&mock_image_ctx]() { mock_image_ctx.snap_info.erase(2); }); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, IntermediateSnapDNEFromSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - expect_get_flags(mock_image_ctx, 1U, 0, 0); + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; BitVector<2> object_map_1; object_map_1.resize(object_count); - expect_load_map(mock_image_ctx, 1U, object_map_1, 0); + BitVector<2> object_map_head; + object_map_head.resize(object_count); + object_map_head[1] = OBJECT_EXISTS_CLEAN; + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 1, 2, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(-ENOENT, ctx.wait()); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0, + [&mock_image_ctx]() { mock_image_ctx.snap_info.erase(2); }); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } } -TEST_F(TestMockObjectMapDiffRequest, IntermediateSnapDNE) { +TEST_P(TestMockObjectMapDiffRequest, StartObjectMapDNE) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); - - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}}, - {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}} }; - InSequence seq; + BitVector<2> object_map_1; - expect_get_flags(mock_image_ctx, 1U, 0, 0); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, -ENOENT); + }; + if (is_diff_iterate()) { + ASSERT_EQ(-ENOENT, do_diff(false, load, 1, 2, 0, object_count)); + ASSERT_EQ(-ENOENT, do_diff(true, load, 1, 2, 0, object_count)); + } else { + ASSERT_EQ(-ENOENT, do_diff(false, load, 1, 2, 0, UINT64_MAX)); + ASSERT_EQ(-ENOENT, do_diff(true, load, 1, 2, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, EndObjectMapDNE) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; BitVector<2> object_map_1; object_map_1.resize(object_count); - object_map_1[1] = OBJECT_EXISTS_CLEAN; - expect_load_map(mock_image_ctx, 1U, object_map_1, 0, - [&mock_image_ctx]() { mock_image_ctx.snap_info.erase(2); }); + BitVector<2> object_map_2; + object_map_2.resize(object_count); + object_map_2[1] = OBJECT_EXISTS_CLEAN; + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; - expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, -ENOENT); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_with_map(mock_image_ctx, object_map_2); + }; + ASSERT_EQ(-ENOENT, do_diff(false, load, 0, 2, 0, object_count)); + test_diff_iterate(true, with, 0, 2, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, -ENOENT); + }; + ASSERT_EQ(-ENOENT, do_diff(false, load, 0, 2, 0, UINT64_MAX)); + ASSERT_EQ(-ENOENT, do_diff(true, load, 0, 2, 0, UINT64_MAX)); + } +} +TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapDNEFromBeginning) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + + BitVector<2> object_map_1; BitVector<2> object_map_head; object_map_head.resize(object_count); object_map_head[1] = OBJECT_EXISTS_CLEAN; - expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(0, ctx.wait()); + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, -ENOENT); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapDNEFromSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> object_map_2; + BitVector<2> object_map_head; + object_map_head.resize(object_count); + object_map_head[1] = OBJECT_EXISTS_CLEAN; BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; - ASSERT_EQ(expected_diff_state, m_object_diff_state); + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, -ENOENT); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + if (is_diff_iterate()) { + test_diff_iterate(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } else { + test_deep_copy(false, load, 1, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 1, CEPH_NOSNAP, expected_diff_state); + } +} + +TEST_P(TestMockObjectMapDiffRequest, StartFastDiffInvalid) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + + auto get_flags = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, RBD_FLAG_FAST_DIFF_INVALID, 0); + }; + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 1, 2, 0, object_count)); + ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 1, 2, 0, object_count)); + } else { + ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 1, 2, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 1, 2, 0, UINT64_MAX)); + } } -TEST_F(TestMockObjectMapDiffRequest, LoadObjectMapDNE) { +TEST_P(TestMockObjectMapDiffRequest, EndFastDiffInvalid) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; - MockTestImageCtx mock_image_ctx(*m_image_ctx); + BitVector<2> object_map_1; + object_map_1.resize(object_count); + + if (is_diff_iterate()) { + auto get_flags = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0); + }; + ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 0, 2, 0, object_count)); + ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 0, 2, 0, object_count)); + } else { + auto get_flags = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0); + }; + ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 0, 2, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 0, 2, 0, UINT64_MAX)); + } +} - InSequence seq; +TEST_P(TestMockObjectMapDiffRequest, IntermediateFastDiffInvalidFromBeginning) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; BitVector<2> object_map_head; - expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, -ENOENT); + object_map_head.resize(object_count); + object_map_head[1] = OBJECT_EXISTS_CLEAN; + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(-ENOENT, ctx.wait()); + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto get_flags = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, RBD_FLAG_FAST_DIFF_INVALID, 0); + }; + ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 0, CEPH_NOSNAP, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 0, CEPH_NOSNAP, 0, UINT64_MAX)); + } } -TEST_F(TestMockObjectMapDiffRequest, LoadIntermediateObjectMapDNE) { +TEST_P(TestMockObjectMapDiffRequest, IntermediateFastDiffInvalidFromSnap) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count); + + auto get_flags = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, RBD_FLAG_FAST_DIFF_INVALID, 0); + }; + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 1, CEPH_NOSNAP, 0, object_count)); + ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 1, CEPH_NOSNAP, 0, object_count)); + } else { + ASSERT_EQ(-EINVAL, do_diff(false, get_flags, 1, CEPH_NOSNAP, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, get_flags, 1, CEPH_NOSNAP, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, StartObjectMapLoadError) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}} }; - InSequence seq; + BitVector<2> object_map_1; + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, -EPERM); + }; + if (is_diff_iterate()) { + ASSERT_EQ(-EPERM, do_diff(false, load, 1, 2, 0, object_count)); + ASSERT_EQ(-EPERM, do_diff(true, load, 1, 2, 0, object_count)); + } else { + ASSERT_EQ(-EPERM, do_diff(false, load, 1, 2, 0, UINT64_MAX)); + ASSERT_EQ(-EPERM, do_diff(true, load, 1, 2, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, EndObjectMapLoadError) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - expect_get_flags(mock_image_ctx, 1U, 0, 0); + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; BitVector<2> object_map_1; - expect_load_map(mock_image_ctx, 1U, object_map_1, -ENOENT); + object_map_1.resize(object_count); + BitVector<2> object_map_2; + object_map_2.resize(object_count); + object_map_2[1] = OBJECT_EXISTS_CLEAN; + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; - expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_with_map(mock_image_ctx, object_map_2); + }; + ASSERT_EQ(-EPERM, do_diff(false, load, 0, 2, 0, object_count)); + test_diff_iterate(true, with, 0, 2, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM); + }; + ASSERT_EQ(-EPERM, do_diff(false, load, 0, 2, 0, UINT64_MAX)); + ASSERT_EQ(-EPERM, do_diff(true, load, 0, 2, 0, UINT64_MAX)); + } +} +TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapLoadErrorFromBeginning) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + + BitVector<2> object_map_1; BitVector<2> object_map_head; object_map_head.resize(object_count); object_map_head[1] = OBJECT_EXISTS_CLEAN; - expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); - - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(0, ctx.wait()); - BitVector<2> expected_diff_state; expected_diff_state.resize(object_count); expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; - ASSERT_EQ(expected_diff_state, m_object_diff_state); + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, -EPERM); + }; + ASSERT_EQ(-EPERM, do_diff(false, load, 0, CEPH_NOSNAP, 0, UINT64_MAX)); + ASSERT_EQ(-EPERM, do_diff(true, load, 0, CEPH_NOSNAP, 0, UINT64_MAX)); + } } -TEST_F(TestMockObjectMapDiffRequest, LoadObjectMapError) { +TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapLoadErrorFromSnap) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); - - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}} }; - InSequence seq; + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> object_map_2; + + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, -EPERM); + }; + if (is_diff_iterate()) { + ASSERT_EQ(-EPERM, do_diff(false, load, 1, CEPH_NOSNAP, 0, object_count)); + ASSERT_EQ(-EPERM, do_diff(true, load, 1, CEPH_NOSNAP, 0, object_count)); + } else { + ASSERT_EQ(-EPERM, do_diff(false, load, 1, CEPH_NOSNAP, 0, UINT64_MAX)); + ASSERT_EQ(-EPERM, do_diff(true, load, 1, CEPH_NOSNAP, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, StartObjectMapTooSmall) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - expect_get_flags(mock_image_ctx, 1U, 0, 0); + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; BitVector<2> object_map_1; - expect_load_map(mock_image_ctx, 1U, object_map_1, -EPERM); + object_map_1.resize(object_count - 1); - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(-EPERM, ctx.wait()); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + }; + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, load, 1, 2, 0, object_count)); + ASSERT_EQ(-EINVAL, do_diff(true, load, 1, 2, 0, object_count)); + } else { + ASSERT_EQ(-EINVAL, do_diff(false, load, 1, 2, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, load, 1, 2, 0, UINT64_MAX)); + } } -TEST_F(TestMockObjectMapDiffRequest, ObjectMapTooSmall) { +TEST_P(TestMockObjectMapDiffRequest, EndObjectMapTooSmall) { REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); uint32_t object_count = 5; m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; - MockTestImageCtx mock_image_ctx(*m_image_ctx); - mock_image_ctx.snap_info = { - {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, mock_image_ctx.size, {}, + BitVector<2> object_map_1; + object_map_1.resize(object_count); + BitVector<2> object_map_2; + object_map_2.resize(object_count - 1); + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_with_map(mock_image_ctx, object_map_2); + }; + ASSERT_EQ(-EINVAL, do_diff(false, load, 0, 2, 0, object_count)); + ASSERT_EQ(-EINVAL, do_diff(true, with, 0, 2, 0, object_count)); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + ASSERT_EQ(-EINVAL, do_diff(false, load, 0, 2, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, load, 0, 2, 0, UINT64_MAX)); + } +} + +TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapTooSmallFromBeginning) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, {}, {}, {}}} }; - InSequence seq; + BitVector<2> object_map_1; + object_map_1.resize(object_count - 1); + BitVector<2> object_map_head; + object_map_head.resize(object_count); + object_map_head[1] = OBJECT_EXISTS_CLEAN; + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + }; + ASSERT_EQ(-EINVAL, do_diff(false, load, 0, CEPH_NOSNAP, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, load, 0, CEPH_NOSNAP, 0, UINT64_MAX)); + } +} - expect_get_flags(mock_image_ctx, 1U, 0, 0); +TEST_P(TestMockObjectMapDiffRequest, IntermediateObjectMapTooSmallFromSnap) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}}, + {2U, {"snap2", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; BitVector<2> object_map_1; - expect_load_map(mock_image_ctx, 1U, object_map_1, 0); + object_map_1.resize(object_count); + BitVector<2> object_map_2; + object_map_2.resize(object_count - 1); - C_SaferCond ctx; - auto req = new MockDiffRequest(&mock_image_ctx, 0, CEPH_NOSNAP, - &m_object_diff_state, &ctx); - req->send(); - ASSERT_EQ(-EINVAL, ctx.wait()); + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, 2, 0, 0); + expect_load_map(mock_image_ctx, 2, object_map_2, 0); + }; + if (is_diff_iterate()) { + ASSERT_EQ(-EINVAL, do_diff(false, load, 1, CEPH_NOSNAP, 0, object_count)); + ASSERT_EQ(-EINVAL, do_diff(true, load, 1, CEPH_NOSNAP, 0, object_count)); + } else { + ASSERT_EQ(-EINVAL, do_diff(false, load, 1, CEPH_NOSNAP, 0, UINT64_MAX)); + ASSERT_EQ(-EINVAL, do_diff(true, load, 1, CEPH_NOSNAP, 0, UINT64_MAX)); + } } +TEST_P(TestMockObjectMapDiffRequest, ObjectMapTooLarge) { + REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); + + uint32_t object_count = 5; + m_image_ctx->size = object_count * (1 << m_image_ctx->order); + m_image_ctx->snap_info = { + {1U, {"snap1", {cls::rbd::UserSnapshotNamespace{}}, m_image_ctx->size, {}, + {}, {}, {}}} + }; + + BitVector<2> object_map_1; + object_map_1.resize(object_count + 12); + BitVector<2> object_map_head; + object_map_head.resize(object_count + 34); + object_map_head[1] = OBJECT_EXISTS_CLEAN; + BitVector<2> expected_diff_state; + expected_diff_state.resize(object_count); + expected_diff_state[1] = DIFF_STATE_DATA_UPDATED; + + if (is_diff_iterate()) { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + auto with = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_with_map(mock_image_ctx, object_map_head); + }; + test_diff_iterate(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_diff_iterate(true, with, 0, CEPH_NOSNAP, expected_diff_state); + } else { + auto load = [&](MockTestImageCtx& mock_image_ctx) { + expect_get_flags(mock_image_ctx, 1, 0, 0); + expect_load_map(mock_image_ctx, 1, object_map_1, 0); + expect_get_flags(mock_image_ctx, CEPH_NOSNAP, 0, 0); + expect_load_map(mock_image_ctx, CEPH_NOSNAP, object_map_head, 0); + }; + test_deep_copy(false, load, 0, CEPH_NOSNAP, expected_diff_state); + test_deep_copy(true, load, 0, CEPH_NOSNAP, expected_diff_state); + } +} + +INSTANTIATE_TEST_SUITE_P(MockObjectMapDiffRequestTests, + TestMockObjectMapDiffRequest, ::testing::Bool()); + } // namespace object_map } // librbd diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc index b09b67793..8ad5c5aa5 100644 --- a/src/test/librbd/test_librbd.cc +++ b/src/test/librbd/test_librbd.cc @@ -1902,6 +1902,8 @@ TEST_F(TestLibRBD, TestGetSnapShotTimeStamp) ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + ASSERT_EQ(-ENOENT, rbd_snap_get_timestamp(image, 0, NULL)); + ASSERT_EQ(0, rbd_snap_create(image, "snap1")); num_snaps = rbd_snap_list(image, snaps, &max_size); ASSERT_EQ(1, num_snaps); @@ -7358,61 +7360,6 @@ interval_set<uint64_t> round_diff_interval(const interval_set<uint64_t>& diff, return rounded_diff; } -TEST_F(TestLibRBD, SnapDiff) -{ - REQUIRE_FEATURE(RBD_FEATURE_FAST_DIFF); - - rados_ioctx_t ioctx; - rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx); - - rbd_image_t image; - int order = 0; - std::string image_name = get_temp_image_name(); - uint64_t size = 100 << 20; - ASSERT_EQ(0, create_image(ioctx, image_name.c_str(), size, &order)); - ASSERT_EQ(0, rbd_open(ioctx, image_name.c_str(), &image, nullptr)); - - char test_data[TEST_IO_SIZE + 1]; - for (size_t i = 0; i < TEST_IO_SIZE; ++i) { - test_data[i] = (char) (rand() % (126 - 33) + 33); - } - test_data[TEST_IO_SIZE] = '\0'; - - ASSERT_PASSED(write_test_data, image, test_data, 0, - TEST_IO_SIZE, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); - - interval_set<uint64_t> diff; - ASSERT_EQ(0, rbd_diff_iterate2(image, nullptr, 0, size, true, true, - iterate_cb, &diff)); - EXPECT_EQ(1 << order, diff.size()); - - ASSERT_EQ(0, rbd_snap_create(image, "snap1")); - ASSERT_EQ(0, rbd_snap_create(image, "snap2")); - - diff.clear(); - ASSERT_EQ(0, rbd_diff_iterate2(image, nullptr, 0, size, true, true, - iterate_cb, &diff)); - EXPECT_EQ(1 << order, diff.size()); - - diff.clear(); - ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, true, - iterate_cb, &diff)); - EXPECT_EQ(0, diff.size()); - - diff.clear(); - ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, true, - iterate_cb, &diff)); - EXPECT_EQ(0, diff.size()); - - ASSERT_EQ(0, rbd_snap_remove(image, "snap1")); - ASSERT_EQ(0, rbd_snap_remove(image, "snap2")); - - ASSERT_EQ(0, rbd_close(image)); - ASSERT_EQ(0, rbd_remove(ioctx, image_name.c_str())); - - rados_ioctx_destroy(ioctx); -} - template <typename T> class DiffIterateTest : public TestLibRBD { public: @@ -7501,20 +7448,155 @@ ostream& operator<<(ostream & o, const diff_extent& e) { int vector_iterate_cb(uint64_t off, size_t len, int exists, void *arg) { - cout << "iterate_cb " << off << "~" << len << std::endl; + //cout << "iterate_cb " << off << "~" << len << std::endl; vector<diff_extent> *diff = static_cast<vector<diff_extent> *>(arg); diff->push_back(diff_extent(off, len, exists, 0)); return 0; } -TYPED_TEST(DiffIterateTest, DiffIterateDiscard) +TYPED_TEST(DiffIterateTest, DiffIterateDeterministic) { + REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2)); + + rados_ioctx_t ioctx; + ASSERT_EQ(0, rados_ioctx_create(this->_cluster, this->m_pool_name.c_str(), + &ioctx)); + + rbd_image_t image; + int order = 22; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + std::vector<diff_extent> extents; + ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_EQ(0, rbd_snap_create(image, "snap1")); + + std::string buf(256, '1'); + ASSERT_EQ(256, rbd_write(image, 0, 256, buf.data())); + ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); + + ASSERT_EQ(0, rbd_snap_create(image, "snap2")); + + ASSERT_EQ(256, rbd_write(image, 1 << order, 256, buf.data())); + ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + ASSERT_EQ(0, rbd_snap_create(image, "snap3")); + + // 1. beginning of time -> HEAD + ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + // 2. snap1 -> HEAD + ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + // 3. snap2 -> HEAD + ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); + extents.clear(); + + // 4. snap3 -> HEAD + ASSERT_EQ(0, rbd_diff_iterate2(image, "snap3", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_PASSED(this->validate_object_map, image); + ASSERT_EQ(0, rbd_snap_set(image, "snap3")); + + // 5. beginning of time -> snap3 + ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + // 6. snap1 -> snap3 + ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + // 7. snap2 -> snap3 + ASSERT_EQ(0, rbd_diff_iterate2(image, "snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); + extents.clear(); + + ASSERT_PASSED(this->validate_object_map, image); + ASSERT_EQ(0, rbd_snap_set(image, "snap2")); + + // 8. beginning of time -> snap2 + ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); + + // 9. snap1 -> snap2 + ASSERT_EQ(0, rbd_diff_iterate2(image, "snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); + + ASSERT_PASSED(this->validate_object_map, image); + ASSERT_EQ(0, rbd_snap_set(image, "snap1")); + + // 10. beginning of time -> snap1 + ASSERT_EQ(0, rbd_diff_iterate2(image, NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_PASSED(this->validate_object_map, image); + + ASSERT_EQ(0, rbd_close(image)); + rados_ioctx_destroy(ioctx); +} + +TYPED_TEST(DiffIterateTest, DiffIterateDeterministicPP) +{ + REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2)); + librados::IoCtx ioctx; ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); librbd::RBD rbd; librbd::Image image; - int order = 0; + int order = 22; std::string name = this->get_temp_image_name(); uint64_t size = 20 << 20; @@ -7525,57 +7607,573 @@ TYPED_TEST(DiffIterateTest, DiffIterateDiscard) if (this->whole_object) { object_size = 1 << order; } - vector<diff_extent> extents; - ceph::bufferlist bl; + std::vector<diff_extent> extents; ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); + vector_iterate_cb, &extents)); ASSERT_EQ(0u, extents.size()); - char data[256]; - memset(data, 1, sizeof(data)); - bl.append(data, 256); + ASSERT_EQ(0, image.snap_create("snap1")); + + ceph::bufferlist bl; + bl.append(std::string(256, '1')); ASSERT_EQ(256, image.write(0, 256, bl)); ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); + vector_iterate_cb, &extents)); ASSERT_EQ(1u, extents.size()); ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); - int obj_ofs = 256; - ASSERT_EQ(1 << order, image.discard(0, 1 << order)); + ASSERT_EQ(0, image.snap_create("snap2")); + ASSERT_EQ(256, image.write(1 << order, 256, bl)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); extents.clear(); + + ASSERT_EQ(0, image.snap_create("snap3")); + + // 1. beginning of time -> HEAD ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + // 2. snap1 -> HEAD + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + // 3. snap2 -> HEAD + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); + extents.clear(); + + // 4. snap3 -> HEAD + ASSERT_EQ(0, image.diff_iterate2("snap3", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); ASSERT_EQ(0u, extents.size()); - ASSERT_EQ(0, image.snap_create("snap1")); - ASSERT_EQ(256, image.write(0, 256, bl)); + ASSERT_PASSED(this->validate_object_map, image); + ASSERT_EQ(0, image.snap_set("snap3")); + + // 5. beginning of time -> snap3 ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); - ASSERT_EQ(1u, extents.size()); + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); - ASSERT_EQ(0, image.snap_create("snap2")); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); - ASSERT_EQ(obj_ofs, image.discard(0, obj_ofs)); + // 6. snap1 -> snap3 + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + // 7. snap2 -> snap3 + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); extents.clear(); + + ASSERT_PASSED(this->validate_object_map, image); ASSERT_EQ(0, image.snap_set("snap2")); + + // 8. beginning of time -> snap2 + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); + + // 9. snap1 -> snap2 ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); + vector_iterate_cb, &extents)); ASSERT_EQ(1u, extents.size()); ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); + + ASSERT_PASSED(this->validate_object_map, image); + ASSERT_EQ(0, image.snap_set("snap1")); + + // 10. beginning of time -> snap1 + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_PASSED(this->validate_object_map, image); +} + +TYPED_TEST(DiffIterateTest, DiffIterateDiscard) +{ + REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 22; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + std::vector<diff_extent> extents; + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ceph::bufferlist bl; + bl.append(std::string(256, '1')); + ASSERT_EQ(256, image.write(0, 256, bl)); + ASSERT_EQ(256, image.write(1 << order, 256, bl)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + ASSERT_EQ(size, image.discard(0, size)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_EQ(0, image.snap_create("snap1")); + + ASSERT_EQ(256, image.write(0, 256, bl)); + ASSERT_EQ(256, image.write(1 << order, 256, bl)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + ASSERT_EQ(0, image.snap_create("snap2")); - ASSERT_EQ(0, image.snap_set(NULL)); ASSERT_EQ(1 << order, image.discard(0, 1 << order)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.snap_create("snap3")); + + // 1. beginning of time -> HEAD + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); + extents.clear(); + + // 2. snap1 -> HEAD + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); + extents.clear(); + + // 3. snap2 -> HEAD + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, false, object_size), extents[0]); + extents.clear(); + + // 4. snap3 -> HEAD + ASSERT_EQ(0, image.diff_iterate2("snap3", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_PASSED(this->validate_object_map, image); ASSERT_EQ(0, image.snap_set("snap3")); + // 5. beginning of time -> snap3 + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); extents.clear(); + + // 6. snap1 -> snap3 ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[0]); + extents.clear(); + + // 7. snap2 -> snap3 + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); ASSERT_EQ(1u, extents.size()); ASSERT_EQ(diff_extent(0, 256, false, object_size), extents[0]); + extents.clear(); + + ASSERT_PASSED(this->validate_object_map, image); + ASSERT_EQ(0, image.snap_set("snap2")); + + // 8. beginning of time -> snap2 + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + // 9. snap1 -> snap2 + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(1 << order, 256, true, object_size), extents[1]); + extents.clear(); + + ASSERT_PASSED(this->validate_object_map, image); + ASSERT_EQ(0, image.snap_set("snap1")); + + // 10. beginning of time -> snap1 + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_PASSED(this->validate_object_map, image); +} + +TYPED_TEST(DiffIterateTest, DiffIterateTruncate) +{ + REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 22; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + ASSERT_EQ(0, image.snap_create("snap0")); + + ceph::bufferlist bl; + bl.append(std::string(512 << 10, '1')); + ASSERT_EQ(512 << 10, image.write(0, 512 << 10, bl)); + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(512 << 10, image.write(512 << 10, 512 << 10, bl)); + ASSERT_EQ(0, image.snap_create("snap2")); + + std::vector<diff_extent> extents; + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 1024 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 1024 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(512 << 10, 512 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_EQ(256 << 10, image.discard(768 << 10, 256 << 10)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 768 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 768 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(512 << 10, 256 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(768 << 10, 256 << 10, this->whole_object, object_size), + extents[0]); + extents.clear(); + + ASSERT_EQ(256 << 10, image.discard(512 << 10, 256 << 10)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 512 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 512 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + if (this->whole_object && + (is_feature_enabled(RBD_FEATURE_OBJECT_MAP) || + is_feature_enabled(RBD_FEATURE_FAST_DIFF))) { + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 0, true, object_size), extents[0]); + extents.clear(); + } else { + ASSERT_EQ(0u, extents.size()); + } + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(512 << 10, 512 << 10, this->whole_object, object_size), + extents[0]); + extents.clear(); + + ASSERT_EQ(256 << 10, image.discard(256 << 10, 256 << 10)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(256 << 10, 256 << 10, this->whole_object, object_size), + extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(256 << 10, 768 << 10, this->whole_object, object_size), + extents[0]); + extents.clear(); + + ASSERT_EQ(256 << 10, image.discard(0, 256 << 10)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + if (this->whole_object && + (is_feature_enabled(RBD_FEATURE_OBJECT_MAP) || + is_feature_enabled(RBD_FEATURE_FAST_DIFF))) { + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 0, true, object_size), extents[0]); + extents.clear(); + } else { + ASSERT_EQ(0u, extents.size()); + } + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + if (this->whole_object && + (is_feature_enabled(RBD_FEATURE_OBJECT_MAP) || + is_feature_enabled(RBD_FEATURE_FAST_DIFF))) { + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 0, true, object_size), extents[0]); + extents.clear(); + } else { + ASSERT_EQ(0u, extents.size()); + } + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 512 << 10, this->whole_object, object_size), + extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 1024 << 10, this->whole_object, object_size), + extents[0]); + extents.clear(); + + ASSERT_PASSED(this->validate_object_map, image); +} + +TYPED_TEST(DiffIterateTest, DiffIterateWriteAndTruncate) +{ + REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + librbd::Image image; + int order = 22; + std::string name = this->get_temp_image_name(); + uint64_t size = 20 << 20; + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + ASSERT_EQ(0, image.snap_create("snap0")); + + ceph::bufferlist bl; + bl.append(std::string(512 << 10, '1')); + ASSERT_EQ(512 << 10, image.write(0, 512 << 10, bl)); + ASSERT_EQ(0, image.snap_create("snap1")); + ASSERT_EQ(512 << 10, image.write(512 << 10, 512 << 10, bl)); + ASSERT_EQ(0, image.snap_create("snap2")); + + std::vector<diff_extent> extents; + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 1024 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 1024 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(512 << 10, 512 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(0u, extents.size()); + + ASSERT_EQ(1 << 10, image.write(767 << 10, 1 << 10, bl)); + ASSERT_EQ(256 << 10, image.discard(768 << 10, 256 << 10)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 768 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 768 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(512 << 10, 256 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + if (this->whole_object) { + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 0, true, object_size), extents[0]); + } else { + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(767 << 10, 1 << 10, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(768 << 10, 256 << 10, false, object_size), + extents[1]); + } + extents.clear(); + + ASSERT_EQ(2 << 10, image.write(510 << 10, 2 << 10, bl)); + ASSERT_EQ(256 << 10, image.discard(512 << 10, 256 << 10)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 512 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 512 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(510 << 10, 2 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + if (this->whole_object) { + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 0, true, object_size), extents[0]); + } else { + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(510 << 10, 2 << 10, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(512 << 10, 512 << 10, false, object_size), + extents[1]); + } + extents.clear(); + + ASSERT_EQ(3 << 10, image.write(253 << 10, 3 << 10, bl)); + ASSERT_EQ(256 << 10, image.discard(256 << 10, 256 << 10)); + ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap0", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256 << 10, true, object_size), extents[0]); + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap1", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + if (this->whole_object) { + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 0, true, object_size), extents[0]); + } else { + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(253 << 10, 3 << 10, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(256 << 10, 256 << 10, false, object_size), + extents[1]); + } + extents.clear(); + ASSERT_EQ(0, image.diff_iterate2("snap2", 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + if (this->whole_object) { + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 0, true, object_size), extents[0]); + } else { + ASSERT_EQ(2u, extents.size()); + ASSERT_EQ(diff_extent(253 << 10, 3 << 10, true, object_size), extents[0]); + ASSERT_EQ(diff_extent(256 << 10, 768 << 10, false, object_size), + extents[1]); + } + extents.clear(); + ASSERT_PASSED(this->validate_object_map, image); } @@ -7655,50 +8253,6 @@ TYPED_TEST(DiffIterateTest, DiffIterateStress) ASSERT_PASSED(this->validate_object_map, image); } -TYPED_TEST(DiffIterateTest, DiffIterateRegression6926) -{ - librados::IoCtx ioctx; - ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); - - librbd::RBD rbd; - librbd::Image image; - int order = 0; - std::string name = this->get_temp_image_name(); - uint64_t size = 20 << 20; - - ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); - ASSERT_EQ(0, rbd.open(ioctx, image, name.c_str(), NULL)); - - uint64_t object_size = 0; - if (this->whole_object) { - object_size = 1 << order; - } - vector<diff_extent> extents; - ceph::bufferlist bl; - - ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); - ASSERT_EQ(0u, extents.size()); - - ASSERT_EQ(0, image.snap_create("snap1")); - char data[256]; - memset(data, 1, sizeof(data)); - bl.append(data, 256); - ASSERT_EQ(256, image.write(0, 256, bl)); - - extents.clear(); - ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); - ASSERT_EQ(1u, extents.size()); - ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); - - ASSERT_EQ(0, image.snap_set("snap1")); - extents.clear(); - ASSERT_EQ(0, image.diff_iterate2(NULL, 0, size, true, this->whole_object, - vector_iterate_cb, (void *) &extents)); - ASSERT_EQ(static_cast<size_t>(0), extents.size()); -} - TYPED_TEST(DiffIterateTest, DiffIterateParent) { REQUIRE_FEATURE(RBD_FEATURE_LAYERING); @@ -7968,6 +8522,83 @@ TYPED_TEST(DiffIterateTest, DiffIterateUnaligned) ioctx.close(); } +TYPED_TEST(DiffIterateTest, DiffIterateTryAcquireLock) +{ + REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK); + + librados::IoCtx ioctx; + ASSERT_EQ(0, this->_rados.ioctx_create(this->m_pool_name.c_str(), ioctx)); + + { + librbd::RBD rbd; + int order = 22; + std::string name = this->get_temp_image_name(); + ssize_t size = 20 << 20; + + uint64_t object_size = 0; + if (this->whole_object) { + object_size = 1 << order; + } + + ASSERT_EQ(0, create_image_pp(rbd, ioctx, name.c_str(), size, &order)); + + librbd::Image image1; + ASSERT_EQ(0, rbd.open(ioctx, image1, name.c_str(), NULL)); + + ceph::bufferlist bl; + bl.append(std::string(256, '1')); + ASSERT_EQ(256, image1.write(0, 256, bl)); + ASSERT_EQ(0, image1.flush()); + + bool lock_owner; + ASSERT_EQ(0, image1.is_exclusive_lock_owner(&lock_owner)); + ASSERT_TRUE(lock_owner); + + librbd::Image image2; + ASSERT_EQ(0, rbd.open(ioctx, image2, name.c_str(), NULL)); + + std::vector<diff_extent> extents; + ASSERT_EQ(0, image2.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + ASSERT_EQ(0, image1.close()); + ASSERT_EQ(0, image2.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + ASSERT_FALSE(lock_owner); + + sleep(5); + ASSERT_EQ(0, image2.diff_iterate2(NULL, 0, size, true, this->whole_object, + vector_iterate_cb, &extents)); + ASSERT_EQ(1u, extents.size()); + ASSERT_EQ(diff_extent(0, 256, true, object_size), extents[0]); + extents.clear(); + + ASSERT_EQ(0, image2.is_exclusive_lock_owner(&lock_owner)); + if (this->whole_object && + (is_feature_enabled(RBD_FEATURE_OBJECT_MAP) || + is_feature_enabled(RBD_FEATURE_FAST_DIFF))) { + ASSERT_TRUE(lock_owner); + } else { + ASSERT_FALSE(lock_owner); + } + + ASSERT_PASSED(this->validate_object_map, image2); + } + + ioctx.close(); +} + TYPED_TEST(DiffIterateTest, DiffIterateStriping) { REQUIRE_FEATURE(RBD_FEATURE_STRIPINGV2); diff --git a/src/test/librbd/test_main.cc b/src/test/librbd/test_main.cc index 2ff9f69de..82b72b1ef 100644 --- a/src/test/librbd/test_main.cc +++ b/src/test/librbd/test_main.cc @@ -17,6 +17,7 @@ extern void register_test_image_watcher(); extern void register_test_internal(); extern void register_test_journal_entries(); extern void register_test_journal_replay(); +extern void register_test_journal_stress(); extern void register_test_migration(); extern void register_test_mirroring(); extern void register_test_mirroring_watcher(); @@ -37,6 +38,7 @@ int main(int argc, char **argv) register_test_internal(); register_test_journal_entries(); register_test_journal_replay(); + register_test_journal_stress(); register_test_migration(); register_test_mirroring(); register_test_mirroring_watcher(); diff --git a/src/test/librbd/test_mock_Journal.cc b/src/test/librbd/test_mock_Journal.cc index 2fe74d2fe..589695c50 100644 --- a/src/test/librbd/test_mock_Journal.cc +++ b/src/test/librbd/test_mock_Journal.cc @@ -460,7 +460,7 @@ public: bl.append_zero(length); std::shared_lock owner_locker{mock_image_ctx.owner_lock}; - return mock_journal->append_write_event(0, length, bl, false); + return mock_journal->append_write_event({{0, length}}, bl, false); } uint64_t when_append_compare_and_write_event( diff --git a/src/test/mds/TestMDSAuthCaps.cc b/src/test/mds/TestMDSAuthCaps.cc index a05f16027..84abd16c9 100644 --- a/src/test/mds/TestMDSAuthCaps.cc +++ b/src/test/mds/TestMDSAuthCaps.cc @@ -23,7 +23,14 @@ using namespace std; entity_addr_t addr; -const char *parse_good[] = { +string fsnamecap = "fsname=a"; +string pathcap = "path=/dir1"; +string rscap = "root_squash"; +string uidcap = "uid=1000"; +string gidscap = "gids=1000,1001,1002"; + + +vector<string> parse_good = { "allow rw uid=1 gids=1", "allow * path=\"/foo\"", "allow * path=/foo", @@ -34,8 +41,6 @@ const char *parse_good[] = { "allow *", "allow r", "allow rw", - "allow rw uid=1 gids=1,2,3", - "allow rw path=/foo uid=1 gids=1,2,3", "allow r, allow rw path=/foo", "allow r, allow * uid=1", "allow r ,allow * uid=1", @@ -45,23 +50,82 @@ const char *parse_good[] = { "allow r uid=1 gids=1,2,3, allow * uid=2", "allow r network 1.2.3.4/8", "allow rw path=/foo uid=1 gids=1,2,3 network 2.3.4.5/16", - "allow r root_squash", - "allow rw path=/foo root_squash", - "allow rw fsname=a root_squash", - "allow rw fsname=a path=/foo root_squash", - "allow rw fsname=a root_squash, allow rwp fsname=a path=/volumes", - 0 + + // Following are all types of MDS caps, or in other words, all + // (mathematical) combinations of fsnamecap, pathcap, rscap, uidcap, and + // gidscaps. + "allow rw " + fsnamecap, + "allow rw " + pathcap, + "allow rw " + rscap, + "allow rw " + uidcap, + "allow rw " + gidscap, + + "allow rw " + fsnamecap + " " + pathcap, + "allow rw " + fsnamecap + " " + rscap, + "allow rw " + fsnamecap + " " + uidcap, + "allow rw " + fsnamecap + " " + gidscap, + "allow rw " + pathcap + " " + rscap, + "allow rw " + pathcap + " " + uidcap, + "allow rw " + pathcap + " " + gidscap, + "allow rw " + rscap + " " + uidcap, + "allow rw " + rscap + " " + gidscap, + "allow rw " + uidcap + " " + gidscap, + + "allow rw " + fsnamecap + " " + pathcap + " " + rscap, + "allow rw " + fsnamecap + " " + pathcap + " " + uidcap, + "allow rw " + fsnamecap + " " + pathcap + " " + gidscap, + "allow rw " + fsnamecap + " " + rscap + " " + uidcap, + "allow rw " + fsnamecap + " " + rscap + " " + gidscap, + "allow rw " + fsnamecap + " " + uidcap + " " + gidscap, + "allow rw " + pathcap + " " + rscap + " " + uidcap, + "allow rw " + pathcap + " " + rscap + " " + gidscap, + "allow rw " + pathcap + " " + uidcap + " " + gidscap, + "allow rw " + rscap + " " + uidcap + " " + gidscap, + + "allow rw " + fsnamecap + " " + pathcap + " " + rscap + " " + uidcap, + "allow rw " + fsnamecap + " " + pathcap + " " + rscap + " " + gidscap, + "allow rw " + fsnamecap + " " + pathcap + " " + uidcap + " " + gidscap, + "allow rw " + fsnamecap + " " + rscap + " " + uidcap + " " + gidscap, + "allow rw " + pathcap + " " + rscap + " " + uidcap + " " + gidscap, + + "allow rw " + fsnamecap + " " + pathcap + " " + rscap + " " + uidcap + + " " + gidscap }; TEST(MDSAuthCaps, ParseGood) { - for (int i=0; parse_good[i]; i++) { - string str = parse_good[i]; + for (auto str : parse_good) { MDSAuthCaps cap; std::cout << "Testing good input: '" << str << "'" << std::endl; ASSERT_TRUE(cap.parse(str, &cout)); } } +TEST(MDSAuthCaps, ParseDumpReparseCaps) { + for (auto str : parse_good) { + MDSAuthCaps cap1; + ASSERT_TRUE(cap1.parse(str, &cout)); + + std::cout << "Testing by parsing caps, dumping to string, reparsing " + "string and then redumping and checking strings from " + "first and second dumps: '" << str << "'" << std::endl; + // Convert cap object to string, reparse and check if converting again + // gives same string as before. + MDSAuthCaps cap2; + std::ostringstream cap1_ostream; + cap1_ostream << cap1; + string cap1_str = cap1_ostream.str(); + // Removing "MDSAuthCaps[" from cap1_str + cap1_str.replace(0, 12, ""); + // Removing "]" from cap1_str + cap1_str.replace(cap1_str.length() - 1, 1, ""); + ASSERT_TRUE(cap2.parse(cap1_str, &cout)); + + std::ostringstream cap2_ostream; + cap2_ostream << cap2; + ASSERT_TRUE(cap1_ostream.str().compare(cap2_ostream.str()) == 0); + } +} + const char *parse_bad[] = { "allow r poolfoo", "allow r w", @@ -85,8 +149,6 @@ const char *parse_bad[] = { "allow namespace=foo", "allow rwx auid 123 namespace asdf", "allow wwx pool ''", - "allow rw gids=1", - "allow rw gids=1,2,3", "allow rw uid=123 gids=asdf", "allow rw uid=123 gids=1,2,asdf", 0 @@ -98,6 +160,8 @@ TEST(MDSAuthCaps, ParseBad) { MDSAuthCaps cap; std::cout << "Testing bad input: '" << str << "'" << std::endl; ASSERT_FALSE(cap.parse(str, &cout)); + // error message from parse() doesn't have newline char at the end of it + std::cout << std::endl; } } diff --git a/src/test/objectstore/test_kv.cc b/src/test/objectstore/test_kv.cc index 33ffd6ab3..95c712cef 100644 --- a/src/test/objectstore/test_kv.cc +++ b/src/test/objectstore/test_kv.cc @@ -29,6 +29,14 @@ using namespace std; +std::string gen_random_string(size_t size) { + std::string s; + for (size_t i = 0; i < size; i++) { + s.push_back(rand()); + } + return s; +} + class KVTest : public ::testing::TestWithParam<const char*> { public: boost::scoped_ptr<KeyValueDB> db; @@ -556,10 +564,11 @@ TEST_P(KVTest, RocksDB_estimate_size) { for(int test = 0; test < 20; test++) { KeyValueDB::Transaction t = db->get_transaction(); - bufferlist v1; - v1.append(string(1000, '1')); - for (int i = 0; i < 100; i++) + for (int i = 0; i < 100; i++) { + bufferlist v1; + v1.append(gen_random_string(1000)); t->set("A", to_string(rand()%100000), v1); + } db->submit_transaction_sync(t); db->compact(); @@ -588,10 +597,11 @@ TEST_P(KVTest, RocksDB_estimate_size_column_family) { for(int test = 0; test < 20; test++) { KeyValueDB::Transaction t = db->get_transaction(); - bufferlist v1; - v1.append(string(1000, '1')); - for (int i = 0; i < 100; i++) + for (int i = 0; i < 100; i++) { + bufferlist v1; + v1.append(gen_random_string(1000)); t->set("cf1", to_string(rand()%100000), v1); + } db->submit_transaction_sync(t); db->compact(); diff --git a/src/test/osd/TestMClockScheduler.cc b/src/test/osd/TestMClockScheduler.cc index 8291da268..205ef2f98 100644 --- a/src/test/osd/TestMClockScheduler.cc +++ b/src/test/osd/TestMClockScheduler.cc @@ -31,6 +31,7 @@ public: uint32_t num_shards; int shard_id; bool is_rotational; + unsigned cutoff_priority; MonClient *monc; mClockScheduler q; @@ -43,8 +44,10 @@ public: num_shards(1), shard_id(0), is_rotational(false), + cutoff_priority(12), monc(nullptr), - q(g_ceph_context, whoami, num_shards, shard_id, is_rotational, monc), + q(g_ceph_context, whoami, num_shards, shard_id, is_rotational, + cutoff_priority, monc), client1(1001), client2(9999), client3(100000001) diff --git a/src/test/pybind/test_cephfs.py b/src/test/pybind/test_cephfs.py index d16807de9..247ddca37 100644 --- a/src/test/pybind/test_cephfs.py +++ b/src/test/pybind/test_cephfs.py @@ -10,6 +10,7 @@ import random import time import stat import uuid +import json from datetime import datetime cephfs = None @@ -907,3 +908,35 @@ def test_snapdiff(testdir): # remove directory purge_dir(b"/snapdiff_test"); + +def test_single_target_command(): + command = {'prefix': u'session ls', 'format': 'json'} + mds_spec = "a" + inbuf = b'' + ret, outbl, outs = cephfs.mds_command(mds_spec, json.dumps(command), inbuf) + if outbl: + session_map = json.loads(outbl) + # Standby MDSs will return -38 + assert(ret == 0 or ret == -38) + +def test_multi_target_command(): + mds_get_command = {'prefix': 'status', 'format': 'json'} + inbuf = b'' + ret, outbl, outs = cephfs.mds_command('*', json.dumps(mds_get_command), inbuf) + print(outbl) + mds_status = json.loads(outbl) + print(mds_status) + + command = {'prefix': u'session ls', 'format': 'json'} + mds_spec = "*" + inbuf = b'' + + ret, outbl, outs = cephfs.mds_command(mds_spec, json.dumps(command), inbuf) + # Standby MDSs will return -38 + assert(ret == 0 or ret == -38) + print(outbl) + session_map = json.loads(outbl) + + if isinstance(mds_status, list): # if multi target command result + for mds_sessions in session_map: + assert(list(mds_sessions.keys())[0].startswith('mds.')) diff --git a/src/test/pybind/test_rbd.py b/src/test/pybind/test_rbd.py index 7b5f31b57..df47b0d29 100644 --- a/src/test/pybind/test_rbd.py +++ b/src/test/pybind/test_rbd.py @@ -415,6 +415,18 @@ def test_remove_canceled(tmp_image): assert_raises(OperationCanceled, RBD().remove, ioctx, image_name, on_progress=progress_cb) +def test_remove_with_progress_except(): + create_image() + d = {'received_callback': False} + def progress_cb(current, total): + d['received_callback'] = True + raise Exception() + + # exception is logged and ignored with a Cython warning: + # Exception ignored in: 'rbd.progress_callback' + RBD().remove(ioctx, image_name, on_progress=progress_cb) + eq(True, d['received_callback']) + def test_rename(tmp_image): rbd = RBD() image_name2 = get_temp_image_name() @@ -1251,6 +1263,16 @@ class TestImage(object): assert(comp.get_return_value() < 0) eq(sys.getrefcount(comp), 2) + # test3: except case + def cbex(_, buf): + raise KeyError() + + def test3(): + comp = self.image.aio_read(IMG_SIZE, 20, cbex) + comp.wait_for_complete_and_cb() + + assert_raises(KeyError, test3) + def test_aio_write(self): retval = [None] def cb(comp): @@ -1449,7 +1471,7 @@ def check_diff(image, offset, length, from_snapshot, expected): extents = [] def cb(offset, length, exists): extents.append((offset, length, exists)) - image.diff_iterate(0, IMG_SIZE, None, cb) + image.diff_iterate(0, IMG_SIZE, from_snapshot, cb) eq(extents, expected) class TestClone(object): diff --git a/src/test/rgw/bucket_notification/test_bn.py b/src/test/rgw/bucket_notification/test_bn.py index 87a2acb76..ee89d326d 100644 --- a/src/test/rgw/bucket_notification/test_bn.py +++ b/src/test/rgw/bucket_notification/test_bn.py @@ -2346,6 +2346,71 @@ def test_http_post_object_upload(): conn1.delete_bucket(Bucket=bucket_name) +@attr('mpu_test') +def test_ps_s3_multipart_on_master_http(): + """ test http multipart object upload on master""" + conn = connection() + zonegroup = 'default' + + # create random port for the http server + host = get_ip() + port = random.randint(10000, 20000) + # start an http server in a separate thread + http_server = StreamingHTTPServer(host, port, num_workers=10) + + # create bucket + bucket_name = gen_bucket_name() + bucket = conn.create_bucket(bucket_name) + topic_name = bucket_name + TOPIC_SUFFIX + + # create s3 topic + endpoint_address = 'http://'+host+':'+str(port) + endpoint_args = 'push-endpoint='+endpoint_address + opaque_data = 'http://1.2.3.4:8888' + topic_conf = PSTopicS3(conn, topic_name, zonegroup, endpoint_args=endpoint_args, opaque_data=opaque_data) + topic_arn = topic_conf.set_config() + # create s3 notification + notification_name = bucket_name + NOTIFICATION_SUFFIX + topic_conf_list = [{'Id': notification_name, + 'TopicArn': topic_arn, + 'Events': [] + }] + s3_notification_conf = PSNotificationS3(conn, bucket_name, topic_conf_list) + response, status = s3_notification_conf.set_config() + assert_equal(status/100, 2) + + # create objects in the bucket + client_threads = [] + content = str(os.urandom(20*1024*1024)) + key = bucket.new_key('obj') + thr = threading.Thread(target = set_contents_from_string, args=(key, content,)) + thr.start() + client_threads.append(thr) + [thr.join() for thr in client_threads] + + print('wait for 5sec for the messages...') + time.sleep(5) + + # check http receiver + keys = list(bucket.list()) + print('total number of objects: ' + str(len(keys))) + events = http_server.get_and_reset_events() + for event in events: + assert_equal(event['Records'][0]['opaqueData'], opaque_data) + assert_equal(event['Records'][0]['s3']['object']['eTag'] != '', True) + print(event['Records'][0]['s3']['object']) + + # cleanup + for key in keys: + key.delete() + [thr.join() for thr in client_threads] + topic_conf.del_config() + s3_notification_conf.del_config(notification=notification_name) + # delete the bucket + conn.delete_bucket(bucket_name) + http_server.close() + + @attr('amqp_test') def test_ps_s3_multipart_on_master(): """ test multipart object upload on master""" diff --git a/src/test/rgw/rgw_multi/tests.py b/src/test/rgw/rgw_multi/tests.py index 156fac12e..fee0c9a3a 100644 --- a/src/test/rgw/rgw_multi/tests.py +++ b/src/test/rgw/rgw_multi/tests.py @@ -664,6 +664,39 @@ def test_object_delete(): zone_bucket_checkpoint(target_conn.zone, source_conn.zone, bucket.name) check_bucket_eq(source_conn, target_conn, bucket) +def test_multi_object_delete(): + zonegroup = realm.master_zonegroup() + zonegroup_conns = ZonegroupConns(zonegroup) + buckets, zone_bucket = create_bucket_per_zone(zonegroup_conns) + + objnames = [f'obj{i}' for i in range(1,50)] + content = 'asdasd' + + # don't wait for meta sync just yet + for zone, bucket in zone_bucket: + create_objects(zone, bucket, objnames, content) + + zonegroup_meta_checkpoint(zonegroup) + + # check objects exist + for source_conn, bucket in zone_bucket: + for target_conn in zonegroup_conns.zones: + if source_conn.zone == target_conn.zone: + continue + + zone_bucket_checkpoint(target_conn.zone, source_conn.zone, bucket.name) + check_bucket_eq(source_conn, target_conn, bucket) + + # check object removal + for source_conn, bucket in zone_bucket: + bucket.delete_keys(objnames) + for target_conn in zonegroup_conns.zones: + if source_conn.zone == target_conn.zone: + continue + + zone_bucket_checkpoint(target_conn.zone, source_conn.zone, bucket.name) + check_bucket_eq(source_conn, target_conn, bucket) + def get_latest_object_version(key): for k in key.bucket.list_versions(key.name): if k.is_latest: diff --git a/src/test/rgw/test_rgw_dmclock_scheduler.cc b/src/test/rgw/test_rgw_dmclock_scheduler.cc index 92800767c..28ae78180 100644 --- a/src/test/rgw/test_rgw_dmclock_scheduler.cc +++ b/src/test/rgw/test_rgw_dmclock_scheduler.cc @@ -105,7 +105,7 @@ TEST(Queue, RateLimit) EXPECT_EQ(1u, counters(client_id::admin)->get(queue_counters::l_qlen)); EXPECT_EQ(1u, counters(client_id::auth)->get(queue_counters::l_qlen)); - context.run_for(std::chrono::milliseconds(1)); + context.run_for(std::chrono::milliseconds(50)); EXPECT_TRUE(context.stopped()); ASSERT_TRUE(ec1); @@ -163,7 +163,7 @@ TEST(Queue, AsyncRequest) EXPECT_EQ(1u, counters(client_id::admin)->get(queue_counters::l_qlen)); EXPECT_EQ(1u, counters(client_id::auth)->get(queue_counters::l_qlen)); - context.run_for(std::chrono::milliseconds(1)); + context.run_for(std::chrono::milliseconds(50)); EXPECT_TRUE(context.stopped()); ASSERT_TRUE(ec1); @@ -217,7 +217,7 @@ TEST(Queue, Cancel) EXPECT_FALSE(ec1); EXPECT_FALSE(ec2); - context.run_for(std::chrono::milliseconds(1)); + context.run_for(std::chrono::milliseconds(50)); EXPECT_TRUE(context.stopped()); ASSERT_TRUE(ec1); @@ -265,7 +265,7 @@ TEST(Queue, CancelClient) EXPECT_FALSE(ec1); EXPECT_FALSE(ec2); - context.run_for(std::chrono::milliseconds(1)); + context.run_for(std::chrono::milliseconds(50)); EXPECT_TRUE(context.stopped()); ASSERT_TRUE(ec1); @@ -315,7 +315,7 @@ TEST(Queue, CancelOnDestructor) EXPECT_FALSE(ec1); EXPECT_FALSE(ec2); - context.run_for(std::chrono::milliseconds(1)); + context.run_for(std::chrono::milliseconds(50)); EXPECT_TRUE(context.stopped()); ASSERT_TRUE(ec1); @@ -369,20 +369,20 @@ TEST(Queue, CrossExecutorRequest) EXPECT_EQ(1u, counters(client_id::admin)->get(queue_counters::l_qlen)); EXPECT_EQ(1u, counters(client_id::auth)->get(queue_counters::l_qlen)); - callback_context.run_for(std::chrono::milliseconds(1)); + callback_context.poll(); // maintains work on callback executor while in queue EXPECT_FALSE(callback_context.stopped()); EXPECT_FALSE(ec1); EXPECT_FALSE(ec2); - queue_context.run_for(std::chrono::milliseconds(1)); + queue_context.run_for(std::chrono::milliseconds(50)); EXPECT_TRUE(queue_context.stopped()); EXPECT_FALSE(ec1); // no callbacks until callback executor runs EXPECT_FALSE(ec2); - callback_context.run_for(std::chrono::milliseconds(1)); + callback_context.run_for(std::chrono::milliseconds(50)); EXPECT_TRUE(callback_context.stopped()); ASSERT_TRUE(ec1); @@ -421,7 +421,7 @@ TEST(Queue, SpawnAsyncRequest) EXPECT_EQ(PhaseType::priority, p2); }); - context.run_for(std::chrono::milliseconds(1)); + context.run_for(std::chrono::milliseconds(50)); EXPECT_TRUE(context.stopped()); } diff --git a/src/test/rgw/test_rgw_lc.cc b/src/test/rgw/test_rgw_lc.cc index 83a4cac67..d10b482cb 100644 --- a/src/test/rgw/test_rgw_lc.cc +++ b/src/test/rgw/test_rgw_lc.cc @@ -5,7 +5,6 @@ #include "rgw_lc.h" #include "rgw_lc_s3.h" #include <gtest/gtest.h> -//#include <spawn/spawn.hpp> #include <string> #include <vector> #include <stdexcept> @@ -107,3 +106,239 @@ TEST(TestLCFilterInvalidAnd, XMLDoc3) /* check our flags */ ASSERT_EQ(filter.get_flags(), uint32_t(LCFlagType::none)); } + +struct LCWorkTimeTests : ::testing::Test +{ + CephContext* cct; + std::unique_ptr<RGWLC::LCWorker> worker; + + // expects input in the form of "%m/%d/%y %H:%M:%S"; e.g., "01/15/23 23:59:01" + utime_t get_utime_by_date_time_string(const std::string& date_time_str) + { + struct tm tm{}; + struct timespec ts = {0}; + + strptime(date_time_str.c_str(), "%m/%d/%y %H:%M:%S", &tm); + ts.tv_sec = mktime(&tm); + + return utime_t(ts); + } + + // expects a map from input value (date & time string) to expected result (boolean) + void run_should_work_test(const auto& test_values_to_expectations_map) { + for (const auto& [date_time_str, expected_value] : test_values_to_expectations_map) { + auto ut = get_utime_by_date_time_string(date_time_str); + auto should_work = worker->should_work(ut); + + ASSERT_EQ(should_work, expected_value) + << "input time: " << ut + << " expected: " << expected_value + << " should_work: " << should_work + << " work-time-window: " << cct->_conf->rgw_lifecycle_work_time << std::endl; + } + } + + // expects a map from input value (a tuple of date & time strings) to expected result (seconds) + void run_schedule_next_start_time_test(const auto& test_values_to_expectations_map) { + for (const auto& [date_time_str_tuple, expected_value] : test_values_to_expectations_map) { + auto work_started_at = get_utime_by_date_time_string(std::get<0>(date_time_str_tuple)); + auto work_completed_at = get_utime_by_date_time_string(std::get<1>(date_time_str_tuple)); + auto wait_secs_till_next_start = worker->schedule_next_start_time(work_started_at, work_completed_at); + + ASSERT_EQ(wait_secs_till_next_start, expected_value) + << "work_started_at: " << work_started_at + << " work_completed_at: " << work_completed_at + << " expected: " << expected_value + << " wait_secs_till_next_start: " << wait_secs_till_next_start + << " work-time-window: " << cct->_conf->rgw_lifecycle_work_time << std::endl; + } + } + +protected: + + void SetUp() override { + cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get(); + + cct->_conf->set_value("rgw_lc_max_wp_worker", 0, 0); // no need to create a real workpool + worker = std::make_unique<RGWLC::LCWorker>(nullptr, cct, nullptr, 0); + } + + void TearDown() override { + worker.reset(); + cct->put(); + } +}; + +TEST_F(LCWorkTimeTests, ShouldWorkDefaultWorkTime) +{ + std::unordered_map<std::string, bool> test_values_to_expectations = { + {"01/01/23 00:00:00", true}, + {"01/01/24 00:00:00", true}, // date is not relevant, but only the time-window + {"01/01/23 00:00:01", true}, + {"01/01/23 03:00:00", true}, + {"01/01/23 05:59:59", true}, + {"01/01/23 06:00:00", true}, + {"01/01/23 06:00:59", true}, // seconds don't matter, but only hours and minutes + {"01/01/23 06:01:00", false}, + {"01/01/23 23:59:59", false}, + {"01/02/23 23:59:59", false}, + {"01/01/23 12:00:00", false}, + {"01/01/23 14:00:00", false} + }; + + run_should_work_test(test_values_to_expectations); +} + +TEST_F(LCWorkTimeTests, ShouldWorkCustomWorkTimeEndTimeInTheSameDay) +{ + cct->_conf->rgw_lifecycle_work_time = "14:00-16:00"; + + std::unordered_map<std::string, bool> test_values_to_expectations = { + {"01/01/23 00:00:00", false}, + {"01/01/23 12:00:00", false}, + {"01/01/24 13:59:59", false}, + {"01/01/23 14:00:00", true}, + {"01/01/23 16:00:00", true}, + {"01/01/23 16:00:59", true}, + {"01/01/23 16:01:00", false}, + {"01/01/23 17:00:00", false}, + {"01/01/23 23:59:59", false}, + }; + + run_should_work_test(test_values_to_expectations); +} + +TEST_F(LCWorkTimeTests, ShouldWorkCustomWorkTimeEndTimeInTheSameDay24Hours) +{ + cct->_conf->rgw_lifecycle_work_time = "00:00-23:59"; + + std::unordered_map<std::string, bool> test_values_to_expectations = { + {"01/01/23 23:59:00", true}, + {"01/01/23 23:59:59", true}, + {"01/01/23 00:00:00", true}, + {"01/01/23 00:00:01", true}, + {"01/01/23 00:01:00", true}, + {"01/01/23 01:00:00", true}, + {"01/01/23 12:00:00", true}, + {"01/01/23 17:00:00", true}, + {"01/01/23 23:00:00", true} + }; + + run_should_work_test(test_values_to_expectations); +} + + +TEST_F(LCWorkTimeTests, ShouldWorkCustomWorkTimeEndTimeInTheNextDay) +{ + cct->_conf->rgw_lifecycle_work_time = "14:00-01:00"; + + std::unordered_map<std::string, bool> test_values_to_expectations = { + {"01/01/23 13:59:00", false}, + {"01/01/23 13:59:59", false}, + {"01/01/24 14:00:00", true}, // used-to-fail + {"01/01/24 17:00:00", true}, // used-to-fail + {"01/01/24 23:59:59", true}, // used-to-fail + {"01/01/23 00:00:00", true}, // used-to-fail + {"01/01/23 00:59:59", true}, // used-to-fail + {"01/01/23 01:00:00", true}, // used-to-fail + {"01/01/23 01:00:59", true}, // used-to-fail + {"01/01/23 01:01:00", false}, + {"01/01/23 05:00:00", false}, + {"01/01/23 12:00:00", false}, + {"01/01/23 13:00:00", false} + }; + + run_should_work_test(test_values_to_expectations); +} + +TEST_F(LCWorkTimeTests, ShouldWorkCustomWorkTimeEndTimeInTheNextDay24Hours) +{ + cct->_conf->rgw_lifecycle_work_time = "14:00-13:59"; + + // all of the below cases used-to-fail + std::unordered_map<std::string, bool> test_values_to_expectations = { + {"01/01/23 00:00:00", true}, + {"01/01/23 00:00:01", true}, + {"01/01/23 00:01:00", true}, + {"01/01/24 01:00:00", true}, + {"01/01/24 12:00:00", true}, + {"01/01/24 13:00:00", true}, + {"01/01/24 13:59:00", true}, + {"01/01/24 13:59:59", true}, + {"01/01/23 14:00:00", true}, + {"01/01/23 14:00:01", true}, + {"01/01/23 14:01:00", true}, + {"01/01/23 16:00:00", true}, + {"01/01/23 23:59:00", true}, + {"01/01/23 23:59:59", true}, + }; + + run_should_work_test(test_values_to_expectations); +} + +TEST_F(LCWorkTimeTests, ShouldWorkCustomWorkTimeEndTimeInTheNextDayIrregularMins) +{ + cct->_conf->rgw_lifecycle_work_time = "22:15-03:33"; + + std::unordered_map<std::string, bool> test_values_to_expectations = { + {"01/01/23 22:14:59", false}, + {"01/01/23 22:15:00", true}, // used-to-fail + {"01/01/24 00:00:00", true}, // used-to-fail + {"01/01/24 01:00:00", true}, // used-to-fail + {"01/01/24 02:00:00", true}, // used-to-fail + {"01/01/23 03:33:00", true}, // used-to-fail + {"01/01/23 03:33:59", true}, // used-to-fail + {"01/01/23 03:34:00", false}, + {"01/01/23 04:00:00", false}, + {"01/01/23 12:00:00", false}, + {"01/01/23 22:00:00", false}, + }; + + run_should_work_test(test_values_to_expectations); +} + +TEST_F(LCWorkTimeTests, ShouldWorkCustomWorkTimeStartEndSameHour) +{ + cct->_conf->rgw_lifecycle_work_time = "22:15-22:45"; + + std::unordered_map<std::string, bool> test_values_to_expectations = { + {"01/01/23 22:14:59", false}, + {"01/01/23 22:15:00", true}, + {"01/01/24 22:44:59", true}, + {"01/01/24 22:45:59", true}, + {"01/01/24 22:46:00", false}, + {"01/01/23 23:00:00", false}, + {"01/01/23 00:00:00", false}, + {"01/01/23 12:00:00", false}, + {"01/01/23 21:00:00", false}, + }; + + run_should_work_test(test_values_to_expectations); +} + +TEST_F(LCWorkTimeTests, ScheduleNextStartTime) +{ + cct->_conf->rgw_lifecycle_work_time = "22:15-03:33"; + + // items of the map: [ (work_started_time, work_completed_time), expected_value (seconds) ] + // + // expected_value is the difference between configured start time (i.e, 22:15:00) and + // the second item of the tuple (i.e., work_completed_time). + // + // Note that "seconds" of work completion time is taken into account but date is not relevant. + // e.g., the first testcase: 75713 == 01:13:07 - 22:15:00 (https://tinyurl.com/ydm86752) + std::map<std::tuple<std::string, std::string>, int> test_values_to_expectations = { + {{"01/01/23 22:15:05", "01/01/23 01:13:07"}, 75713}, + {{"01/01/23 22:15:05", "01/02/23 01:13:07"}, 75713}, + {{"01/01/23 22:15:05", "01/01/23 22:17:07"}, 86273}, + {{"01/01/23 22:15:05", "01/02/23 22:17:07"}, 86273}, + {{"01/01/23 22:15:05", "01/01/23 22:14:00"}, 60}, + {{"01/01/23 22:15:05", "01/02/23 22:14:00"}, 60}, + {{"01/01/23 22:15:05", "01/01/23 22:15:00"}, 24 * 60 * 60}, + {{"01/01/23 22:15:05", "01/02/23 22:15:00"}, 24 * 60 * 60}, + {{"01/01/23 22:15:05", "01/01/23 22:15:01"}, 24 * 60 * 60 - 1}, + {{"01/01/23 22:15:05", "01/02/23 22:15:01"}, 24 * 60 * 60 - 1}, + }; + + run_schedule_next_start_time_test(test_values_to_expectations); +} diff --git a/src/test/test_weighted_shuffle.cc b/src/test/test_weighted_shuffle.cc index 9f92cbdc0..efc1cdeb7 100644 --- a/src/test/test_weighted_shuffle.cc +++ b/src/test/test_weighted_shuffle.cc @@ -37,3 +37,55 @@ TEST(WeightedShuffle, Basic) { epsilon); } } + +TEST(WeightedShuffle, ZeroedWeights) { + std::array<char, 5> choices{'a', 'b', 'c', 'd', 'e'}; + std::array<int, 5> weights{0, 0, 0, 0, 0}; + std::map<char, std::array<unsigned, 5>> frequency { + {'a', {0, 0, 0, 0, 0}}, + {'b', {0, 0, 0, 0, 0}}, + {'c', {0, 0, 0, 0, 0}}, + {'d', {0, 0, 0, 0, 0}}, + {'e', {0, 0, 0, 0, 0}} + }; // count each element appearing in each position + const int samples = 10000; + std::random_device rd; + for (auto i = 0; i < samples; i++) { + weighted_shuffle(begin(choices), end(choices), + begin(weights), end(weights), + std::mt19937{rd()}); + for (size_t j = 0; j < choices.size(); ++j) + ++frequency[choices[j]][j]; + } + + for (char ch : choices) { + // all samples on the diagonal + ASSERT_EQ(std::accumulate(begin(frequency[ch]), end(frequency[ch]), 0), + samples); + ASSERT_EQ(frequency[ch][ch-'a'], samples); + } +} + +TEST(WeightedShuffle, SingleNonZeroWeight) { + std::array<char, 5> choices{'a', 'b', 'c', 'd', 'e'}; + std::array<int, 5> weights{0, 42, 0, 0, 0}; + std::map<char, std::array<unsigned, 5>> frequency { + {'a', {0, 0, 0, 0, 0}}, + {'b', {0, 0, 0, 0, 0}}, + {'c', {0, 0, 0, 0, 0}}, + {'d', {0, 0, 0, 0, 0}}, + {'e', {0, 0, 0, 0, 0}} + }; // count each element appearing in each position + const int samples = 10000; + std::random_device rd; + for (auto i = 0; i < samples; i++) { + weighted_shuffle(begin(choices), end(choices), + begin(weights), end(weights), + std::mt19937{rd()}); + for (size_t j = 0; j < choices.size(); ++j) + ++frequency[choices[j]][j]; + } + + // 'b' is always first + ASSERT_EQ(frequency['b'][0], samples); +} |