summaryrefslogtreecommitdiffstats
path: root/src/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-23 16:45:17 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-23 16:45:17 +0000
commitb98f2fb9922af9b7a8ec418716d79ee2a4af5b77 (patch)
treea0f4f617c881a28eb0d52754b15b0a082bb545e1 /src/test
parentAdding debian version 18.2.2-1. (diff)
downloadceph-b98f2fb9922af9b7a8ec418716d79ee2a4af5b77.tar.xz
ceph-b98f2fb9922af9b7a8ec418716d79ee2a4af5b77.zip
Merging upstream version 18.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test')
-rw-r--r--src/test/cli-integration/rbd/snap-diff.t4
-rw-r--r--src/test/cli/rbd/help.t7
-rw-r--r--src/test/client/CMakeLists.txt1
-rw-r--r--src/test/client/commands.cc48
-rw-r--r--src/test/common/CMakeLists.txt11
-rw-r--r--src/test/common/test_mclock_priority_queue.cc320
-rw-r--r--src/test/libcephfs/CMakeLists.txt7
-rw-r--r--src/test/libcephfs/access.cc16
-rw-r--r--src/test/libcephfs/test.cc6
-rw-r--r--src/test/librados/snapshots_cxx.cc60
-rw-r--r--src/test/librbd/CMakeLists.txt3
-rw-r--r--src/test/librbd/deep_copy/test_mock_ImageCopyRequest.cc1
-rw-r--r--src/test/librbd/io/test_mock_ImageRequest.cc31
-rw-r--r--src/test/librbd/io/test_mock_ObjectRequest.cc804
-rw-r--r--src/test/librbd/journal/test_Entries.cc63
-rw-r--r--src/test/librbd/journal/test_Stress.cc121
-rw-r--r--src/test/librbd/mock/MockObjectMap.h12
-rw-r--r--src/test/librbd/object_map/test_mock_DiffRequest.cc2227
-rw-r--r--src/test/librbd/test_librbd.cc873
-rw-r--r--src/test/librbd/test_main.cc2
-rw-r--r--src/test/librbd/test_mock_Journal.cc2
-rw-r--r--src/test/mds/TestMDSAuthCaps.cc90
-rw-r--r--src/test/objectstore/test_kv.cc22
-rw-r--r--src/test/osd/TestMClockScheduler.cc5
-rw-r--r--src/test/pybind/test_cephfs.py33
-rw-r--r--src/test/pybind/test_rbd.py24
-rw-r--r--src/test/rgw/bucket_notification/test_bn.py65
-rw-r--r--src/test/rgw/rgw_multi/tests.py33
-rw-r--r--src/test/rgw/test_rgw_dmclock_scheduler.cc18
-rw-r--r--src/test/rgw/test_rgw_lc.cc237
-rw-r--r--src/test/test_weighted_shuffle.cc52
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);
+}