]> git.proxmox.com Git - ceph.git/blame - ceph/src/test/crimson/seastore/onode_tree/test_staged_fltree.cc
update ceph source to reef 18.2.1
[ceph.git] / ceph / src / test / crimson / seastore / onode_tree / test_staged_fltree.cc
CommitLineData
f67539c2
TL
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
2// vim: ts=8 sw=2 smarttab
3
4#include <array>
5#include <cstring>
6#include <memory>
7#include <set>
8#include <sstream>
9#include <vector>
10
11#include "crimson/common/log.h"
12#include "crimson/os/seastore/onode_manager/staged-fltree/node.h"
20effc67
TL
13#include "crimson/os/seastore/onode_manager/staged-fltree/node_extent_manager/dummy.h"
14#include "crimson/os/seastore/onode_manager/staged-fltree/node_extent_manager/seastore.h"
f67539c2
TL
15#include "crimson/os/seastore/onode_manager/staged-fltree/node_layout.h"
16#include "crimson/os/seastore/onode_manager/staged-fltree/tree.h"
17#include "crimson/os/seastore/onode_manager/staged-fltree/tree_utils.h"
18
19#include "test/crimson/gtest_seastar.h"
20#include "test/crimson/seastore/transaction_manager_test_state.h"
20effc67 21#include "test_value.h"
f67539c2
TL
22
23using namespace crimson::os::seastore::onode;
24
20effc67
TL
25#define INTR(fun, t) \
26 with_trans_intr( \
27 t, \
28 [&] (auto &tr) { \
29 return fun(tr); \
30 } \
31 )
32
33#define INTR_R(fun, t, args...) \
34 with_trans_intr( \
35 t, \
36 [&] (auto &tr) { \
37 return fun(tr, args); \
38 } \
39 )
40
41#define INTR_WITH_PARAM(fun, c, b, v) \
42 with_trans_intr( \
43 c.t, \
44 [=] (auto &t) { \
45 return fun(c, L_ADDR_MIN, b, v); \
46 } \
47 )
48
f67539c2
TL
49namespace {
50 constexpr bool IS_DUMMY_SYNC = false;
20effc67
TL
51 using DummyManager = DummyNodeExtentManager<IS_DUMMY_SYNC>;
52
53 using UnboundedBtree = Btree<UnboundedValue>;
f67539c2
TL
54
55 [[maybe_unused]] seastar::logger& logger() {
56 return crimson::get_logger(ceph_subsys_test);
57 }
58
59 ghobject_t make_ghobj(
60 shard_t shard, pool_t pool, crush_hash_t crush,
61 std::string ns, std::string oid, snap_t snap, gen_t gen) {
62 return ghobject_t{shard_id_t{shard}, pool, crush, ns, oid, snap, gen};
63 }
64
65 // return a key_view_t and its underlying memory buffer.
66 // the buffer needs to be freed manually.
67 std::pair<key_view_t, void*> build_key_view(const ghobject_t& hobj) {
68 key_hobj_t key_hobj(hobj);
69 size_t key_size = sizeof(shard_pool_crush_t) + sizeof(snap_gen_t) +
1e59de90 70 ns_oid_view_t::estimate_size(key_hobj);
f67539c2
TL
71 void* p_mem = std::malloc(key_size);
72
73 key_view_t key_view;
74 char* p_fill = (char*)p_mem + key_size;
75
1e59de90 76 auto spc = shard_pool_crush_t::from_key(key_hobj);
f67539c2
TL
77 p_fill -= sizeof(shard_pool_crush_t);
78 std::memcpy(p_fill, &spc, sizeof(shard_pool_crush_t));
79 key_view.set(*reinterpret_cast<const shard_pool_crush_t*>(p_fill));
80
81 auto p_ns_oid = p_fill;
1e59de90 82 ns_oid_view_t::test_append(key_hobj, p_fill);
f67539c2
TL
83 ns_oid_view_t ns_oid_view(p_ns_oid);
84 key_view.set(ns_oid_view);
85
1e59de90 86 auto sg = snap_gen_t::from_key(key_hobj);
f67539c2
TL
87 p_fill -= sizeof(snap_gen_t);
88 ceph_assert(p_fill == (char*)p_mem);
89 std::memcpy(p_fill, &sg, sizeof(snap_gen_t));
90 key_view.set(*reinterpret_cast<const snap_gen_t*>(p_fill));
91
92 return {key_view, p_mem};
93 }
94}
95
96struct a_basic_test_t : public seastar_test_suite_t {};
97
98TEST_F(a_basic_test_t, 1_basic_sizes)
99{
100 logger().info("\n"
101 "Bytes of struct:\n"
102 " node_header_t: {}\n"
103 " shard_pool_t: {}\n"
104 " shard_pool_crush_t: {}\n"
105 " crush_t: {}\n"
106 " snap_gen_t: {}\n"
107 " slot_0_t: {}\n"
108 " slot_1_t: {}\n"
109 " slot_3_t: {}\n"
110 " node_fields_0_t: {}\n"
111 " node_fields_1_t: {}\n"
112 " node_fields_2_t: {}\n"
113 " internal_fields_3_t: {}\n"
114 " leaf_fields_3_t: {}\n"
115 " internal_sub_item_t: {}",
116 sizeof(node_header_t), sizeof(shard_pool_t),
117 sizeof(shard_pool_crush_t), sizeof(crush_t), sizeof(snap_gen_t),
118 sizeof(slot_0_t), sizeof(slot_1_t), sizeof(slot_3_t),
119 sizeof(node_fields_0_t), sizeof(node_fields_1_t), sizeof(node_fields_2_t),
120 sizeof(internal_fields_3_t), sizeof(leaf_fields_3_t), sizeof(internal_sub_item_t)
121 );
122
123 auto hobj = make_ghobj(0, 0, 0, "n", "o", 0, 0);
124 key_hobj_t key(hobj);
125 auto [key_view, p_mem] = build_key_view(hobj);
20effc67
TL
126 value_config_t value;
127 value.payload_size = 8;
f67539c2
TL
128#define _STAGE_T(NodeType) node_to_stage_t<typename NodeType::node_stage_t>
129#define NXT_T(StageType) staged<typename StageType::next_param_t>
20effc67 130 laddr_t i_value{0};
f67539c2
TL
131 logger().info("\n"
132 "Bytes of a key-value insertion (full-string):\n"
20effc67 133 " s-p-c, 'n'-'o', s-g => value_payload(8): typically internal 43B, leaf 59B\n"
f67539c2
TL
134 " InternalNode0: {} {} {}\n"
135 " InternalNode1: {} {} {}\n"
136 " InternalNode2: {} {}\n"
137 " InternalNode3: {}\n"
138 " LeafNode0: {} {} {}\n"
139 " LeafNode1: {} {} {}\n"
140 " LeafNode2: {} {}\n"
141 " LeafNode3: {}",
1e59de90
TL
142 _STAGE_T(InternalNode0)::insert_size(key_view, i_value),
143 NXT_T(_STAGE_T(InternalNode0))::insert_size(key_view, i_value),
144 NXT_T(NXT_T(_STAGE_T(InternalNode0)))::insert_size(key_view, i_value),
145 _STAGE_T(InternalNode1)::insert_size(key_view, i_value),
146 NXT_T(_STAGE_T(InternalNode1))::insert_size(key_view, i_value),
147 NXT_T(NXT_T(_STAGE_T(InternalNode1)))::insert_size(key_view, i_value),
148 _STAGE_T(InternalNode2)::insert_size(key_view, i_value),
149 NXT_T(_STAGE_T(InternalNode2))::insert_size(key_view, i_value),
150 _STAGE_T(InternalNode3)::insert_size(key_view, i_value),
151 _STAGE_T(LeafNode0)::insert_size(key, value),
152 NXT_T(_STAGE_T(LeafNode0))::insert_size(key, value),
153 NXT_T(NXT_T(_STAGE_T(LeafNode0)))::insert_size(key, value),
154 _STAGE_T(LeafNode1)::insert_size(key, value),
155 NXT_T(_STAGE_T(LeafNode1))::insert_size(key, value),
156 NXT_T(NXT_T(_STAGE_T(LeafNode1)))::insert_size(key, value),
157 _STAGE_T(LeafNode2)::insert_size(key, value),
158 NXT_T(_STAGE_T(LeafNode2))::insert_size(key, value),
159 _STAGE_T(LeafNode3)::insert_size(key, value)
f67539c2
TL
160 );
161 std::free(p_mem);
162}
163
164TEST_F(a_basic_test_t, 2_node_sizes)
165{
20effc67 166 run_async([] {
f67539c2 167 auto nm = NodeExtentManager::create_dummy(IS_DUMMY_SYNC);
20effc67
TL
168 auto t = make_test_transaction();
169 ValueBuilderImpl<UnboundedValue> vb;
170 context_t c{*nm, vb, *t};
f67539c2 171 std::array<std::pair<NodeImplURef, NodeExtentMutable>, 16> nodes = {
20effc67
TL
172 INTR_WITH_PARAM(InternalNode0::allocate, c, false, 1u).unsafe_get0().make_pair(),
173 INTR_WITH_PARAM(InternalNode1::allocate, c, false, 1u).unsafe_get0().make_pair(),
174 INTR_WITH_PARAM(InternalNode2::allocate, c, false, 1u).unsafe_get0().make_pair(),
175 INTR_WITH_PARAM(InternalNode3::allocate, c, false, 1u).unsafe_get0().make_pair(),
176 INTR_WITH_PARAM(InternalNode0::allocate, c, true, 1u).unsafe_get0().make_pair(),
177 INTR_WITH_PARAM(InternalNode1::allocate, c, true, 1u).unsafe_get0().make_pair(),
178 INTR_WITH_PARAM(InternalNode2::allocate, c, true, 1u).unsafe_get0().make_pair(),
179 INTR_WITH_PARAM(InternalNode3::allocate, c, true, 1u).unsafe_get0().make_pair(),
180 INTR_WITH_PARAM(LeafNode0::allocate, c, false, 0u).unsafe_get0().make_pair(),
181 INTR_WITH_PARAM(LeafNode1::allocate, c, false, 0u).unsafe_get0().make_pair(),
182 INTR_WITH_PARAM(LeafNode2::allocate, c, false, 0u).unsafe_get0().make_pair(),
183 INTR_WITH_PARAM(LeafNode3::allocate, c, false, 0u).unsafe_get0().make_pair(),
184 INTR_WITH_PARAM(LeafNode0::allocate, c, true, 0u).unsafe_get0().make_pair(),
185 INTR_WITH_PARAM(LeafNode1::allocate, c, true, 0u).unsafe_get0().make_pair(),
186 INTR_WITH_PARAM(LeafNode2::allocate, c, true, 0u).unsafe_get0().make_pair(),
187 INTR_WITH_PARAM(LeafNode3::allocate, c, true, 0u).unsafe_get0().make_pair()
f67539c2
TL
188 };
189 std::ostringstream oss;
190 oss << "\nallocated nodes:";
191 for (auto iter = nodes.begin(); iter != nodes.end(); ++iter) {
192 oss << "\n ";
193 auto& ref_node = iter->first;
194 ref_node->dump_brief(oss);
195 }
196 logger().info("{}", oss.str());
197 });
198}
199
200struct b_dummy_tree_test_t : public seastar_test_suite_t {
f67539c2 201 TransactionRef ref_t;
20effc67 202 std::unique_ptr<UnboundedBtree> tree;
f67539c2 203
20effc67 204 b_dummy_tree_test_t() = default;
f67539c2
TL
205
206 seastar::future<> set_up_fut() override final {
20effc67
TL
207 ref_t = make_test_transaction();
208 tree.reset(
209 new UnboundedBtree(NodeExtentManager::create_dummy(IS_DUMMY_SYNC))
210 );
211 return INTR(tree->mkfs, *ref_t).handle_error(
f67539c2
TL
212 crimson::ct_error::all_same_way([] {
213 ASSERT_FALSE("Unable to mkfs");
214 })
215 );
216 }
20effc67
TL
217
218 seastar::future<> tear_down_fut() final {
219 ref_t.reset();
220 tree.reset();
221 return seastar::now();
222 }
f67539c2
TL
223};
224
20effc67 225TEST_F(b_dummy_tree_test_t, 3_random_insert_erase_leaf_node)
f67539c2
TL
226{
227 run_async([this] {
228 logger().info("\n---------------------------------------------"
229 "\nrandomized leaf node insert:\n");
20effc67
TL
230 auto key_s = ghobject_t();
231 auto key_e = ghobject_t::get_max();
232 ASSERT_TRUE(INTR_R(tree->find, *ref_t, key_s).unsafe_get0().is_end());
233 ASSERT_TRUE(INTR(tree->begin, *ref_t).unsafe_get0().is_end());
234 ASSERT_TRUE(INTR(tree->last, *ref_t).unsafe_get0().is_end());
235
236 std::map<ghobject_t,
237 std::tuple<test_item_t, UnboundedBtree::Cursor>> insert_history;
238
f67539c2 239 auto f_validate_insert_new = [this, &insert_history] (
20effc67
TL
240 const ghobject_t& key, const test_item_t& value) {
241 auto conf = UnboundedBtree::tree_value_config_t{value.get_payload_size()};
242 auto [cursor, success] = INTR_R(tree->insert,
243 *ref_t, key, conf).unsafe_get0();
244 initialize_cursor_from_item(*ref_t, key, value, cursor, success);
245 insert_history.emplace(key, std::make_tuple(value, cursor));
246 auto cursor_ = INTR_R(tree->find, *ref_t, key).unsafe_get0();
247 ceph_assert(cursor_ != tree->end());
f67539c2 248 ceph_assert(cursor_.value() == cursor.value());
20effc67 249 validate_cursor_from_item(key, value, cursor_);
f67539c2
TL
250 return cursor.value();
251 };
f67539c2 252
20effc67
TL
253 auto f_validate_erase = [this, &insert_history] (const ghobject_t& key) {
254 auto cursor_erase = INTR_R(tree->find, *ref_t, key).unsafe_get0();
255 auto cursor_next = INTR(cursor_erase.get_next, *ref_t).unsafe_get0();
256 auto cursor_ret = INTR_R(tree->erase, *ref_t, cursor_erase).unsafe_get0();
257 ceph_assert(cursor_erase.is_end());
258 ceph_assert(cursor_ret == cursor_next);
259 auto cursor_lb = INTR_R(tree->lower_bound, *ref_t, key).unsafe_get0();
260 ceph_assert(cursor_lb == cursor_next);
261 auto it = insert_history.find(key);
262 ceph_assert(std::get<1>(it->second).is_end());
263 insert_history.erase(it);
264 };
265
266 auto f_insert_erase_insert = [&f_validate_insert_new, &f_validate_erase] (
267 const ghobject_t& key, const test_item_t& value) {
268 f_validate_insert_new(key, value);
269 f_validate_erase(key);
270 return f_validate_insert_new(key, value);
271 };
272
273 auto values = Values<test_item_t>(15);
274
275 // insert key1, value1 at STAGE_LEFT
f67539c2 276 auto key1 = make_ghobj(3, 3, 3, "ns3", "oid3", 3, 3);
20effc67
TL
277 auto value1 = values.pick();
278 auto test_value1 = f_insert_erase_insert(key1, value1);
f67539c2
TL
279
280 // validate lookup
281 {
20effc67 282 auto cursor1_s = INTR_R(tree->lower_bound, *ref_t, key_s).unsafe_get0();
f67539c2 283 ASSERT_EQ(cursor1_s.get_ghobj(), key1);
20effc67
TL
284 ASSERT_EQ(cursor1_s.value(), test_value1);
285 auto cursor1_e = INTR_R(tree->lower_bound, *ref_t, key_e).unsafe_get0();
f67539c2
TL
286 ASSERT_TRUE(cursor1_e.is_end());
287 }
288
20effc67 289 // insert the same key1 with a different value
f67539c2 290 {
20effc67
TL
291 auto value1_dup = values.pick();
292 auto conf = UnboundedBtree::tree_value_config_t{value1_dup.get_payload_size()};
293 auto [cursor1_dup, ret1_dup] = INTR_R(tree->insert,
294 *ref_t, key1, conf).unsafe_get0();
f67539c2 295 ASSERT_FALSE(ret1_dup);
20effc67 296 validate_cursor_from_item(key1, value1, cursor1_dup);
f67539c2
TL
297 }
298
20effc67 299 // insert key2, value2 to key1's left at STAGE_LEFT
f67539c2
TL
300 // insert node front at STAGE_LEFT
301 auto key2 = make_ghobj(2, 2, 2, "ns3", "oid3", 3, 3);
20effc67
TL
302 auto value2 = values.pick();
303 f_insert_erase_insert(key2, value2);
f67539c2 304
20effc67 305 // insert key3, value3 to key1's right at STAGE_LEFT
f67539c2
TL
306 // insert node last at STAGE_LEFT
307 auto key3 = make_ghobj(4, 4, 4, "ns3", "oid3", 3, 3);
20effc67
TL
308 auto value3 = values.pick();
309 f_insert_erase_insert(key3, value3);
f67539c2 310
20effc67 311 // insert key4, value4 to key1's left at STAGE_STRING (collision)
f67539c2 312 auto key4 = make_ghobj(3, 3, 3, "ns2", "oid2", 3, 3);
20effc67
TL
313 auto value4 = values.pick();
314 f_insert_erase_insert(key4, value4);
f67539c2 315
20effc67 316 // insert key5, value5 to key1's right at STAGE_STRING (collision)
f67539c2 317 auto key5 = make_ghobj(3, 3, 3, "ns4", "oid4", 3, 3);
20effc67
TL
318 auto value5 = values.pick();
319 f_insert_erase_insert(key5, value5);
f67539c2 320
20effc67 321 // insert key6, value6 to key1's left at STAGE_RIGHT
f67539c2 322 auto key6 = make_ghobj(3, 3, 3, "ns3", "oid3", 2, 2);
20effc67
TL
323 auto value6 = values.pick();
324 f_insert_erase_insert(key6, value6);
f67539c2 325
20effc67 326 // insert key7, value7 to key1's right at STAGE_RIGHT
f67539c2 327 auto key7 = make_ghobj(3, 3, 3, "ns3", "oid3", 4, 4);
20effc67
TL
328 auto value7 = values.pick();
329 f_insert_erase_insert(key7, value7);
f67539c2
TL
330
331 // insert node front at STAGE_RIGHT
332 auto key8 = make_ghobj(2, 2, 2, "ns3", "oid3", 2, 2);
20effc67
TL
333 auto value8 = values.pick();
334 f_insert_erase_insert(key8, value8);
f67539c2
TL
335
336 // insert node front at STAGE_STRING (collision)
337 auto key9 = make_ghobj(2, 2, 2, "ns2", "oid2", 3, 3);
20effc67
TL
338 auto value9 = values.pick();
339 f_insert_erase_insert(key9, value9);
f67539c2
TL
340
341 // insert node last at STAGE_RIGHT
342 auto key10 = make_ghobj(4, 4, 4, "ns3", "oid3", 4, 4);
20effc67
TL
343 auto value10 = values.pick();
344 f_insert_erase_insert(key10, value10);
f67539c2
TL
345
346 // insert node last at STAGE_STRING (collision)
347 auto key11 = make_ghobj(4, 4, 4, "ns4", "oid4", 3, 3);
20effc67
TL
348 auto value11 = values.pick();
349 f_insert_erase_insert(key11, value11);
f67539c2
TL
350
351 // insert key, value randomly until a perfect 3-ary tree is formed
20effc67
TL
352 std::vector<std::pair<ghobject_t, test_item_t>> kvs{
353 {make_ghobj(2, 2, 2, "ns2", "oid2", 2, 2), values.pick()},
354 {make_ghobj(2, 2, 2, "ns2", "oid2", 4, 4), values.pick()},
355 {make_ghobj(2, 2, 2, "ns3", "oid3", 4, 4), values.pick()},
356 {make_ghobj(2, 2, 2, "ns4", "oid4", 2, 2), values.pick()},
357 {make_ghobj(2, 2, 2, "ns4", "oid4", 3, 3), values.pick()},
358 {make_ghobj(2, 2, 2, "ns4", "oid4", 4, 4), values.pick()},
359 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2), values.pick()},
360 {make_ghobj(3, 3, 3, "ns2", "oid2", 4, 4), values.pick()},
361 {make_ghobj(3, 3, 3, "ns4", "oid4", 2, 2), values.pick()},
362 {make_ghobj(3, 3, 3, "ns4", "oid4", 4, 4), values.pick()},
363 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2), values.pick()},
364 {make_ghobj(4, 4, 4, "ns2", "oid2", 3, 3), values.pick()},
365 {make_ghobj(4, 4, 4, "ns2", "oid2", 4, 4), values.pick()},
366 {make_ghobj(4, 4, 4, "ns3", "oid3", 2, 2), values.pick()},
367 {make_ghobj(4, 4, 4, "ns4", "oid4", 2, 2), values.pick()},
368 {make_ghobj(4, 4, 4, "ns4", "oid4", 4, 4), values.pick()}};
f67539c2
TL
369 auto [smallest_key, smallest_value] = kvs[0];
370 auto [largest_key, largest_value] = kvs[kvs.size() - 1];
1e59de90 371 std::shuffle(kvs.begin(), kvs.end(), std::default_random_engine{});
20effc67
TL
372 std::for_each(kvs.begin(), kvs.end(), [&f_insert_erase_insert] (auto& kv) {
373 f_insert_erase_insert(kv.first, kv.second);
f67539c2 374 });
20effc67
TL
375 ASSERT_EQ(INTR(tree->height, *ref_t).unsafe_get0(), 1);
376 ASSERT_FALSE(tree->test_is_clean());
f67539c2 377
20effc67
TL
378 for (auto& [k, val] : insert_history) {
379 auto& [v, c] = val;
f67539c2 380 // validate values in tree keep intact
20effc67
TL
381 auto cursor = with_trans_intr(*ref_t, [this, &k=k](auto& tr) {
382 return tree->find(tr, k);
383 }).unsafe_get0();
384 EXPECT_NE(cursor, tree->end());
385 validate_cursor_from_item(k, v, cursor);
f67539c2 386 // validate values in cursors keep intact
20effc67
TL
387 validate_cursor_from_item(k, v, c);
388 }
389 {
390 auto cursor = INTR_R(tree->lower_bound, *ref_t, key_s).unsafe_get0();
391 validate_cursor_from_item(smallest_key, smallest_value, cursor);
392 }
393 {
394 auto cursor = INTR(tree->begin, *ref_t).unsafe_get0();
395 validate_cursor_from_item(smallest_key, smallest_value, cursor);
396 }
397 {
398 auto cursor = INTR(tree->last, *ref_t).unsafe_get0();
399 validate_cursor_from_item(largest_key, largest_value, cursor);
400 }
401
402 // validate range query
403 {
404 kvs.clear();
405 for (auto& [k, val] : insert_history) {
406 auto& [v, c] = val;
407 kvs.emplace_back(k, v);
408 }
409 insert_history.clear();
410 std::sort(kvs.begin(), kvs.end(), [](auto& l, auto& r) {
411 return l.first < r.first;
412 });
413 auto cursor = INTR(tree->begin, *ref_t).unsafe_get0();
414 for (auto& [k, v] : kvs) {
415 ASSERT_FALSE(cursor.is_end());
416 validate_cursor_from_item(k, v, cursor);
417 cursor = INTR(cursor.get_next, *ref_t).unsafe_get0();
418 }
419 ASSERT_TRUE(cursor.is_end());
f67539c2 420 }
f67539c2
TL
421
422 std::ostringstream oss;
20effc67 423 tree->dump(*ref_t, oss);
f67539c2
TL
424 logger().info("\n{}\n", oss.str());
425
20effc67 426 // randomized erase until empty
1e59de90 427 std::shuffle(kvs.begin(), kvs.end(), std::default_random_engine{});
20effc67
TL
428 for (auto& [k, v] : kvs) {
429 auto e_size = with_trans_intr(*ref_t, [this, &k=k](auto& tr) {
430 return tree->erase(tr, k);
431 }).unsafe_get0();
432 ASSERT_EQ(e_size, 1);
433 }
434 auto cursor = INTR(tree->begin, *ref_t).unsafe_get0();
435 ASSERT_TRUE(cursor.is_end());
436 ASSERT_EQ(INTR(tree->height, *ref_t).unsafe_get0(), 1);
f67539c2
TL
437 });
438}
439
440static std::set<ghobject_t> build_key_set(
441 std::pair<unsigned, unsigned> range_2,
442 std::pair<unsigned, unsigned> range_1,
443 std::pair<unsigned, unsigned> range_0,
444 std::string padding = "",
445 bool is_internal = false) {
446 ceph_assert(range_1.second <= 10);
447 std::set<ghobject_t> ret;
448 ghobject_t key;
449 for (unsigned i = range_2.first; i < range_2.second; ++i) {
450 for (unsigned j = range_1.first; j < range_1.second; ++j) {
451 for (unsigned k = range_0.first; k < range_0.second; ++k) {
452 std::ostringstream os_ns;
453 os_ns << "ns" << j;
454 std::ostringstream os_oid;
455 os_oid << "oid" << j << padding;
456 key = make_ghobj(i, i, i, os_ns.str(), os_oid.str(), k, k);
457 ret.insert(key);
458 }
459 }
460 }
461 if (is_internal) {
462 ret.insert(make_ghobj(9, 9, 9, "ns~last", "oid~last", 9, 9));
463 }
464 return ret;
465}
466
467class TestTree {
468 public:
469 TestTree()
470 : moved_nm{NodeExtentManager::create_dummy(IS_DUMMY_SYNC)},
20effc67 471 ref_t{make_test_transaction()},
f67539c2 472 t{*ref_t},
20effc67 473 c{*moved_nm, vb, t},
f67539c2 474 tree{std::move(moved_nm)},
20effc67 475 values{0} {}
f67539c2
TL
476
477 seastar::future<> build_tree(
478 std::pair<unsigned, unsigned> range_2,
479 std::pair<unsigned, unsigned> range_1,
480 std::pair<unsigned, unsigned> range_0,
20effc67
TL
481 size_t value_size) {
482 return seastar::async([this, range_2, range_1, range_0, value_size] {
483 INTR(tree.mkfs, t).unsafe_get0();
f67539c2
TL
484 //logger().info("\n---------------------------------------------"
485 // "\nbefore leaf node split:\n");
486 auto keys = build_key_set(range_2, range_1, range_0);
487 for (auto& key : keys) {
20effc67 488 auto value = values.create(value_size);
f67539c2
TL
489 insert_tree(key, value).get0();
490 }
20effc67 491 ASSERT_EQ(INTR(tree.height, t).unsafe_get0(), 1);
f67539c2
TL
492 ASSERT_FALSE(tree.test_is_clean());
493 //std::ostringstream oss;
494 //tree.dump(t, oss);
495 //logger().info("\n{}\n", oss.str());
496 });
497 }
498
499 seastar::future<> build_tree(
20effc67 500 const std::vector<ghobject_t>& keys, const std::vector<test_item_t>& values) {
f67539c2 501 return seastar::async([this, keys, values] {
20effc67 502 INTR(tree.mkfs, t).unsafe_get0();
f67539c2
TL
503 //logger().info("\n---------------------------------------------"
504 // "\nbefore leaf node split:\n");
505 ASSERT_EQ(keys.size(), values.size());
506 auto key_iter = keys.begin();
507 auto value_iter = values.begin();
508 while (key_iter != keys.end()) {
20effc67 509 insert_tree(*key_iter, *value_iter).get0();
f67539c2
TL
510 ++key_iter;
511 ++value_iter;
512 }
20effc67 513 ASSERT_EQ(INTR(tree.height, t).unsafe_get0(), 1);
f67539c2
TL
514 ASSERT_FALSE(tree.test_is_clean());
515 //std::ostringstream oss;
516 //tree.dump(t, oss);
517 //logger().info("\n{}\n", oss.str());
518 });
519 }
520
20effc67
TL
521 seastar::future<> split_merge(
522 const ghobject_t& key,
523 const test_item_t& value,
524 const split_expectation_t& expected,
525 std::optional<ghobject_t> next_key) {
526 return seastar::async([this, key, value, expected, next_key] {
527 // clone
528 auto ref_dummy = NodeExtentManager::create_dummy(IS_DUMMY_SYNC);
529 auto p_dummy = static_cast<DummyManager*>(ref_dummy.get());
530 UnboundedBtree tree_clone(std::move(ref_dummy));
531 auto ref_t_clone = make_test_transaction();
f67539c2 532 Transaction& t_clone = *ref_t_clone;
20effc67
TL
533 INTR_R(tree_clone.test_clone_from, t_clone, t, tree).unsafe_get0();
534
535 // insert and split
536 logger().info("\n\nINSERT-SPLIT {}:", key_hobj_t(key));
537 auto conf = UnboundedBtree::tree_value_config_t{value.get_payload_size()};
538 auto [cursor, success] = INTR_R(tree_clone.insert,
539 t_clone, key, conf).unsafe_get0();
540 initialize_cursor_from_item(t, key, value, cursor, success);
541
542 {
543 std::ostringstream oss;
544 tree_clone.dump(t_clone, oss);
545 logger().info("dump new root:\n{}", oss.str());
546 }
547 EXPECT_EQ(INTR(tree_clone.height, t_clone).unsafe_get0(), 2);
548
549 for (auto& [k, val] : insert_history) {
550 auto& [v, c] = val;
551 auto result = with_trans_intr(t_clone, [&tree_clone, &k=k] (auto& tr) {
552 return tree_clone.find(tr, k);
553 }).unsafe_get0();
554 EXPECT_NE(result, tree_clone.end());
555 validate_cursor_from_item(k, v, result);
556 }
557 auto result = INTR_R(tree_clone.find, t_clone, key).unsafe_get0();
558 EXPECT_NE(result, tree_clone.end());
559 validate_cursor_from_item(key, value, result);
560 EXPECT_TRUE(last_split.match(expected));
561 EXPECT_EQ(p_dummy->size(), 3);
562
563 // erase and merge
564 logger().info("\n\nERASE-MERGE {}:", key_hobj_t(key));
565 auto nxt_cursor = with_trans_intr(t_clone, [&cursor=cursor](auto& tr) {
566 return cursor.erase<true>(tr);
567 }).unsafe_get0();
568
569 {
570 // track root again to dump
571 auto begin = INTR(tree_clone.begin, t_clone).unsafe_get0();
572 std::ignore = begin;
573 std::ostringstream oss;
574 tree_clone.dump(t_clone, oss);
575 logger().info("dump root:\n{}", oss.str());
576 }
f67539c2 577
20effc67
TL
578 if (next_key.has_value()) {
579 auto found = insert_history.find(*next_key);
580 ceph_assert(found != insert_history.end());
581 validate_cursor_from_item(
582 *next_key, std::get<0>(found->second), nxt_cursor);
583 } else {
584 EXPECT_TRUE(nxt_cursor.is_end());
585 }
f67539c2 586
20effc67
TL
587 for (auto& [k, val] : insert_history) {
588 auto& [v, c] = val;
589 auto result = with_trans_intr(t_clone, [&tree_clone, &k=k](auto& tr) {
590 return tree_clone.find(tr, k);
591 }).unsafe_get0();
592 EXPECT_NE(result, tree_clone.end());
593 validate_cursor_from_item(k, v, result);
f67539c2 594 }
20effc67
TL
595 EXPECT_EQ(INTR(tree_clone.height, t_clone).unsafe_get0(), 1);
596 EXPECT_EQ(p_dummy->size(), 1);
f67539c2
TL
597 });
598 }
599
20effc67
TL
600 test_item_t create_value(size_t size) {
601 return values.create(size);
f67539c2
TL
602 }
603
604 private:
20effc67 605 seastar::future<> insert_tree(const ghobject_t& key, const test_item_t& value) {
f67539c2 606 return seastar::async([this, &key, &value] {
20effc67
TL
607 auto conf = UnboundedBtree::tree_value_config_t{value.get_payload_size()};
608 auto [cursor, success] = INTR_R(tree.insert,
609 t, key, conf).unsafe_get0();
610 initialize_cursor_from_item(t, key, value, cursor, success);
611 insert_history.emplace(key, std::make_tuple(value, cursor));
f67539c2
TL
612 });
613 }
614
615 NodeExtentManagerURef moved_nm;
616 TransactionRef ref_t;
617 Transaction& t;
20effc67 618 ValueBuilderImpl<UnboundedValue> vb;
f67539c2 619 context_t c;
20effc67
TL
620 UnboundedBtree tree;
621 Values<test_item_t> values;
622 std::map<ghobject_t,
623 std::tuple<test_item_t, UnboundedBtree::Cursor>> insert_history;
f67539c2
TL
624};
625
626struct c_dummy_test_t : public seastar_test_suite_t {};
627
20effc67 628TEST_F(c_dummy_test_t, 4_split_merge_leaf_node)
f67539c2 629{
20effc67 630 run_async([] {
f67539c2
TL
631 {
632 TestTree test;
633 test.build_tree({2, 5}, {2, 5}, {2, 5}, 120).get0();
634
20effc67 635 auto value = test.create_value(1144);
f67539c2
TL
636 logger().info("\n---------------------------------------------"
637 "\nsplit at stage 2; insert to left front at stage 2, 1, 0\n");
20effc67
TL
638 test.split_merge(make_ghobj(1, 1, 1, "ns3", "oid3", 3, 3), value,
639 {2u, 2u, true, InsertType::BEGIN},
640 {make_ghobj(2, 2, 2, "ns2", "oid2", 2, 2)}).get0();
641 test.split_merge(make_ghobj(2, 2, 2, "ns1", "oid1", 3, 3), value,
642 {2u, 1u, true, InsertType::BEGIN},
643 {make_ghobj(2, 2, 2, "ns2", "oid2", 2, 2)}).get0();
644 test.split_merge(make_ghobj(2, 2, 2, "ns2", "oid2", 1, 1), value,
645 {2u, 0u, true, InsertType::BEGIN},
646 {make_ghobj(2, 2, 2, "ns2", "oid2", 2, 2)}).get0();
f67539c2
TL
647
648 logger().info("\n---------------------------------------------"
649 "\nsplit at stage 2; insert to left back at stage 0, 1, 2, 1, 0\n");
20effc67
TL
650 test.split_merge(make_ghobj(2, 2, 2, "ns4", "oid4", 5, 5), value,
651 {2u, 0u, true, InsertType::LAST},
652 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
653 test.split_merge(make_ghobj(2, 2, 2, "ns5", "oid5", 3, 3), value,
654 {2u, 1u, true, InsertType::LAST},
655 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
656 test.split_merge(make_ghobj(2, 3, 3, "ns3", "oid3", 3, 3), value,
657 {2u, 2u, true, InsertType::LAST},
658 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
659 test.split_merge(make_ghobj(3, 3, 3, "ns1", "oid1", 3, 3), value,
660 {2u, 1u, true, InsertType::LAST},
661 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
662 test.split_merge(make_ghobj(3, 3, 3, "ns2", "oid2", 1, 1), value,
663 {2u, 0u, true, InsertType::LAST},
664 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
665
666 auto value0 = test.create_value(1416);
f67539c2
TL
667 logger().info("\n---------------------------------------------"
668 "\nsplit at stage 2; insert to right front at stage 0, 1, 2, 1, 0\n");
20effc67
TL
669 test.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4", 5, 5), value0,
670 {2u, 0u, false, InsertType::BEGIN},
671 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
672 test.split_merge(make_ghobj(3, 3, 3, "ns5", "oid5", 3, 3), value0,
673 {2u, 1u, false, InsertType::BEGIN},
674 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
675 test.split_merge(make_ghobj(3, 4, 4, "ns3", "oid3", 3, 3), value0,
676 {2u, 2u, false, InsertType::BEGIN},
677 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
678 test.split_merge(make_ghobj(4, 4, 4, "ns1", "oid1", 3, 3), value0,
679 {2u, 1u, false, InsertType::BEGIN},
680 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
681 test.split_merge(make_ghobj(4, 4, 4, "ns2", "oid2", 1, 1), value0,
682 {2u, 0u, false, InsertType::BEGIN},
683 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
f67539c2
TL
684
685 logger().info("\n---------------------------------------------"
686 "\nsplit at stage 2; insert to right back at stage 0, 1, 2\n");
20effc67
TL
687 test.split_merge(make_ghobj(4, 4, 4, "ns4", "oid4", 5, 5), value0,
688 {2u, 0u, false, InsertType::LAST},
689 std::nullopt).get0();
690 test.split_merge(make_ghobj(4, 4, 4, "ns5", "oid5", 3, 3), value0,
691 {2u, 1u, false, InsertType::LAST},
692 std::nullopt).get0();
693 test.split_merge(make_ghobj(5, 5, 5, "ns3", "oid3", 3, 3), value0,
694 {2u, 2u, false, InsertType::LAST},
695 std::nullopt).get0();
696
697 auto value1 = test.create_value(316);
f67539c2
TL
698 logger().info("\n---------------------------------------------"
699 "\nsplit at stage 1; insert to left middle at stage 0, 1, 2, 1, 0\n");
20effc67
TL
700 test.split_merge(make_ghobj(2, 2, 2, "ns4", "oid4", 5, 5), value1,
701 {1u, 0u, true, InsertType::MID},
702 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
703 test.split_merge(make_ghobj(2, 2, 2, "ns5", "oid5", 3, 3), value1,
704 {1u, 1u, true, InsertType::MID},
705 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
706 test.split_merge(make_ghobj(2, 2, 3, "ns3", "oid3", 3, 3), value1,
707 {1u, 2u, true, InsertType::MID},
708 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
709 test.split_merge(make_ghobj(3, 3, 3, "ns1", "oid1", 3, 3), value1,
710 {1u, 1u, true, InsertType::MID},
711 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
712 test.split_merge(make_ghobj(3, 3, 3, "ns2", "oid2", 1, 1), value1,
713 {1u, 0u, true, InsertType::MID},
714 {make_ghobj(3, 3, 3, "ns2", "oid2", 2, 2)}).get0();
f67539c2
TL
715
716 logger().info("\n---------------------------------------------"
717 "\nsplit at stage 1; insert to left back at stage 0, 1, 0\n");
20effc67
TL
718 test.split_merge(make_ghobj(3, 3, 3, "ns2", "oid2", 5, 5), value1,
719 {1u, 0u, true, InsertType::LAST},
720 {make_ghobj(3, 3, 3, "ns3", "oid3", 2, 2)}).get0();
721 test.split_merge(make_ghobj(3, 3, 3, "ns2", "oid3", 3, 3), value1,
722 {1u, 1u, true, InsertType::LAST},
723 {make_ghobj(3, 3, 3, "ns3", "oid3", 2, 2)}).get0();
724 test.split_merge(make_ghobj(3, 3, 3, "ns3", "oid3", 1, 1), value1,
725 {1u, 0u, true, InsertType::LAST},
726 {make_ghobj(3, 3, 3, "ns3", "oid3", 2, 2)}).get0();
727
728 auto value2 = test.create_value(452);
f67539c2
TL
729 logger().info("\n---------------------------------------------"
730 "\nsplit at stage 1; insert to right front at stage 0, 1, 0\n");
20effc67
TL
731 test.split_merge(make_ghobj(3, 3, 3, "ns3", "oid3", 5, 5), value2,
732 {1u, 0u, false, InsertType::BEGIN},
733 {make_ghobj(3, 3, 3, "ns4", "oid4", 2, 2)}).get0();
734 test.split_merge(make_ghobj(3, 3, 3, "ns3", "oid4", 3, 3), value2,
735 {1u, 1u, false, InsertType::BEGIN},
736 {make_ghobj(3, 3, 3, "ns4", "oid4", 2, 2)}).get0();
737 test.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4", 1, 1), value2,
738 {1u, 0u, false, InsertType::BEGIN},
739 {make_ghobj(3, 3, 3, "ns4", "oid4", 2, 2)}).get0();
f67539c2
TL
740
741 logger().info("\n---------------------------------------------"
742 "\nsplit at stage 1; insert to right middle at stage 0, 1, 2, 1, 0\n");
20effc67
TL
743 test.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4", 5, 5), value2,
744 {1u, 0u, false, InsertType::MID},
745 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
746 test.split_merge(make_ghobj(3, 3, 3, "ns5", "oid5", 3, 3), value2,
747 {1u, 1u, false, InsertType::MID},
748 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
749 test.split_merge(make_ghobj(3, 3, 4, "ns3", "oid3", 3, 3), value2,
750 {1u, 2u, false, InsertType::MID},
751 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
752 test.split_merge(make_ghobj(4, 4, 4, "ns1", "oid1", 3, 3), value2,
753 {1u, 1u, false, InsertType::MID},
754 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
755 test.split_merge(make_ghobj(4, 4, 4, "ns2", "oid2", 1, 1), value2,
756 {1u, 0u, false, InsertType::MID},
757 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
758
759 auto value3 = test.create_value(834);
f67539c2
TL
760 logger().info("\n---------------------------------------------"
761 "\nsplit at stage 0; insert to right middle at stage 0, 1, 2, 1, 0\n");
20effc67
TL
762 test.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4", 5, 5), value3,
763 {0u, 0u, false, InsertType::MID},
764 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
765 test.split_merge(make_ghobj(3, 3, 3, "ns5", "oid5", 3, 3), value3,
766 {0u, 1u, false, InsertType::MID},
767 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
768 test.split_merge(make_ghobj(3, 3, 4, "ns3", "oid3", 3, 3), value3,
769 {0u, 2u, false, InsertType::MID},
770 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
771 test.split_merge(make_ghobj(4, 4, 4, "ns1", "oid1", 3, 3), value3,
772 {0u, 1u, false, InsertType::MID},
773 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
774 test.split_merge(make_ghobj(4, 4, 4, "ns2", "oid2", 1, 1), value3,
775 {0u, 0u, false, InsertType::MID},
776 {make_ghobj(4, 4, 4, "ns2", "oid2", 2, 2)}).get0();
f67539c2
TL
777
778 logger().info("\n---------------------------------------------"
779 "\nsplit at stage 0; insert to right front at stage 0\n");
20effc67
TL
780 test.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4", 2, 3), value3,
781 {0u, 0u, false, InsertType::BEGIN},
782 {make_ghobj(3, 3, 3, "ns4", "oid4", 3, 3)}).get0();
f67539c2 783
20effc67 784 auto value4 = test.create_value(572);
f67539c2
TL
785 logger().info("\n---------------------------------------------"
786 "\nsplit at stage 0; insert to left back at stage 0\n");
20effc67
TL
787 test.split_merge(make_ghobj(3, 3, 3, "ns2", "oid2", 3, 4), value4,
788 {0u, 0u, true, InsertType::LAST},
789 {make_ghobj(3, 3, 3, "ns2", "oid2", 4, 4)}).get0();
f67539c2
TL
790 }
791
792 {
793 TestTree test;
794 test.build_tree({2, 4}, {2, 4}, {2, 4}, 232).get0();
20effc67 795 auto value = test.create_value(1996);
f67539c2
TL
796 logger().info("\n---------------------------------------------"
797 "\nsplit at [0, 0, 0]; insert to left front at stage 2, 1, 0\n");
20effc67
TL
798 test.split_merge(make_ghobj(1, 1, 1, "ns3", "oid3", 3, 3), value,
799 {2u, 2u, true, InsertType::BEGIN},
800 {make_ghobj(2, 2, 2, "ns2", "oid2", 2, 2)}).get0();
f67539c2 801 EXPECT_TRUE(last_split.match_split_pos({0, {0, {0}}}));
20effc67
TL
802 test.split_merge(make_ghobj(2, 2, 2, "ns1", "oid1", 3, 3), value,
803 {2u, 1u, true, InsertType::BEGIN},
804 {make_ghobj(2, 2, 2, "ns2", "oid2", 2, 2)}).get0();
f67539c2 805 EXPECT_TRUE(last_split.match_split_pos({0, {0, {0}}}));
20effc67
TL
806 test.split_merge(make_ghobj(2, 2, 2, "ns2", "oid2", 1, 1), value,
807 {2u, 0u, true, InsertType::BEGIN},
808 {make_ghobj(2, 2, 2, "ns2", "oid2", 2, 2)}).get0();
f67539c2
TL
809 EXPECT_TRUE(last_split.match_split_pos({0, {0, {0}}}));
810 }
811
812 {
813 TestTree test;
814 std::vector<ghobject_t> keys = {
815 make_ghobj(2, 2, 2, "ns3", "oid3", 3, 3),
816 make_ghobj(3, 3, 3, "ns3", "oid3", 3, 3)};
20effc67
TL
817 std::vector<test_item_t> values = {
818 test.create_value(1360),
819 test.create_value(1632)};
f67539c2 820 test.build_tree(keys, values).get0();
20effc67 821 auto value = test.create_value(1640);
f67539c2
TL
822 logger().info("\n---------------------------------------------"
823 "\nsplit at [END, END, END]; insert to right at stage 0, 1, 2\n");
20effc67
TL
824 test.split_merge(make_ghobj(3, 3, 3, "ns3", "oid3", 4, 4), value,
825 {0u, 0u, false, InsertType::BEGIN},
826 std::nullopt).get0();
f67539c2 827 EXPECT_TRUE(last_split.match_split_pos({1, {0, {1}}}));
20effc67
TL
828 test.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4", 3, 3), value,
829 {1u, 1u, false, InsertType::BEGIN},
830 std::nullopt).get0();
f67539c2 831 EXPECT_TRUE(last_split.match_split_pos({1, {1, {0}}}));
20effc67
TL
832 test.split_merge(make_ghobj(4, 4, 4, "ns3", "oid3", 3, 3), value,
833 {2u, 2u, false, InsertType::BEGIN},
834 std::nullopt).get0();
f67539c2
TL
835 EXPECT_TRUE(last_split.match_split_pos({2, {0, {0}}}));
836 }
837 });
838}
839
840namespace crimson::os::seastore::onode {
841
842class DummyChildPool {
843 class DummyChildImpl final : public NodeImpl {
844 public:
845 using URef = std::unique_ptr<DummyChildImpl>;
846 DummyChildImpl(const std::set<ghobject_t>& keys, bool is_level_tail, laddr_t laddr)
847 : keys{keys}, _is_level_tail{is_level_tail}, _laddr{laddr} {
848 std::tie(key_view, p_mem_key_view) = build_key_view(*keys.crbegin());
20effc67 849 build_name();
f67539c2
TL
850 }
851 ~DummyChildImpl() override {
852 std::free(p_mem_key_view);
853 }
854
855 const std::set<ghobject_t>& get_keys() const { return keys; }
856
857 void reset(const std::set<ghobject_t>& _keys, bool level_tail) {
858 keys = _keys;
859 _is_level_tail = level_tail;
860 std::free(p_mem_key_view);
861 std::tie(key_view, p_mem_key_view) = build_key_view(*keys.crbegin());
20effc67 862 build_name();
f67539c2
TL
863 }
864
865 public:
866 laddr_t laddr() const override { return _laddr; }
867 bool is_level_tail() const override { return _is_level_tail; }
20effc67
TL
868 std::optional<key_view_t> get_pivot_index() const override { return {key_view}; }
869 bool is_extent_retired() const override { return _is_extent_retired; }
870 const std::string& get_name() const override { return name; }
871 search_position_t make_tail() override {
872 _is_level_tail = true;
873 build_name();
874 return search_position_t::end();
875 }
876 eagain_ifuture<> retire_extent(context_t) override {
877 assert(!_is_extent_retired);
878 _is_extent_retired = true;
879 return eagain_iertr::now();
880 }
f67539c2
TL
881
882 protected:
20effc67 883 node_type_t node_type() const override { return node_type_t::LEAF; }
f67539c2 884 field_type_t field_type() const override { return field_type_t::N0; }
20effc67
TL
885 const char* read() const override {
886 ceph_abort("impossible path"); }
887 extent_len_t get_node_size() const override {
888 ceph_abort("impossible path"); }
889 nextent_state_t get_extent_state() const override {
890 ceph_abort("impossible path"); }
f67539c2 891 level_t level() const override { return 0u; }
f67539c2
TL
892 void prepare_mutate(context_t) override {
893 ceph_abort("impossible path"); }
20effc67
TL
894 void validate_non_empty() const override {
895 ceph_abort("impossible path"); }
896 bool is_keys_empty() const override {
897 ceph_abort("impossible path"); }
898 bool has_single_value() const override {
f67539c2
TL
899 ceph_abort("impossible path"); }
900 node_offset_t free_size() const override {
901 ceph_abort("impossible path"); }
20effc67 902 extent_len_t total_size() const override {
f67539c2 903 ceph_abort("impossible path"); }
20effc67
TL
904 bool is_size_underflow() const override {
905 ceph_abort("impossible path"); }
906 std::tuple<match_stage_t, search_position_t> erase(const search_position_t&) override {
907 ceph_abort("impossible path"); }
908 std::tuple<match_stage_t, std::size_t> evaluate_merge(NodeImpl&) override {
909 ceph_abort("impossible path"); }
910 search_position_t merge(NodeExtentMutable&, NodeImpl&, match_stage_t, extent_len_t) override {
911 ceph_abort("impossible path"); }
912 eagain_ifuture<NodeExtentMutable> rebuild_extent(context_t) override {
f67539c2
TL
913 ceph_abort("impossible path"); }
914 node_stats_t get_stats() const override {
915 ceph_abort("impossible path"); }
916 std::ostream& dump(std::ostream&) const override {
917 ceph_abort("impossible path"); }
918 std::ostream& dump_brief(std::ostream&) const override {
919 ceph_abort("impossible path"); }
920 void validate_layout() const override {
921 ceph_abort("impossible path"); }
922 void test_copy_to(NodeExtentMutable&) const override {
923 ceph_abort("impossible path"); }
924 void test_set_tail(NodeExtentMutable&) override {
925 ceph_abort("impossible path"); }
926
927 private:
20effc67
TL
928 void build_name() {
929 std::ostringstream sos;
930 sos << "DummyNode"
931 << "@0x" << std::hex << laddr() << std::dec
932 << "Lv" << (unsigned)level()
933 << (is_level_tail() ? "$" : "")
934 << "(" << key_view << ")";
935 name = sos.str();
936 }
937
f67539c2
TL
938 std::set<ghobject_t> keys;
939 bool _is_level_tail;
940 laddr_t _laddr;
20effc67
TL
941 std::string name;
942 bool _is_extent_retired = false;
f67539c2
TL
943
944 key_view_t key_view;
945 void* p_mem_key_view;
946 };
947
948 class DummyChild final : public Node {
949 public:
950 ~DummyChild() override = default;
951
20effc67
TL
952 key_view_t get_pivot_key() const { return *impl->get_pivot_index(); }
953
954 eagain_ifuture<> populate_split(
f67539c2
TL
955 context_t c, std::set<Ref<DummyChild>>& splitable_nodes) {
956 ceph_assert(can_split());
957 ceph_assert(splitable_nodes.find(this) != splitable_nodes.end());
958
959 size_t index;
960 const auto& keys = impl->get_keys();
961 if (keys.size() == 2) {
962 index = 1;
963 } else {
964 index = rd() % (keys.size() - 2) + 1;
965 }
966 auto iter = keys.begin();
967 std::advance(iter, index);
968
969 std::set<ghobject_t> left_keys(keys.begin(), iter);
970 std::set<ghobject_t> right_keys(iter, keys.end());
971 bool right_is_tail = impl->is_level_tail();
972 impl->reset(left_keys, false);
973 auto right_child = DummyChild::create_new(right_keys, right_is_tail, pool);
974 if (!can_split()) {
975 splitable_nodes.erase(this);
976 }
977 if (right_child->can_split()) {
978 splitable_nodes.insert(right_child);
979 }
20effc67
TL
980 Ref<Node> this_ref = this;
981 return apply_split_to_parent(
982 c, std::move(this_ref), std::move(right_child), false);
f67539c2
TL
983 }
984
20effc67 985 eagain_ifuture<> insert_and_split(
f67539c2
TL
986 context_t c, const ghobject_t& insert_key,
987 std::set<Ref<DummyChild>>& splitable_nodes) {
988 const auto& keys = impl->get_keys();
989 ceph_assert(keys.size() == 1);
990 auto& key = *keys.begin();
991 ceph_assert(insert_key < key);
992
993 std::set<ghobject_t> new_keys;
994 new_keys.insert(insert_key);
995 new_keys.insert(key);
996 impl->reset(new_keys, impl->is_level_tail());
997
998 splitable_nodes.clear();
999 splitable_nodes.insert(this);
1000 auto fut = populate_split(c, splitable_nodes);
1001 ceph_assert(splitable_nodes.size() == 0);
1002 return fut;
1003 }
1004
20effc67
TL
1005 eagain_ifuture<> merge(context_t c, Ref<DummyChild>&& this_ref) {
1006 return parent_info().ptr->get_child_peers(c, parent_info().position
1007 ).si_then([c, this_ref = std::move(this_ref), this] (auto lr_nodes) mutable {
1008 auto& [lnode, rnode] = lr_nodes;
1009 if (rnode) {
1010 lnode.reset();
1011 Ref<DummyChild> r_dummy(static_cast<DummyChild*>(rnode.get()));
1012 rnode.reset();
1013 pool.untrack_node(r_dummy);
1014 assert(r_dummy->use_count() == 1);
1015 return do_merge(c, std::move(this_ref), std::move(r_dummy), true);
1016 } else {
1017 ceph_assert(lnode);
1018 Ref<DummyChild> l_dummy(static_cast<DummyChild*>(lnode.get()));
1019 pool.untrack_node(this_ref);
1020 assert(this_ref->use_count() == 1);
1021 return do_merge(c, std::move(l_dummy), std::move(this_ref), false);
1022 }
1023 });
1024 }
1025
1026 eagain_ifuture<> fix_key(context_t c, const ghobject_t& new_key) {
1027 const auto& keys = impl->get_keys();
1028 ceph_assert(keys.size() == 1);
1029 assert(impl->is_level_tail() == false);
1030
1031 std::set<ghobject_t> new_keys;
1032 new_keys.insert(new_key);
1033 impl->reset(new_keys, impl->is_level_tail());
1034 Ref<Node> this_ref = this;
1035 return fix_parent_index<true>(c, std::move(this_ref), false);
1036 }
1037
f67539c2
TL
1038 bool match_pos(const search_position_t& pos) const {
1039 ceph_assert(!is_root());
1040 return pos == parent_info().position;
1041 }
1042
1043 static Ref<DummyChild> create(
1044 const std::set<ghobject_t>& keys, bool is_level_tail,
1045 laddr_t addr, DummyChildPool& pool) {
1046 auto ref_impl = std::make_unique<DummyChildImpl>(keys, is_level_tail, addr);
1047 return new DummyChild(ref_impl.get(), std::move(ref_impl), pool);
1048 }
1049
1050 static Ref<DummyChild> create_new(
1051 const std::set<ghobject_t>& keys, bool is_level_tail, DummyChildPool& pool) {
1052 static laddr_t seed = 0;
1053 return create(keys, is_level_tail, seed++, pool);
1054 }
1055
20effc67 1056 static eagain_ifuture<Ref<DummyChild>> create_initial(
f67539c2
TL
1057 context_t c, const std::set<ghobject_t>& keys,
1058 DummyChildPool& pool, RootNodeTracker& root_tracker) {
1059 auto initial = create_new(keys, true, pool);
1060 return c.nm.get_super(c.t, root_tracker
20effc67
TL
1061 ).handle_error_interruptible(
1062 eagain_iertr::pass_further{},
1063 crimson::ct_error::assert_all{"Invalid error during create_initial()"}
1064 ).si_then([c, initial](auto super) {
f67539c2 1065 initial->make_root_new(c, std::move(super));
20effc67 1066 return initial->upgrade_root(c, L_ADDR_MIN).si_then([initial] {
f67539c2
TL
1067 return initial;
1068 });
1069 });
1070 }
1071
1072 protected:
20effc67 1073 eagain_ifuture<> test_clone_non_root(
f67539c2
TL
1074 context_t, Ref<InternalNode> new_parent) const override {
1075 ceph_assert(!is_root());
1076 auto p_pool_clone = pool.pool_clone_in_progress;
1077 ceph_assert(p_pool_clone != nullptr);
1078 auto clone = create(
1079 impl->get_keys(), impl->is_level_tail(), impl->laddr(), *p_pool_clone);
1080 clone->as_child(parent_info().position, new_parent);
20effc67 1081 return eagain_iertr::now();
f67539c2 1082 }
20effc67 1083 eagain_ifuture<Ref<tree_cursor_t>> lookup_smallest(context_t) override {
f67539c2 1084 ceph_abort("impossible path"); }
20effc67 1085 eagain_ifuture<Ref<tree_cursor_t>> lookup_largest(context_t) override {
f67539c2 1086 ceph_abort("impossible path"); }
20effc67 1087 eagain_ifuture<> test_clone_root(context_t, RootNodeTracker&) const override {
f67539c2 1088 ceph_abort("impossible path"); }
20effc67 1089 eagain_ifuture<search_result_t> lower_bound_tracked(
f67539c2
TL
1090 context_t, const key_hobj_t&, MatchHistory&) override {
1091 ceph_abort("impossible path"); }
20effc67
TL
1092 eagain_ifuture<> do_get_tree_stats(context_t, tree_stats_t&) override {
1093 ceph_abort("impossible path"); }
1094 bool is_tracking() const override { return false; }
1095 void track_merge(Ref<Node>, match_stage_t, search_position_t&) override {
f67539c2
TL
1096 ceph_abort("impossible path"); }
1097
1098 private:
1099 DummyChild(DummyChildImpl* impl, DummyChildImpl::URef&& ref, DummyChildPool& pool)
1100 : Node(std::move(ref)), impl{impl}, pool{pool} {
1101 pool.track_node(this);
1102 }
1103
1104 bool can_split() const { return impl->get_keys().size() > 1; }
1105
20effc67
TL
1106 static eagain_ifuture<> do_merge(
1107 context_t c, Ref<DummyChild>&& left, Ref<DummyChild>&& right, bool stole_key) {
1108 assert(right->use_count() == 1);
1109 assert(left->impl->get_keys().size() == 1);
1110 assert(right->impl->get_keys().size() == 1);
1111 bool left_is_tail = right->impl->is_level_tail();
1112 const std::set<ghobject_t>* p_keys;
1113 if (stole_key) {
1114 p_keys = &right->impl->get_keys();
1115 } else {
1116 p_keys = &left->impl->get_keys();
1117 }
1118 left->impl->reset(*p_keys, left_is_tail);
1119 auto left_addr = left->impl->laddr();
1120 return left->parent_info().ptr->apply_children_merge<true>(
1121 c, std::move(left), left_addr, std::move(right), !stole_key);
1122 }
1123
f67539c2
TL
1124 DummyChildImpl* impl;
1125 DummyChildPool& pool;
1126 mutable std::random_device rd;
1127 };
1128
1129 public:
f67539c2
TL
1130 DummyChildPool() = default;
1131 ~DummyChildPool() { reset(); }
1132
20effc67 1133 auto build_tree(const std::set<ghobject_t>& keys) {
f67539c2 1134 reset();
f67539c2 1135 // create tree
20effc67
TL
1136 auto ref_dummy = NodeExtentManager::create_dummy(IS_DUMMY_SYNC);
1137 p_dummy = static_cast<DummyManager*>(ref_dummy.get());
1138 p_btree.emplace(std::move(ref_dummy));
1139 return with_trans_intr(get_context().t, [this, &keys] (auto &tr) {
1140 return DummyChild::create_initial(get_context(), keys, *this, *p_btree->root_tracker
1141 ).si_then([this](auto initial_child) {
1142 // split
1143 splitable_nodes.insert(initial_child);
1144 return trans_intr::repeat([this] ()
1145 -> eagain_ifuture<seastar::stop_iteration> {
1146 if (splitable_nodes.empty()) {
1147 return seastar::make_ready_future<seastar::stop_iteration>(
1148 seastar::stop_iteration::yes);
1149 }
1150 auto index = rd() % splitable_nodes.size();
1151 auto iter = splitable_nodes.begin();
1152 std::advance(iter, index);
1153 Ref<DummyChild> child = *iter;
1154 return child->populate_split(get_context(), splitable_nodes
1155 ).si_then([] {
1156 return seastar::stop_iteration::no;
1157 });
f67539c2 1158 });
20effc67 1159 }).si_then([this] {
f67539c2
TL
1160 //std::ostringstream oss;
1161 //p_btree->dump(t(), oss);
1162 //logger().info("\n{}\n", oss.str());
20effc67
TL
1163 return p_btree->height(t());
1164 }).si_then([](auto height) {
1165 ceph_assert(height == 2);
1166 });
f67539c2
TL
1167 });
1168 }
1169
20effc67
TL
1170 seastar::future<> split_merge(ghobject_t key, search_position_t pos,
1171 const split_expectation_t& expected) {
f67539c2 1172 return seastar::async([this, key, pos, expected] {
f67539c2 1173 DummyChildPool pool_clone;
20effc67
TL
1174 clone_to(pool_clone);
1175
1176 // insert and split
1177 logger().info("\n\nINSERT-SPLIT {} at pos({}):", key_hobj_t(key), pos);
f67539c2 1178 auto node_to_split = pool_clone.get_node_by_pos(pos);
20effc67
TL
1179 with_trans_intr(pool_clone.get_context().t, [&] (auto &t) {
1180 return node_to_split->insert_and_split(
1181 pool_clone.get_context(), key, pool_clone.splitable_nodes);
1182 }).unsafe_get0();
1183 {
1184 std::ostringstream oss;
1185 pool_clone.p_btree->dump(pool_clone.t(), oss);
1186 logger().info("dump new root:\n{}", oss.str());
1187 }
1188 auto &pt = pool_clone.t();
1189 EXPECT_EQ(INTR(pool_clone.p_btree->height, pt).unsafe_get0(), 3);
f67539c2 1190 EXPECT_TRUE(last_split.match(expected));
20effc67
TL
1191 EXPECT_EQ(pool_clone.p_dummy->size(), 3);
1192
1193 // erase and merge
1e59de90 1194 [[maybe_unused]] auto pivot_key = node_to_split->get_pivot_key();
20effc67 1195 logger().info("\n\nERASE-MERGE {}:", node_to_split->get_name());
1e59de90 1196 assert(pivot_key == key_hobj_t(key));
20effc67
TL
1197 with_trans_intr(pool_clone.get_context().t, [&] (auto &t) {
1198 return node_to_split->merge(
1199 pool_clone.get_context(), std::move(node_to_split));
1200 }).unsafe_get0();
1201 auto &pt2 = pool_clone.t();
1202 EXPECT_EQ(INTR(pool_clone.p_btree->height ,pt2).unsafe_get0(), 2);
1203 EXPECT_EQ(pool_clone.p_dummy->size(), 1);
1204 });
1205 }
1206
1207 seastar::future<> fix_index(
1208 ghobject_t new_key, search_position_t pos, bool expect_split) {
1209 return seastar::async([this, new_key, pos, expect_split] {
1210 DummyChildPool pool_clone;
1211 clone_to(pool_clone);
1212
1213 // fix
1214 auto node_to_fix = pool_clone.get_node_by_pos(pos);
1215 auto old_key = node_to_fix->get_pivot_key().to_ghobj();
1216 logger().info("\n\nFIX pos({}) from {} to {}, expect_split={}:",
1217 pos, node_to_fix->get_name(), key_hobj_t(new_key), expect_split);
1218 with_trans_intr(pool_clone.get_context().t, [&] (auto &t) {
1219 return node_to_fix->fix_key(pool_clone.get_context(), new_key);
1220 }).unsafe_get0();
1221 if (expect_split) {
1222 std::ostringstream oss;
1223 pool_clone.p_btree->dump(pool_clone.t(), oss);
1224 logger().info("dump new root:\n{}", oss.str());
1225 auto &pt = pool_clone.t();
1226 EXPECT_EQ(INTR(pool_clone.p_btree->height, pt).unsafe_get0(), 3);
1227 EXPECT_EQ(pool_clone.p_dummy->size(), 3);
1228 } else {
1229 auto &pt = pool_clone.t();
1230 EXPECT_EQ(INTR(pool_clone.p_btree->height, pt).unsafe_get0(), 2);
1231 EXPECT_EQ(pool_clone.p_dummy->size(), 1);
1232 }
1233
1234 // fix back
1235 logger().info("\n\nFIX pos({}) from {} back to {}:",
1236 pos, node_to_fix->get_name(), key_hobj_t(old_key));
1237 with_trans_intr(pool_clone.get_context().t, [&] (auto &t) {
1238 return node_to_fix->fix_key(pool_clone.get_context(), old_key);
1239 }).unsafe_get0();
1240 auto &pt = pool_clone.t();
1241 EXPECT_EQ(INTR(pool_clone.p_btree->height, pt).unsafe_get0(), 2);
1242 EXPECT_EQ(pool_clone.p_dummy->size(), 1);
f67539c2
TL
1243 });
1244 }
1245
1246 private:
20effc67
TL
1247 void clone_to(DummyChildPool& pool_clone) {
1248 pool_clone_in_progress = &pool_clone;
1249 auto ref_dummy = NodeExtentManager::create_dummy(IS_DUMMY_SYNC);
1250 pool_clone.p_dummy = static_cast<DummyManager*>(ref_dummy.get());
1251 pool_clone.p_btree.emplace(std::move(ref_dummy));
1252 auto &pt = pool_clone.t();
1253 [[maybe_unused]] auto &tr = t();
1254 INTR_R(pool_clone.p_btree->test_clone_from,
1255 pt, tr, *p_btree).unsafe_get0();
1256 pool_clone_in_progress = nullptr;
1257 }
1258
f67539c2
TL
1259 void reset() {
1260 ceph_assert(pool_clone_in_progress == nullptr);
1261 if (tracked_children.size()) {
1262 ceph_assert(!p_btree->test_is_clean());
1263 tracked_children.clear();
1264 ceph_assert(p_btree->test_is_clean());
20effc67 1265 p_dummy = nullptr;
f67539c2
TL
1266 p_btree.reset();
1267 } else {
1268 ceph_assert(!p_btree.has_value());
1269 }
1270 splitable_nodes.clear();
1271 }
1272
1273 void track_node(Ref<DummyChild> node) {
1274 ceph_assert(tracked_children.find(node) == tracked_children.end());
1275 tracked_children.insert(node);
1276 }
1277
20effc67
TL
1278 void untrack_node(Ref<DummyChild> node) {
1279 auto ret = tracked_children.erase(node);
1280 ceph_assert(ret == 1);
1281 }
1282
f67539c2
TL
1283 Ref<DummyChild> get_node_by_pos(const search_position_t& pos) const {
1284 auto iter = std::find_if(
1285 tracked_children.begin(), tracked_children.end(), [&pos](auto& child) {
1286 return child->match_pos(pos);
1287 });
1288 ceph_assert(iter != tracked_children.end());
1289 return *iter;
1290 }
1291
1292 context_t get_context() {
20effc67
TL
1293 ceph_assert(p_dummy != nullptr);
1294 return {*p_dummy, vb, t()};
f67539c2
TL
1295 }
1296
1297 Transaction& t() const { return *ref_t; }
1298
1299 std::set<Ref<DummyChild>> tracked_children;
20effc67
TL
1300 std::optional<UnboundedBtree> p_btree;
1301 DummyManager* p_dummy = nullptr;
1302 ValueBuilderImpl<UnboundedValue> vb;
1303 TransactionRef ref_t = make_test_transaction();
f67539c2
TL
1304
1305 std::random_device rd;
1306 std::set<Ref<DummyChild>> splitable_nodes;
1307
1308 DummyChildPool* pool_clone_in_progress = nullptr;
1309};
1310
1311}
1312
20effc67 1313TEST_F(c_dummy_test_t, 5_split_merge_internal_node)
f67539c2 1314{
20effc67 1315 run_async([] {
f67539c2
TL
1316 DummyChildPool pool;
1317 {
1318 logger().info("\n---------------------------------------------"
1319 "\nbefore internal node insert:\n");
1320 auto padding = std::string(250, '_');
1321 auto keys = build_key_set({2, 6}, {2, 5}, {2, 5}, padding, true);
1322 keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 2, 2));
1323 keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 3, 3));
1324 keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 4, 4));
1325 keys.erase(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 2, 2));
1326 keys.erase(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 3, 3));
1327 keys.erase(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 4, 4));
1328 auto padding_s = std::string(257, '_');
1329 keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 2, 2));
1330 keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 3, 3));
1331 keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 4, 4));
20effc67 1332 auto padding_e = std::string(247, '_');
f67539c2
TL
1333 keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding_e, 2, 2));
1334 keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding_e, 3, 3));
1335 keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding_e, 4, 4));
1336 pool.build_tree(keys).unsafe_get0();
1337
1338 logger().info("\n---------------------------------------------"
1339 "\nsplit at stage 2; insert to right front at stage 0, 1, 2, 1, 0\n");
20effc67
TL
1340 pool.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4" + padding, 5, 5), {2, {0, {0}}},
1341 {2u, 0u, false, InsertType::BEGIN}).get();
1342 pool.split_merge(make_ghobj(3, 3, 3, "ns5", "oid5", 3, 3), {2, {0, {0}}},
1343 {2u, 1u, false, InsertType::BEGIN}).get();
1344 pool.split_merge(make_ghobj(3, 4, 4, "ns3", "oid3", 3, 3), {2, {0, {0}}},
1345 {2u, 2u, false, InsertType::BEGIN}).get();
1346 pool.split_merge(make_ghobj(4, 4, 4, "ns1", "oid1", 3, 3), {2, {0, {0}}},
1347 {2u, 1u, false, InsertType::BEGIN}).get();
1348 pool.split_merge(make_ghobj(4, 4, 4, "ns2", "oid2" + padding, 1, 1), {2, {0, {0}}},
1349 {2u, 0u, false, InsertType::BEGIN}).get();
f67539c2
TL
1350
1351 logger().info("\n---------------------------------------------"
1352 "\nsplit at stage 2; insert to right middle at stage 0, 1, 2, 1, 0\n");
20effc67
TL
1353 pool.split_merge(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 5, 5), {3, {0, {0}}},
1354 {2u, 0u, false, InsertType::MID}).get();
1355 pool.split_merge(make_ghobj(4, 4, 4, "ns5", "oid5", 3, 3), {3, {0, {0}}},
1356 {2u, 1u, false, InsertType::MID}).get();
1357 pool.split_merge(make_ghobj(4, 4, 5, "ns3", "oid3", 3, 3), {3, {0, {0}}},
1358 {2u, 2u, false, InsertType::MID}).get();
1359 pool.split_merge(make_ghobj(5, 5, 5, "ns1", "oid1", 3, 3), {3, {0, {0}}},
1360 {2u, 1u, false, InsertType::MID}).get();
1361 pool.split_merge(make_ghobj(5, 5, 5, "ns2", "oid2" + padding, 1, 1), {3, {0, {0}}},
1362 {2u, 0u, false, InsertType::MID}).get();
f67539c2
TL
1363
1364 logger().info("\n---------------------------------------------"
1365 "\nsplit at stage 2; insert to right back at stage 0, 1, 2\n");
20effc67 1366 pool.split_merge(make_ghobj(5, 5, 5, "ns4", "oid4" + padding_e, 5, 5), search_position_t::end() ,
f67539c2 1367 {2u, 0u, false, InsertType::LAST}).get();
20effc67
TL
1368 pool.split_merge(make_ghobj(5, 5, 5, "ns5", "oid5", 3, 3), search_position_t::end(),
1369 {2u, 1u, false, InsertType::LAST}).get();
1370 pool.split_merge(make_ghobj(6, 6, 6, "ns3", "oid3", 3, 3), search_position_t::end(),
1371 {2u, 2u, false, InsertType::LAST}).get();
f67539c2
TL
1372
1373 logger().info("\n---------------------------------------------"
1374 "\nsplit at stage 0; insert to left front at stage 2, 1, 0\n");
20effc67
TL
1375 pool.split_merge(make_ghobj(1, 1, 1, "ns3", "oid3", 3, 3), {0, {0, {0}}},
1376 {0u, 2u, true, InsertType::BEGIN}).get();
1377 pool.split_merge(make_ghobj(2, 2, 2, "ns1", "oid1", 3, 3), {0, {0, {0}}},
1378 {0u, 1u, true, InsertType::BEGIN}).get();
1379 pool.split_merge(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 1, 1), {0, {0, {0}}},
1380 {0u, 0u, true, InsertType::BEGIN}).get();
f67539c2
TL
1381
1382 logger().info("\n---------------------------------------------"
1383 "\nsplit at stage 0; insert to left middle at stage 0, 1, 2, 1, 0\n");
20effc67
TL
1384 pool.split_merge(make_ghobj(2, 2, 2, "ns4", "oid4" + padding, 5, 5), {1, {0, {0}}},
1385 {0u, 0u, true, InsertType::MID}).get();
1386 pool.split_merge(make_ghobj(2, 2, 2, "ns5", "oid5", 3, 3), {1, {0, {0}}},
1387 {0u, 1u, true, InsertType::MID}).get();
1388 pool.split_merge(make_ghobj(2, 2, 3, "ns3", "oid3" + std::string(80, '_'), 3, 3), {1, {0, {0}}} ,
f67539c2 1389 {0u, 2u, true, InsertType::MID}).get();
20effc67
TL
1390 pool.split_merge(make_ghobj(3, 3, 3, "ns1", "oid1", 3, 3), {1, {0, {0}}},
1391 {0u, 1u, true, InsertType::MID}).get();
1392 pool.split_merge(make_ghobj(3, 3, 3, "ns2", "oid2" + padding, 1, 1), {1, {0, {0}}},
1393 {0u, 0u, true, InsertType::MID}).get();
f67539c2
TL
1394
1395 logger().info("\n---------------------------------------------"
1396 "\nsplit at stage 0; insert to left back at stage 0\n");
20effc67
TL
1397 pool.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4" + padding, 3, 4), {1, {2, {2}}},
1398 {0u, 0u, true, InsertType::LAST}).get();
f67539c2
TL
1399 }
1400
1401 {
1402 logger().info("\n---------------------------------------------"
1403 "\nbefore internal node insert (1):\n");
1404 auto padding = std::string(244, '_');
1405 auto keys = build_key_set({2, 6}, {2, 5}, {2, 5}, padding, true);
1406 keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 5, 5));
1407 keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 6, 6));
1408 keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 7, 7));
1409 pool.build_tree(keys).unsafe_get0();
1410
1411 logger().info("\n---------------------------------------------"
1412 "\nsplit at stage 2; insert to left back at stage 0, 1, 2, 1\n");
20effc67
TL
1413 pool.split_merge(make_ghobj(3, 3, 3, "ns4", "oid4" + padding, 5, 5), {2, {0, {0}}},
1414 {2u, 0u, true, InsertType::LAST}).get();
1415 pool.split_merge(make_ghobj(3, 3, 3, "ns5", "oid5", 3, 3), {2, {0, {0}}},
1416 {2u, 1u, true, InsertType::LAST}).get();
1417 pool.split_merge(make_ghobj(3, 4, 4, "n", "o", 3, 3), {2, {0, {0}}},
1418 {2u, 2u, true, InsertType::LAST}).get();
1419 pool.split_merge(make_ghobj(4, 4, 4, "n", "o", 3, 3), {2, {0, {0}}},
1420 {2u, 1u, true, InsertType::LAST}).get();
f67539c2
TL
1421
1422 logger().info("\n---------------------------------------------"
1423 "\nsplit at stage 2; insert to left middle at stage 2\n");
20effc67
TL
1424 pool.split_merge(make_ghobj(2, 3, 3, "n", "o", 3, 3), {1, {0, {0}}},
1425 {2u, 2u, true, InsertType::MID}).get();
f67539c2
TL
1426 }
1427
1428 {
1429 logger().info("\n---------------------------------------------"
1430 "\nbefore internal node insert (2):\n");
1431 auto padding = std::string(243, '_');
1432 auto keys = build_key_set({2, 6}, {2, 5}, {2, 5}, padding, true);
1433 keys.insert(make_ghobj(4, 4, 4, "n", "o", 3, 3));
1434 keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 5, 5));
1435 keys.insert(make_ghobj(5, 5, 5, "ns4", "oid4" + padding, 6, 6));
1436 pool.build_tree(keys).unsafe_get0();
1437
1438 logger().info("\n---------------------------------------------"
1439 "\nsplit at stage 2; insert to left back at stage (0, 1, 2, 1,) 0\n");
20effc67
TL
1440 pool.split_merge(make_ghobj(4, 4, 4, "n", "o", 2, 2), {2, {0, {0}}},
1441 {2u, 0u, true, InsertType::LAST}).get();
f67539c2
TL
1442 }
1443
1444 {
1445 logger().info("\n---------------------------------------------"
1446 "\nbefore internal node insert (3):\n");
20effc67 1447 auto padding = std::string(419, '_');
f67539c2
TL
1448 auto keys = build_key_set({2, 5}, {2, 5}, {2, 5}, padding, true);
1449 keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 2, 2));
1450 keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 3, 3));
1451 keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 4, 4));
1452 pool.build_tree(keys).unsafe_get0();
1453
1454 logger().info("\n---------------------------------------------"
1455 "\nsplit at stage 1; insert to right front at stage 0, 1, 0\n");
20effc67
TL
1456 pool.split_merge(make_ghobj(3, 3, 3, "ns2", "oid2" + padding, 5, 5), {1, {1, {0}}},
1457 {1u, 0u, false, InsertType::BEGIN}).get();
1458 pool.split_merge(make_ghobj(3, 3, 3, "ns2", "oid3", 3, 3), {1, {1, {0}}},
1459 {1u, 1u, false, InsertType::BEGIN}).get();
1460 pool.split_merge(make_ghobj(3, 3, 3, "ns3", "oid3" + padding, 1, 1), {1, {1, {0}}},
1461 {1u, 0u, false, InsertType::BEGIN}).get();
f67539c2
TL
1462 }
1463
1464 {
1465 logger().info("\n---------------------------------------------"
1466 "\nbefore internal node insert (4):\n");
1467 auto padding = std::string(361, '_');
1468 auto keys = build_key_set({2, 5}, {2, 5}, {2, 5}, padding, true);
1469 keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 2, 2));
1470 keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 3, 3));
1471 keys.erase(make_ghobj(2, 2, 2, "ns2", "oid2" + padding, 4, 4));
20effc67 1472 auto padding_s = std::string(386, '_');
f67539c2
TL
1473 keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 2, 2));
1474 keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 3, 3));
1475 keys.insert(make_ghobj(2, 2, 2, "ns2", "oid2" + padding_s, 4, 4));
1476 pool.build_tree(keys).unsafe_get0();
1477
1478 logger().info("\n---------------------------------------------"
1479 "\nsplit at stage 1; insert to left back at stage 0, 1\n");
20effc67
TL
1480 pool.split_merge(make_ghobj(3, 3, 3, "ns2", "oid2" + padding, 5, 5), {1, {1, {0}}},
1481 {1u, 0u, true, InsertType::LAST}).get();
1482 pool.split_merge(make_ghobj(3, 3, 3, "ns2", "oid3", 3, 3), {1, {1, {0}}},
1483 {1u, 1u, true, InsertType::LAST}).get();
1484
1485 logger().info("\n---------------------------------------------"
1486 "\nfix end index from stage 0 to 0, 1, 2\n");
1487 auto padding1 = std::string(400, '_');
1488 pool.fix_index(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 5, 5),
1489 {2, {2, {2}}}, false).get();
1490 pool.fix_index(make_ghobj(4, 4, 4, "ns5", "oid5" + padding1, 3, 3),
1491 {2, {2, {2}}}, true).get();
1492 pool.fix_index(make_ghobj(5, 5, 5, "ns3", "oid3" + padding1, 3, 3),
1493 {2, {2, {2}}}, true).get();
f67539c2
TL
1494 }
1495
1496 {
1497 logger().info("\n---------------------------------------------"
1498 "\nbefore internal node insert (5):\n");
1499 auto padding = std::string(412, '_');
1500 auto keys = build_key_set({2, 5}, {2, 5}, {2, 5}, padding);
1501 keys.insert(make_ghobj(3, 3, 3, "ns2", "oid3", 3, 3));
1502 keys.insert(make_ghobj(4, 4, 4, "ns3", "oid3" + padding, 5, 5));
1503 keys.insert(make_ghobj(9, 9, 9, "ns~last", "oid~last", 9, 9));
1504 keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 2, 2));
1505 keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 3, 3));
1506 keys.erase(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 4, 4));
1507 pool.build_tree(keys).unsafe_get0();
1508
1509 logger().info("\n---------------------------------------------"
1510 "\nsplit at stage 1; insert to left back at stage (0, 1,) 0\n");
20effc67
TL
1511 pool.split_merge(make_ghobj(3, 3, 3, "ns2", "oid3", 2, 2), {1, {1, {0}}},
1512 {1u, 0u, true, InsertType::LAST}).get();
f67539c2
TL
1513 }
1514
1515 {
1516 logger().info("\n---------------------------------------------"
1517 "\nbefore internal node insert (6):\n");
1518 auto padding = std::string(328, '_');
1519 auto keys = build_key_set({2, 5}, {2, 5}, {2, 5}, padding);
20effc67 1520 keys.insert(make_ghobj(5, 5, 5, "ns3", "oid3" + std::string(270, '_'), 3, 3));
f67539c2
TL
1521 keys.insert(make_ghobj(9, 9, 9, "ns~last", "oid~last", 9, 9));
1522 pool.build_tree(keys).unsafe_get0();
1523
1524 logger().info("\n---------------------------------------------"
1525 "\nsplit at stage 0; insert to right front at stage 0\n");
20effc67
TL
1526 pool.split_merge(make_ghobj(3, 3, 3, "ns3", "oid3" + padding, 2, 3), {1, {1, {1}}},
1527 {0u, 0u, false, InsertType::BEGIN}).get();
1528
1529 logger().info("\n---------------------------------------------"
1530 "\nfix end index from stage 2 to 0, 1, 2\n");
1531 auto padding1 = std::string(400, '_');
1532 pool.fix_index(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 5, 5),
1533 {3, {0, {0}}}, false).get();
1534 pool.fix_index(make_ghobj(4, 4, 4, "ns5", "oid5" + padding1, 3, 3),
1535 {3, {0, {0}}}, true).get();
1536 pool.fix_index(make_ghobj(5, 5, 5, "ns4", "oid4" + padding1, 3, 3),
1537 {3, {0, {0}}}, true).get();
1538 }
1539
1540 {
1541 logger().info("\n---------------------------------------------"
1542 "\nbefore internal node insert (7):\n");
1543 auto padding = std::string(323, '_');
1544 auto keys = build_key_set({2, 5}, {2, 5}, {2, 5}, padding);
1545 keys.insert(make_ghobj(4, 4, 4, "ns5", "oid5" + padding, 3, 3));
1546 keys.insert(make_ghobj(9, 9, 9, "ns~last", "oid~last", 9, 9));
1547 pool.build_tree(keys).unsafe_get0();
1548
1549 logger().info("\n---------------------------------------------"
1550 "\nfix end index from stage 1 to 0, 1, 2\n");
1551 auto padding1 = std::string(400, '_');
1552 pool.fix_index(make_ghobj(4, 4, 4, "ns4", "oid4" + padding, 5, 5),
1553 {2, {3, {0}}}, false).get();
1554 pool.fix_index(make_ghobj(4, 4, 4, "ns6", "oid6" + padding1, 3, 3),
1555 {2, {3, {0}}}, true).get();
1556 pool.fix_index(make_ghobj(5, 5, 5, "ns3", "oid3" + padding1, 3, 3),
1557 {2, {3, {0}}}, true).get();
f67539c2
TL
1558 }
1559
1560 // Impossible to split at {0, 0, 0}
1561 // Impossible to split at [END, END, END]
1562 });
1563}
1564
1565struct d_seastore_tm_test_t :
1566 public seastar_test_suite_t, TMTestState {
1567 seastar::future<> set_up_fut() override final {
1568 return tm_setup();
1569 }
1570 seastar::future<> tear_down_fut() override final {
1571 return tm_teardown();
1572 }
1573};
1574
aee94f69 1575TEST_P(d_seastore_tm_test_t, 6_random_tree_insert_erase)
f67539c2
TL
1576{
1577 run_async([this] {
1578 constexpr bool TEST_SEASTORE = true;
1579 constexpr bool TRACK_CURSORS = true;
20effc67
TL
1580 auto kvs = KVPool<test_item_t>::create_raw_range(
1581 {8, 11, 64, 256, 301, 320},
1582 {8, 11, 64, 256, 301, 320},
1583 {8, 16, 128, 512, 576, 640},
1584 {0, 16}, {0, 10}, {0, 4});
1585 auto moved_nm = (TEST_SEASTORE ? NodeExtentManager::create_seastore(*tm)
1586 : NodeExtentManager::create_dummy(IS_DUMMY_SYNC));
1587 auto p_nm = moved_nm.get();
1588 auto tree = std::make_unique<TreeBuilder<TRACK_CURSORS, BoundedValue>>(
1589 kvs, std::move(moved_nm));
1590 {
1591 auto t = create_mutate_transaction();
1592 INTR(tree->bootstrap, *t).unsafe_get();
1593 submit_transaction(std::move(t));
20effc67
TL
1594 }
1595
1596 // test insert
f67539c2 1597 {
20effc67
TL
1598 auto t = create_mutate_transaction();
1599 INTR(tree->insert, *t).unsafe_get();
1600 submit_transaction(std::move(t));
f67539c2
TL
1601 }
1602 {
20effc67
TL
1603 auto t = create_read_transaction();
1604 INTR(tree->get_stats, *t).unsafe_get();
1605 }
1606 if constexpr (TEST_SEASTORE) {
20effc67
TL
1607 restart();
1608 tree->reload(NodeExtentManager::create_seastore(*tm));
20effc67
TL
1609 }
1610 {
1611 // Note: create_weak_transaction() can also work, but too slow.
1612 auto t = create_read_transaction();
1613 INTR(tree->validate, *t).unsafe_get();
1614 }
1615
1616 // test erase 3/4
1617 {
1618 auto t = create_mutate_transaction();
1619 auto size = kvs.size() / 4 * 3;
1620 INTR_R(tree->erase, *t, size).unsafe_get();
1621 submit_transaction(std::move(t));
f67539c2
TL
1622 }
1623 {
20effc67
TL
1624 auto t = create_read_transaction();
1625 INTR(tree->get_stats, *t).unsafe_get();
f67539c2
TL
1626 }
1627 if constexpr (TEST_SEASTORE) {
f67539c2
TL
1628 restart();
1629 tree->reload(NodeExtentManager::create_seastore(*tm));
f67539c2
TL
1630 }
1631 {
20effc67
TL
1632 auto t = create_read_transaction();
1633 INTR(tree->validate, *t).unsafe_get();
f67539c2 1634 }
20effc67
TL
1635
1636 // test erase remaining
1637 {
1638 auto t = create_mutate_transaction();
1639 auto size = kvs.size();
1640 INTR_R(tree->erase, *t, size).unsafe_get();
1641 submit_transaction(std::move(t));
20effc67
TL
1642 }
1643 {
1644 auto t = create_read_transaction();
1645 INTR(tree->get_stats, *t).unsafe_get();
1646 }
1647 if constexpr (TEST_SEASTORE) {
20effc67
TL
1648 restart();
1649 tree->reload(NodeExtentManager::create_seastore(*tm));
20effc67
TL
1650 }
1651 {
1652 auto t = create_read_transaction();
1653 INTR(tree->validate, *t).unsafe_get();
1654 EXPECT_EQ(INTR(tree->height, *t).unsafe_get0(), 1);
1655 }
1656
1657 if constexpr (!TEST_SEASTORE) {
1658 auto p_dummy = static_cast<DummyManager*>(p_nm);
1659 EXPECT_EQ(p_dummy->size(), 1);
1660 }
1661 tree.reset();
1662 });
1663}
1664
aee94f69 1665TEST_P(d_seastore_tm_test_t, 7_tree_insert_erase_eagain)
20effc67
TL
1666{
1667 run_async([this] {
1668 constexpr double EAGAIN_PROBABILITY = 0.1;
1669 constexpr bool TRACK_CURSORS = false;
1670 auto kvs = KVPool<test_item_t>::create_raw_range(
1671 {8, 11, 64, 128, 255, 256},
1672 {8, 13, 64, 512, 2035, 2048},
1673 {8, 16, 128, 576, 992, 1200},
1674 {0, 8}, {0, 10}, {0, 4});
1675 auto moved_nm = NodeExtentManager::create_seastore(
1676 *tm, L_ADDR_MIN, EAGAIN_PROBABILITY);
1677 auto p_nm = static_cast<SeastoreNodeExtentManager<true>*>(moved_nm.get());
1678 auto tree = std::make_unique<TreeBuilder<TRACK_CURSORS, ExtendedValue>>(
1679 kvs, std::move(moved_nm));
1680 unsigned num_ops = 0;
1681 unsigned num_ops_eagain = 0;
1682
1683 // bootstrap
1684 ++num_ops;
1685 repeat_eagain([this, &tree, &num_ops_eagain] {
1686 ++num_ops_eagain;
1687 return seastar::do_with(
1688 create_mutate_transaction(),
1689 [this, &tree](auto &t) {
1690 return INTR(tree->bootstrap, *t
1691 ).safe_then([this, &t] {
1692 return submit_transaction_fut(*t);
1693 });
1694 });
1695 }).unsafe_get0();
1e59de90 1696 epm->run_background_work_until_halt().get0();
20effc67
TL
1697
1698 // insert
1699 logger().warn("start inserting {} kvs ...", kvs.size());
1700 {
1701 auto iter = kvs.random_begin();
1702 while (iter != kvs.random_end()) {
1703 ++num_ops;
1704 repeat_eagain([this, &tree, &num_ops_eagain, &iter] {
1705 ++num_ops_eagain;
1706 return seastar::do_with(
1707 create_mutate_transaction(),
1708 [this, &tree, &iter](auto &t) {
1709 return INTR_R(tree->insert_one, *t, iter
1710 ).safe_then([this, &t](auto cursor) {
1711 cursor.invalidate();
1712 return submit_transaction_fut(*t);
1713 });
1714 });
1715 }).unsafe_get0();
1e59de90 1716 epm->run_background_work_until_halt().get0();
20effc67
TL
1717 ++iter;
1718 }
1719 }
1720
1721 {
1722 p_nm->set_generate_eagain(false);
1723 auto t = create_read_transaction();
1724 INTR(tree->get_stats, *t).unsafe_get0();
1725 p_nm->set_generate_eagain(true);
1726 }
1727
1728 // lookup
1729 logger().warn("start lookup {} kvs ...", kvs.size());
1730 {
1731 auto iter = kvs.begin();
1732 while (iter != kvs.end()) {
1733 ++num_ops;
1734 repeat_eagain([this, &tree, &num_ops_eagain, &iter] {
1735 ++num_ops_eagain;
1736 auto t = create_read_transaction();
1737 return INTR_R(tree->validate_one, *t, iter
1738 ).safe_then([t=std::move(t)]{});
1739 }).unsafe_get0();
1740 ++iter;
1741 }
1742 }
1743
1744 // erase
1745 logger().warn("start erase {} kvs ...", kvs.size());
1746 {
1747 kvs.shuffle();
1748 auto iter = kvs.random_begin();
1749 while (iter != kvs.random_end()) {
1750 ++num_ops;
1751 repeat_eagain([this, &tree, &num_ops_eagain, &iter] {
1752 ++num_ops_eagain;
1753 return seastar::do_with(
1754 create_mutate_transaction(),
1755 [this, &tree, &iter](auto &t) {
1756 return INTR_R(tree->erase_one, *t, iter
1757 ).safe_then([this, &t] () mutable {
1758 return submit_transaction_fut(*t);
1759 });
1760 });
1761 }).unsafe_get0();
1e59de90 1762 epm->run_background_work_until_halt().get0();
20effc67
TL
1763 ++iter;
1764 }
1765 kvs.erase_from_random(kvs.random_begin(), kvs.random_end());
1766 }
1767
1768 {
1769 p_nm->set_generate_eagain(false);
1770 auto t = create_read_transaction();
1771 INTR(tree->get_stats, *t).unsafe_get0();
1772 INTR(tree->validate, *t).unsafe_get0();
1773 EXPECT_EQ(INTR(tree->height,*t).unsafe_get0(), 1);
1774 }
1775
1776 // we can adjust EAGAIN_PROBABILITY to get a proper eagain_rate
1777 double eagain_rate = num_ops_eagain;
1778 eagain_rate /= num_ops;
1779 logger().info("eagain rate: {}", eagain_rate);
1780
f67539c2
TL
1781 tree.reset();
1782 });
1783}
aee94f69
TL
1784
1785INSTANTIATE_TEST_SUITE_P(
1786 d_seastore_tm_test,
1787 d_seastore_tm_test_t,
1788 ::testing::Values (
1789 "segmented",
1790 "circularbounded"
1791 )
1792);