1 // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "include/types.h"
5 #include "cls/rgw/cls_rgw_client.h"
6 #include "cls/rgw/cls_rgw_ops.h"
8 #include "gtest/gtest.h"
9 #include "test/librados/test_cxx.h"
10 #include "global/global_context.h"
11 #include "common/ceph_context.h"
20 using namespace librados
;
22 // creates a temporary pool and initializes an IoCtx shared by all tests
23 class cls_rgw
: public ::testing::Test
{
24 static librados::Rados rados
;
25 static std::string pool_name
;
27 static librados::IoCtx ioctx
;
29 static void SetUpTestCase() {
30 pool_name
= get_temp_pool_name();
32 ASSERT_EQ("", create_one_pool_pp(pool_name
, rados
));
33 ASSERT_EQ(0, rados
.ioctx_create(pool_name
.c_str(), ioctx
));
35 static void TearDownTestCase() {
38 ASSERT_EQ(0, destroy_one_pool_pp(pool_name
, rados
));
41 librados::Rados
cls_rgw::rados
;
42 std::string
cls_rgw::pool_name
;
43 librados::IoCtx
cls_rgw::ioctx
;
46 string
str_int(string s
, int i
)
49 snprintf(buf
, sizeof(buf
), "-%d", i
);
55 void test_stats(librados::IoCtx
& ioctx
, string
& oid
, RGWObjCategory category
, uint64_t num_entries
, uint64_t total_size
)
57 map
<int, struct rgw_cls_list_ret
> results
;
58 map
<int, string
> oids
;
60 ASSERT_EQ(0, CLSRGWIssueGetDirHeader(ioctx
, oids
, results
, 8)());
64 map
<int, struct rgw_cls_list_ret
>::iterator iter
= results
.begin();
65 for (; iter
!= results
.end(); ++iter
) {
66 entries
+= (iter
->second
).dir
.header
.stats
[category
].num_entries
;
67 size
+= (iter
->second
).dir
.header
.stats
[category
].total_size
;
69 ASSERT_EQ(total_size
, size
);
70 ASSERT_EQ(num_entries
, entries
);
73 void index_prepare(librados::IoCtx
& ioctx
, string
& oid
, RGWModifyOp index_op
,
74 string
& tag
, const cls_rgw_obj_key
& key
, string
& loc
,
75 uint16_t bi_flags
= 0, bool log_op
= true)
77 ObjectWriteOperation op
;
78 rgw_zone_set zones_trace
;
79 cls_rgw_bucket_prepare_op(op
, index_op
, tag
, key
, loc
, log_op
, bi_flags
, zones_trace
);
80 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
83 void index_complete(librados::IoCtx
& ioctx
, string
& oid
, RGWModifyOp index_op
,
84 string
& tag
, int epoch
, const cls_rgw_obj_key
& key
,
85 rgw_bucket_dir_entry_meta
& meta
, uint16_t bi_flags
= 0,
88 ObjectWriteOperation op
;
89 rgw_bucket_entry_ver ver
;
90 ver
.pool
= ioctx
.get_id();
92 meta
.accounted_size
= meta
.size
;
93 cls_rgw_bucket_complete_op(op
, index_op
, tag
, ver
, key
, meta
, nullptr, log_op
, bi_flags
, nullptr);
94 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
95 if (!key
.instance
.empty()) {
98 rgw_zone_set zone_set
;
99 ASSERT_EQ(0, cls_rgw_bucket_link_olh(ioctx
, oid
, key
, olh_tag
,
100 false, tag
, &meta
, epoch
,
101 ceph::real_time
{}, true, true, zone_set
));
105 TEST_F(cls_rgw
, index_basic
)
107 string bucket_oid
= str_int("bucket", 0);
109 ObjectWriteOperation op
;
110 cls_rgw_bucket_init_index(op
);
111 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
115 uint64_t obj_size
= 1024;
118 for (int i
= 0; i
< NUM_OBJS
; i
++) {
119 cls_rgw_obj_key obj
= str_int("obj", i
);
120 string tag
= str_int("tag", i
);
121 string loc
= str_int("loc", i
);
123 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
125 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, i
, obj_size
* i
);
127 rgw_bucket_dir_entry_meta meta
;
128 meta
.category
= RGWObjCategory::None
;
129 meta
.size
= obj_size
;
130 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, epoch
, obj
, meta
);
133 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, NUM_OBJS
,
134 obj_size
* NUM_OBJS
);
137 TEST_F(cls_rgw
, index_multiple_obj_writers
)
139 string bucket_oid
= str_int("bucket", 1);
141 ObjectWriteOperation op
;
142 cls_rgw_bucket_init_index(op
);
143 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
145 uint64_t obj_size
= 1024;
147 cls_rgw_obj_key obj
= str_int("obj", 0);
148 string loc
= str_int("loc", 0);
149 /* multi prepare on a single object */
150 for (int i
= 0; i
< NUM_OBJS
; i
++) {
151 string tag
= str_int("tag", i
);
153 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
155 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, 0, 0);
158 for (int i
= NUM_OBJS
; i
> 0; i
--) {
159 string tag
= str_int("tag", i
- 1);
161 rgw_bucket_dir_entry_meta meta
;
162 meta
.category
= RGWObjCategory::None
;
163 meta
.size
= obj_size
* i
;
165 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, i
, obj
, meta
);
167 /* verify that object size doesn't change, as we went back with epoch */
168 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, 1,
169 obj_size
* NUM_OBJS
);
173 TEST_F(cls_rgw
, index_remove_object
)
175 string bucket_oid
= str_int("bucket", 2);
177 ObjectWriteOperation op
;
178 cls_rgw_bucket_init_index(op
);
179 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
181 uint64_t obj_size
= 1024;
182 uint64_t total_size
= 0;
186 /* prepare multiple objects */
187 for (int i
= 0; i
< NUM_OBJS
; i
++) {
188 cls_rgw_obj_key obj
= str_int("obj", i
);
189 string tag
= str_int("tag", i
);
190 string loc
= str_int("loc", i
);
192 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
194 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, i
, total_size
);
196 rgw_bucket_dir_entry_meta meta
;
197 meta
.category
= RGWObjCategory::None
;
198 meta
.size
= i
* obj_size
;
199 total_size
+= i
* obj_size
;
201 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, ++epoch
, obj
, meta
);
203 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, i
+ 1, total_size
);
206 int i
= NUM_OBJS
/ 2;
207 string tag_remove
= "tag-rm";
208 string tag_modify
= "tag-mod";
209 cls_rgw_obj_key obj
= str_int("obj", i
);
210 string loc
= str_int("loc", i
);
212 /* prepare both removal and modification on the same object */
213 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_remove
, obj
, loc
);
214 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag_modify
, obj
, loc
);
216 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, NUM_OBJS
, total_size
);
218 rgw_bucket_dir_entry_meta meta
;
220 /* complete object removal */
221 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_remove
, ++epoch
, obj
, meta
);
223 /* verify stats correct */
224 total_size
-= i
* obj_size
;
225 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, NUM_OBJS
- 1, total_size
);
228 meta
.category
= RGWObjCategory::None
;
230 /* complete object modification */
231 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag_modify
, ++epoch
, obj
, meta
);
233 /* verify stats correct */
234 total_size
+= meta
.size
;
235 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, NUM_OBJS
, total_size
);
238 /* prepare both removal and modification on the same object, this time we'll
239 * first complete modification then remove*/
240 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_remove
, obj
, loc
);
241 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_modify
, obj
, loc
);
243 /* complete modification */
244 total_size
-= meta
.size
;
245 meta
.size
= i
* obj_size
* 2;
246 meta
.category
= RGWObjCategory::None
;
248 /* complete object modification */
249 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag_modify
, ++epoch
, obj
, meta
);
251 /* verify stats correct */
252 total_size
+= meta
.size
;
253 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, NUM_OBJS
, total_size
);
255 /* complete object removal */
256 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_remove
, ++epoch
, obj
, meta
);
258 /* verify stats correct */
259 total_size
-= meta
.size
;
260 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, NUM_OBJS
- 1,
264 TEST_F(cls_rgw
, index_suggest
)
266 string bucket_oid
= str_int("suggest", 1);
268 ObjectWriteOperation op
;
269 cls_rgw_bucket_init_index(op
);
270 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
272 uint64_t total_size
= 0;
278 uint64_t obj_size
= 1024;
280 /* create multiple objects */
281 for (int i
= 0; i
< num_objs
; i
++) {
282 cls_rgw_obj_key obj
= str_int("obj", i
);
283 string tag
= str_int("tag", i
);
284 string loc
= str_int("loc", i
);
286 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
288 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, i
, total_size
);
290 rgw_bucket_dir_entry_meta meta
;
291 meta
.category
= RGWObjCategory::None
;
292 meta
.size
= obj_size
;
293 total_size
+= meta
.size
;
295 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, ++epoch
, obj
, meta
);
297 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, i
+ 1, total_size
);
300 /* prepare (without completion) some of the objects */
301 for (int i
= 0; i
< num_objs
; i
+= 2) {
302 cls_rgw_obj_key obj
= str_int("obj", i
);
303 string tag
= str_int("tag-prepare", i
);
304 string loc
= str_int("loc", i
);
306 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
308 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, num_objs
, total_size
);
311 int actual_num_objs
= num_objs
;
312 /* remove half of the objects */
313 for (int i
= num_objs
/ 2; i
< num_objs
; i
++) {
314 cls_rgw_obj_key obj
= str_int("obj", i
);
315 string tag
= str_int("tag-rm", i
);
316 string loc
= str_int("loc", i
);
318 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
320 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, actual_num_objs
, total_size
);
322 rgw_bucket_dir_entry_meta meta
;
323 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag
, ++epoch
, obj
, meta
);
325 total_size
-= obj_size
;
327 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, actual_num_objs
, total_size
);
332 for (int i
= 0; i
< num_objs
; i
+= 2) {
333 cls_rgw_obj_key obj
= str_int("obj", i
);
334 string tag
= str_int("tag-rm", i
);
335 string loc
= str_int("loc", i
);
337 rgw_bucket_dir_entry dirent
;
338 dirent
.key
.name
= obj
.name
;
339 dirent
.locator
= loc
;
340 dirent
.exists
= (i
< num_objs
/ 2); // we removed half the objects
341 dirent
.meta
.size
= 1024;
342 dirent
.meta
.accounted_size
= 1024;
344 char suggest_op
= (i
< num_objs
/ 2 ? CEPH_RGW_UPDATE
: CEPH_RGW_REMOVE
);
345 cls_rgw_encode_suggestion(suggest_op
, dirent
, updates
);
348 map
<int, string
> bucket_objs
;
349 bucket_objs
[0] = bucket_oid
;
350 int r
= CLSRGWIssueSetTagTimeout(ioctx
, bucket_objs
, 8 /* max aio */, 1)();
355 /* suggest changes! */
357 ObjectWriteOperation op
;
358 cls_rgw_suggest_changes(op
, updates
);
359 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
361 /* suggest changes twice! */
363 ObjectWriteOperation op
;
364 cls_rgw_suggest_changes(op
, updates
);
365 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
367 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
, num_objs
/ 2, total_size
);
370 static void list_entries(librados::IoCtx
& ioctx
,
371 const std::string
& oid
,
372 uint32_t num_entries
,
373 std::map
<int, rgw_cls_list_ret
>& results
)
375 std::map
<int, std::string
> oids
= { {0, oid
} };
376 cls_rgw_obj_key start_key
;
378 string empty_delimiter
;
379 ASSERT_EQ(0, CLSRGWIssueBucketList(ioctx
, start_key
, empty_prefix
,
380 empty_delimiter
, num_entries
,
381 true, oids
, results
, 1)());
384 TEST_F(cls_rgw
, index_suggest_complete
)
386 string bucket_oid
= str_int("suggest", 2);
388 ObjectWriteOperation op
;
389 cls_rgw_bucket_init_index(op
);
390 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
393 cls_rgw_obj_key obj
= str_int("obj", 0);
394 string tag
= str_int("tag-prepare", 0);
395 string loc
= str_int("loc", 0);
398 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
400 // list entry before completion
401 rgw_bucket_dir_entry dirent
;
403 std::map
<int, rgw_cls_list_ret
> listing
;
404 list_entries(ioctx
, bucket_oid
, 1, listing
);
405 ASSERT_EQ(1, listing
.size());
406 const auto& entries
= listing
.begin()->second
.dir
.m
;
407 ASSERT_EQ(1, entries
.size());
408 dirent
= entries
.begin()->second
;
409 ASSERT_EQ(obj
, dirent
.key
);
413 rgw_bucket_dir_entry_meta meta
;
414 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, 1, obj
, meta
);
416 // suggest removal of listed entry
419 cls_rgw_encode_suggestion(CEPH_RGW_REMOVE
, dirent
, updates
);
421 ObjectWriteOperation op
;
422 cls_rgw_suggest_changes(op
, updates
);
423 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
425 // list entry again, verify that suggested removal was not applied
427 std::map
<int, rgw_cls_list_ret
> listing
;
428 list_entries(ioctx
, bucket_oid
, 1, listing
);
429 ASSERT_EQ(1, listing
.size());
430 const auto& entries
= listing
.begin()->second
.dir
.m
;
431 ASSERT_EQ(1, entries
.size());
432 EXPECT_TRUE(entries
.begin()->second
.exists
);
437 * This case is used to test whether get_obj_vals will
438 * return all validate utf8 objnames and filter out those
439 * in BI_PREFIX_CHAR private namespace.
441 TEST_F(cls_rgw
, index_list
)
443 string bucket_oid
= str_int("bucket", 4);
445 ObjectWriteOperation op
;
446 cls_rgw_bucket_init_index(op
);
447 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
450 uint64_t obj_size
= 1024;
451 const int num_objs
= 4;
452 const string keys
[num_objs
] = {
453 /* single byte utf8 character */
454 { static_cast<char>(0x41) },
455 /* double byte utf8 character */
456 { static_cast<char>(0xCF), static_cast<char>(0x8F) },
457 /* treble byte utf8 character */
458 { static_cast<char>(0xDF), static_cast<char>(0x8F), static_cast<char>(0x8F) },
459 /* quadruble byte utf8 character */
460 { static_cast<char>(0xF7), static_cast<char>(0x8F), static_cast<char>(0x8F), static_cast<char>(0x8F) },
463 for (int i
= 0; i
< num_objs
; i
++) {
464 string obj
= keys
[i
];
465 string tag
= str_int("tag", i
);
466 string loc
= str_int("loc", i
);
468 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
,
469 0 /* bi_flags */, false /* log_op */);
471 rgw_bucket_dir_entry_meta meta
;
472 meta
.category
= RGWObjCategory::None
;
473 meta
.size
= obj_size
;
474 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, epoch
, obj
, meta
,
475 0 /* bi_flags */, false /* log_op */);
478 map
<string
, bufferlist
> entries
;
479 /* insert 998 omap key starts with BI_PREFIX_CHAR,
480 * so bucket list first time will get one key before 0x80 and one key after */
481 for (int i
= 0; i
< 998; ++i
) {
483 snprintf(buf
, sizeof(buf
), "%c%s%d", 0x80, "1000_", i
);
484 entries
.emplace(string
{buf
}, bufferlist
{});
486 ioctx
.omap_set(bucket_oid
, entries
);
488 test_stats(ioctx
, bucket_oid
, RGWObjCategory::None
,
489 num_objs
, obj_size
* num_objs
);
491 map
<int, string
> oids
= { {0, bucket_oid
} };
492 map
<int, struct rgw_cls_list_ret
> list_results
;
493 cls_rgw_obj_key
start_key("", "");
495 string empty_delimiter
;
496 int r
= CLSRGWIssueBucketList(ioctx
, start_key
,
497 empty_prefix
, empty_delimiter
,
498 1000, true, oids
, list_results
, 1)();
500 ASSERT_EQ(1u, list_results
.size());
502 auto it
= list_results
.begin();
503 auto m
= (it
->second
).dir
.m
;
505 ASSERT_EQ(4u, m
.size());
507 for(auto it2
= m
.cbegin(); it2
!= m
.cend(); it2
++, i
++) {
508 ASSERT_EQ(it2
->first
.compare(keys
[i
]), 0);
514 * This case is used to test when bucket index list that includes a
515 * delimiter can handle the first chunk ending in a delimiter.
517 TEST_F(cls_rgw
, index_list_delimited
)
519 string bucket_oid
= str_int("bucket", 7);
521 ObjectWriteOperation op
;
522 cls_rgw_bucket_init_index(op
);
523 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
526 uint64_t obj_size
= 1024;
527 const int file_num_objs
= 5;
528 const int dir_num_objs
= 1005;
530 std::vector
<std::string
> file_prefixes
=
531 { "a", "c", "e", "g", "i", "k", "m", "o", "q", "s", "u" };
532 std::vector
<std::string
> dir_prefixes
=
533 { "b/", "d/", "f/", "h/", "j/", "l/", "n/", "p/", "r/", "t/" };
535 rgw_bucket_dir_entry_meta meta
;
536 meta
.category
= RGWObjCategory::None
;
537 meta
.size
= obj_size
;
539 // create top-level files
540 for (const auto& p
: file_prefixes
) {
541 for (int i
= 0; i
< file_num_objs
; i
++) {
542 string tag
= str_int("tag", i
);
543 string loc
= str_int("loc", i
);
544 const string obj
= str_int(p
, i
);
546 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
,
547 0 /* bi_flags */, false /* log_op */);
549 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, epoch
, obj
, meta
,
550 0 /* bi_flags */, false /* log_op */);
554 // create large directories
555 for (const auto& p
: dir_prefixes
) {
556 for (int i
= 0; i
< dir_num_objs
; i
++) {
557 string tag
= str_int("tag", i
);
558 string loc
= str_int("loc", i
);
559 const string obj
= p
+ str_int("f", i
);
561 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
,
562 0 /* bi_flags */, false /* log_op */);
564 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, epoch
, obj
, meta
,
565 0 /* bi_flags */, false /* log_op */);
569 map
<int, string
> oids
= { {0, bucket_oid
} };
570 map
<int, struct rgw_cls_list_ret
> list_results
;
571 cls_rgw_obj_key
start_key("", "");
572 const string empty_prefix
;
573 const string delimiter
= "/";
574 int r
= CLSRGWIssueBucketList(ioctx
, start_key
,
575 empty_prefix
, delimiter
,
576 1000, true, oids
, list_results
, 1)();
578 ASSERT_EQ(1u, list_results
.size()) <<
579 "Because we only have one bucket index shard, we should "
580 "only get one list_result.";
582 auto it
= list_results
.begin();
583 auto id_entry_map
= it
->second
.dir
.m
;
584 bool truncated
= it
->second
.is_truncated
;
586 // the cls code will make 4 tries to get 1000 entries; however
587 // because each of the subdirectories is so large, each attempt will
588 // only retrieve the first part of the subdirectory
590 ASSERT_EQ(48u, id_entry_map
.size()) <<
591 "We should get 40 top-level entries and the tops of 8 \"subdirectories\".";
592 ASSERT_EQ(true, truncated
) << "We did not get all entries.";
594 ASSERT_EQ("a-0", id_entry_map
.cbegin()->first
);
595 ASSERT_EQ("p/", id_entry_map
.crbegin()->first
);
597 // now let's get the rest of the entries
599 list_results
.clear();
601 cls_rgw_obj_key
start_key2("p/", "");
602 r
= CLSRGWIssueBucketList(ioctx
, start_key2
,
603 empty_prefix
, delimiter
,
604 1000, true, oids
, list_results
, 1)();
607 it
= list_results
.begin();
608 id_entry_map
= it
->second
.dir
.m
;
609 truncated
= it
->second
.is_truncated
;
611 ASSERT_EQ(17u, id_entry_map
.size()) <<
612 "We should get 15 top-level entries and the tops of 2 \"subdirectories\".";
613 ASSERT_EQ(false, truncated
) << "We now have all entries.";
615 ASSERT_EQ("q-0", id_entry_map
.cbegin()->first
);
616 ASSERT_EQ("u-4", id_entry_map
.crbegin()->first
);
620 TEST_F(cls_rgw
, bi_list
)
622 string bucket_oid
= str_int("bucket", 5);
624 CephContext
*cct
= reinterpret_cast<CephContext
*>(ioctx
.cct());
626 ObjectWriteOperation op
;
627 cls_rgw_bucket_init_index(op
);
628 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
630 const std::string empty_name_filter
;
632 std::list
<rgw_cls_bi_entry
> entries
;
636 int ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, empty_name_filter
, marker
, max
,
637 &entries
, &is_truncated
);
639 ASSERT_EQ(entries
.size(), 0u) <<
640 "The listing of an empty bucket as 0 entries.";
641 ASSERT_EQ(is_truncated
, false) <<
642 "The listing of an empty bucket is not truncated.";
645 uint64_t obj_size
= 1024;
646 const uint64_t num_objs
= 35;
648 for (uint64_t i
= 0; i
< num_objs
; i
++) {
649 string obj
= str_int(i
% 4 ? "obj" : "об'єкт", i
);
650 string tag
= str_int("tag", i
);
651 string loc
= str_int("loc", i
);
652 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
,
653 RGW_BILOG_FLAG_VERSIONED_OP
);
655 rgw_bucket_dir_entry_meta meta
;
656 meta
.category
= RGWObjCategory::None
;
657 meta
.size
= obj_size
;
658 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, epoch
, obj
, meta
,
659 RGW_BILOG_FLAG_VERSIONED_OP
);
662 ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, empty_name_filter
, marker
, num_objs
+ 10,
663 &entries
, &is_truncated
);
666 ASSERT_LT(entries
.size(), num_objs
);
668 ASSERT_EQ(entries
.size(), num_objs
);
671 uint64_t num_entries
= 0;
675 while(is_truncated
) {
676 ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, empty_name_filter
, marker
, max
,
677 &entries
, &is_truncated
);
680 ASSERT_LT(entries
.size(), num_objs
- num_entries
);
682 ASSERT_EQ(entries
.size(), num_objs
- num_entries
);
684 num_entries
+= entries
.size();
685 marker
= entries
.back().idx
;
688 // try with marker as final entry
689 ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, empty_name_filter
, marker
, max
,
690 &entries
, &is_truncated
);
692 ASSERT_EQ(entries
.size(), 0u);
693 ASSERT_EQ(is_truncated
, false);
695 if (cct
->_conf
->osd_max_omap_entries_per_request
< 15) {
700 while(is_truncated
) {
701 ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, empty_name_filter
, marker
, max
,
702 &entries
, &is_truncated
);
705 ASSERT_LT(entries
.size(), num_objs
- num_entries
);
707 ASSERT_EQ(entries
.size(), num_objs
- num_entries
);
709 num_entries
+= entries
.size();
710 marker
= entries
.back().idx
;
713 // try with marker as final entry
714 ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, empty_name_filter
, marker
, max
,
715 &entries
, &is_truncated
);
717 ASSERT_EQ(entries
.size(), 0u);
718 ASSERT_EQ(is_truncated
, false);
721 // test with name filters; pairs contain filter and expected number of elements returned
722 const std::list
<std::pair
<const std::string
,unsigned>> filters_results
=
723 { { str_int("obj", 9), 1 },
724 { str_int("об'єкт", 8), 1 },
725 { str_int("obj", 8), 0 } };
726 for (const auto& filter_result
: filters_results
) {
731 ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, filter_result
.first
, marker
, max
,
732 &entries
, &is_truncated
);
734 ASSERT_EQ(ret
, 0) << "bi list test with name filters should succeed";
735 ASSERT_EQ(entries
.size(), filter_result
.second
) <<
736 "bi list test with filters should return the correct number of results";
737 ASSERT_EQ(is_truncated
, false) <<
738 "bi list test with filters should return correct truncation indicator";
741 // test whether combined segment count is correcgt
742 is_truncated
= false;
746 ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, empty_name_filter
, marker
, num_objs
- 1,
747 &entries
, &is_truncated
);
748 ASSERT_EQ(ret
, 0) << "combined segment count should succeed";
749 ASSERT_EQ(entries
.size(), num_objs
- 1) <<
750 "combined segment count should return the correct number of results";
751 ASSERT_EQ(is_truncated
, true) <<
752 "combined segment count should return correct truncation indicator";
755 marker
= entries
.back().idx
; // advance marker
756 ret
= cls_rgw_bi_list(ioctx
, bucket_oid
, empty_name_filter
, marker
, num_objs
- 1,
757 &entries
, &is_truncated
);
758 ASSERT_EQ(ret
, 0) << "combined segment count should succeed";
759 ASSERT_EQ(entries
.size(), 1) <<
760 "combined segment count should return the correct number of results";
761 ASSERT_EQ(is_truncated
, false) <<
762 "combined segment count should return correct truncation indicator";
765 /* test garbage collection */
766 static void create_obj(cls_rgw_obj
& obj
, int i
, int j
)
769 snprintf(buf
, sizeof(buf
), "-%d.%d", i
, j
);
771 obj
.pool
.append(buf
);
772 obj
.key
.name
= "oid";
773 obj
.key
.name
.append(buf
);
778 static bool cmp_objs(cls_rgw_obj
& obj1
, cls_rgw_obj
& obj2
)
780 return (obj1
.pool
== obj2
.pool
) &&
781 (obj1
.key
== obj2
.key
) &&
782 (obj1
.loc
== obj2
.loc
);
786 TEST_F(cls_rgw
, gc_set
)
790 for (int i
= 0; i
< 10; i
++) {
792 snprintf(buf
, sizeof(buf
), "chain-%d", i
);
794 librados::ObjectWriteOperation op
;
795 cls_rgw_gc_obj_info info
;
797 cls_rgw_obj obj1
, obj2
;
798 create_obj(obj1
, i
, 1);
799 create_obj(obj2
, i
, 2);
800 info
.chain
.objs
.push_back(obj1
);
801 info
.chain
.objs
.push_back(obj2
);
803 op
.create(false); // create object
806 cls_rgw_gc_set_entry(op
, 0, info
);
808 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
812 list
<cls_rgw_gc_obj_info
> entries
;
816 /* list chains, verify truncated */
817 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 8, true, entries
, &truncated
, next_marker
));
818 ASSERT_EQ(8, (int)entries
.size());
819 ASSERT_EQ(1, truncated
);
824 /* list all chains, verify not truncated */
825 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 10, true, entries
, &truncated
, next_marker
));
826 ASSERT_EQ(10, (int)entries
.size());
827 ASSERT_EQ(0, truncated
);
829 /* verify all chains are valid */
830 list
<cls_rgw_gc_obj_info
>::iterator iter
= entries
.begin();
831 for (int i
= 0; i
< 10; i
++, ++iter
) {
832 cls_rgw_gc_obj_info
& entry
= *iter
;
834 /* create expected chain name */
836 snprintf(buf
, sizeof(buf
), "chain-%d", i
);
839 /* verify chain name as expected */
840 ASSERT_EQ(entry
.tag
, tag
);
842 /* verify expected num of objects in chain */
843 ASSERT_EQ(2, (int)entry
.chain
.objs
.size());
845 list
<cls_rgw_obj
>::iterator oiter
= entry
.chain
.objs
.begin();
846 cls_rgw_obj obj1
, obj2
;
848 /* create expected objects */
849 create_obj(obj1
, i
, 1);
850 create_obj(obj2
, i
, 2);
852 /* assign returned object names */
853 cls_rgw_obj
& ret_obj1
= *oiter
++;
854 cls_rgw_obj
& ret_obj2
= *oiter
;
856 /* verify objects are as expected */
857 ASSERT_EQ(1, (int)cmp_objs(obj1
, ret_obj1
));
858 ASSERT_EQ(1, (int)cmp_objs(obj2
, ret_obj2
));
862 TEST_F(cls_rgw
, gc_list
)
866 for (int i
= 0; i
< 10; i
++) {
868 snprintf(buf
, sizeof(buf
), "chain-%d", i
);
870 librados::ObjectWriteOperation op
;
871 cls_rgw_gc_obj_info info
;
873 cls_rgw_obj obj1
, obj2
;
874 create_obj(obj1
, i
, 1);
875 create_obj(obj2
, i
, 2);
876 info
.chain
.objs
.push_back(obj1
);
877 info
.chain
.objs
.push_back(obj2
);
879 op
.create(false); // create object
882 cls_rgw_gc_set_entry(op
, 0, info
);
884 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
888 list
<cls_rgw_gc_obj_info
> entries
;
889 list
<cls_rgw_gc_obj_info
> entries2
;
893 /* list chains, verify truncated */
894 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 8, true, entries
, &truncated
, next_marker
));
895 ASSERT_EQ(8, (int)entries
.size());
896 ASSERT_EQ(1, truncated
);
898 marker
= next_marker
;
901 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 8, true, entries2
, &truncated
, next_marker
));
902 ASSERT_EQ(2, (int)entries2
.size());
903 ASSERT_EQ(0, truncated
);
905 entries
.splice(entries
.end(), entries2
);
907 /* verify all chains are valid */
908 list
<cls_rgw_gc_obj_info
>::iterator iter
= entries
.begin();
909 for (int i
= 0; i
< 10; i
++, ++iter
) {
910 cls_rgw_gc_obj_info
& entry
= *iter
;
912 /* create expected chain name */
914 snprintf(buf
, sizeof(buf
), "chain-%d", i
);
917 /* verify chain name as expected */
918 ASSERT_EQ(entry
.tag
, tag
);
920 /* verify expected num of objects in chain */
921 ASSERT_EQ(2, (int)entry
.chain
.objs
.size());
923 list
<cls_rgw_obj
>::iterator oiter
= entry
.chain
.objs
.begin();
924 cls_rgw_obj obj1
, obj2
;
926 /* create expected objects */
927 create_obj(obj1
, i
, 1);
928 create_obj(obj2
, i
, 2);
930 /* assign returned object names */
931 cls_rgw_obj
& ret_obj1
= *oiter
++;
932 cls_rgw_obj
& ret_obj2
= *oiter
;
934 /* verify objects are as expected */
935 ASSERT_EQ(1, (int)cmp_objs(obj1
, ret_obj1
));
936 ASSERT_EQ(1, (int)cmp_objs(obj2
, ret_obj2
));
940 TEST_F(cls_rgw
, gc_defer
)
942 librados::IoCtx ioctx
;
943 librados::Rados rados
;
945 string gc_pool_name
= get_temp_pool_name();
947 ASSERT_EQ("", create_one_pool_pp(gc_pool_name
, rados
));
948 ASSERT_EQ(0, rados
.ioctx_create(gc_pool_name
.c_str(), ioctx
));
951 string tag
= "mychain";
953 librados::ObjectWriteOperation op
;
954 cls_rgw_gc_obj_info info
;
961 cls_rgw_gc_set_entry(op
, 0, info
);
963 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
966 list
<cls_rgw_gc_obj_info
> entries
;
970 /* list chains, verify num entries as expected */
971 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 1, true, entries
, &truncated
, next_marker
));
972 ASSERT_EQ(1, (int)entries
.size());
973 ASSERT_EQ(0, truncated
);
975 librados::ObjectWriteOperation op2
;
978 cls_rgw_gc_defer_entry(op2
, 5, tag
);
979 ASSERT_EQ(0, ioctx
.operate(oid
, &op2
));
984 /* verify list doesn't show deferred entry (this may fail if cluster is thrashing) */
985 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 1, true, entries
, &truncated
, next_marker
));
986 ASSERT_EQ(0, (int)entries
.size());
987 ASSERT_EQ(0, truncated
);
993 /* verify list shows deferred entry */
994 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 1, true, entries
, &truncated
, next_marker
));
995 ASSERT_EQ(1, (int)entries
.size());
996 ASSERT_EQ(0, truncated
);
998 librados::ObjectWriteOperation op3
;
1000 tags
.push_back(tag
);
1003 cls_rgw_gc_remove(op3
, tags
);
1004 ASSERT_EQ(0, ioctx
.operate(oid
, &op3
));
1007 next_marker
.clear();
1009 /* verify entry was removed */
1010 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 1, true, entries
, &truncated
, next_marker
));
1011 ASSERT_EQ(0, (int)entries
.size());
1012 ASSERT_EQ(0, truncated
);
1016 ASSERT_EQ(0, destroy_one_pool_pp(gc_pool_name
, rados
));
1019 auto populate_usage_log_info(std::string user
, std::string payer
, int total_usage_entries
)
1021 rgw_usage_log_info info
;
1023 for (int i
=0; i
< total_usage_entries
; i
++){
1024 auto bucket
= str_int("bucket", i
);
1025 info
.entries
.emplace_back(rgw_usage_log_entry(user
, payer
, bucket
));
1031 auto gen_usage_log_info(std::string payer
, std::string bucket
, int total_usage_entries
)
1033 rgw_usage_log_info info
;
1034 for (int i
=0; i
< total_usage_entries
; i
++){
1035 auto user
= str_int("user", i
);
1036 info
.entries
.emplace_back(rgw_usage_log_entry(user
, payer
, bucket
));
1042 TEST_F(cls_rgw
, usage_basic
)
1044 string oid
="usage.1";
1045 string user
="user1";
1046 uint64_t start_epoch
{0}, end_epoch
{(uint64_t) -1};
1047 int total_usage_entries
= 512;
1048 uint64_t max_entries
= 2000;
1051 auto info
= populate_usage_log_info(user
, payer
, total_usage_entries
);
1052 ObjectWriteOperation op
;
1053 cls_rgw_usage_log_add(op
, info
);
1054 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
1057 map
<rgw_user_bucket
, rgw_usage_log_entry
> usage
, usage2
;
1061 int ret
= cls_rgw_usage_log_read(ioctx
, oid
, user
, "", start_epoch
, end_epoch
,
1062 max_entries
, read_iter
, usage
, &truncated
);
1063 // read the entries, and see that we have all the added entries
1065 ASSERT_FALSE(truncated
);
1066 ASSERT_EQ(static_cast<uint64_t>(total_usage_entries
), usage
.size());
1068 // delete and read to assert that we've deleted all the values
1069 ASSERT_EQ(0, cls_rgw_usage_log_trim(ioctx
, oid
, user
, "", start_epoch
, end_epoch
));
1072 ret
= cls_rgw_usage_log_read(ioctx
, oid
, user
, "", start_epoch
, end_epoch
,
1073 max_entries
, read_iter
, usage2
, &truncated
);
1075 ASSERT_EQ(0u, usage2
.size());
1077 // add and read to assert that bucket option is valid for usage reading
1078 string bucket1
= "bucket-usage-1";
1079 string bucket2
= "bucket-usage-2";
1080 info
= gen_usage_log_info(payer
, bucket1
, 100);
1081 cls_rgw_usage_log_add(op
, info
);
1082 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
1084 info
= gen_usage_log_info(payer
, bucket2
, 100);
1085 cls_rgw_usage_log_add(op
, info
);
1086 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
1087 ret
= cls_rgw_usage_log_read(ioctx
, oid
, "", bucket1
, start_epoch
, end_epoch
,
1088 max_entries
, read_iter
, usage2
, &truncated
);
1090 ASSERT_EQ(100u, usage2
.size());
1092 // delete and read to assert that bucket option is valid for usage trim
1093 ASSERT_EQ(0, cls_rgw_usage_log_trim(ioctx
, oid
, "", bucket1
, start_epoch
, end_epoch
));
1095 ret
= cls_rgw_usage_log_read(ioctx
, oid
, "", bucket1
, start_epoch
, end_epoch
,
1096 max_entries
, read_iter
, usage2
, &truncated
);
1098 ASSERT_EQ(0u, usage2
.size());
1099 ASSERT_EQ(0, cls_rgw_usage_log_trim(ioctx
, oid
, "", bucket2
, start_epoch
, end_epoch
));
1102 TEST_F(cls_rgw
, usage_clear_no_obj
)
1104 string user
="user1";
1105 string oid
="usage.10";
1106 librados::ObjectWriteOperation op
;
1107 cls_rgw_usage_log_clear(op
);
1108 int ret
= ioctx
.operate(oid
, &op
);
1113 TEST_F(cls_rgw
, usage_clear
)
1115 string user
="user1";
1117 string oid
="usage.10";
1118 librados::ObjectWriteOperation op
;
1119 int max_entries
=2000;
1121 auto info
= populate_usage_log_info(user
, payer
, max_entries
);
1123 cls_rgw_usage_log_add(op
, info
);
1124 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
1126 ObjectWriteOperation op2
;
1127 cls_rgw_usage_log_clear(op2
);
1128 int ret
= ioctx
.operate(oid
, &op2
);
1131 map
<rgw_user_bucket
, rgw_usage_log_entry
> usage
;
1133 uint64_t start_epoch
{0}, end_epoch
{(uint64_t) -1};
1135 ret
= cls_rgw_usage_log_read(ioctx
, oid
, user
, "", start_epoch
, end_epoch
,
1136 max_entries
, read_iter
, usage
, &truncated
);
1138 ASSERT_EQ(0u, usage
.size());
1141 static int bilog_list(librados::IoCtx
& ioctx
, const std::string
& oid
,
1142 cls_rgw_bi_log_list_ret
*result
)
1145 librados::ObjectReadOperation op
;
1146 cls_rgw_bilog_list(op
, "", 128, result
, &retcode
);
1147 int ret
= ioctx
.operate(oid
, &op
, nullptr);
1154 static int bilog_trim(librados::IoCtx
& ioctx
, const std::string
& oid
,
1155 const std::string
& start_marker
,
1156 const std::string
& end_marker
)
1158 librados::ObjectWriteOperation op
;
1159 cls_rgw_bilog_trim(op
, start_marker
, end_marker
);
1160 return ioctx
.operate(oid
, &op
);
1163 TEST_F(cls_rgw
, bi_log_trim
)
1165 string bucket_oid
= str_int("bucket", 6);
1167 ObjectWriteOperation op
;
1168 cls_rgw_bucket_init_index(op
);
1169 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, &op
));
1171 // create 10 versioned entries. this generates instance and olh bi entries,
1172 // allowing us to check that bilog trim doesn't remove any of those
1173 for (int i
= 0; i
< 10; i
++) {
1174 cls_rgw_obj_key obj
{str_int("obj", i
), "inst"};
1175 string tag
= str_int("tag", i
);
1176 string loc
= str_int("loc", i
);
1178 index_prepare(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
1179 rgw_bucket_dir_entry_meta meta
;
1180 index_complete(ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, 1, obj
, meta
);
1184 list
<rgw_cls_bi_entry
> entries
;
1185 bool truncated
{false};
1186 ASSERT_EQ(0, cls_rgw_bi_list(ioctx
, bucket_oid
, "", "", 128,
1187 &entries
, &truncated
));
1188 // prepare/complete/instance/olh entry for each
1189 EXPECT_EQ(40u, entries
.size());
1190 EXPECT_FALSE(truncated
);
1193 vector
<rgw_bi_log_entry
> bilog1
;
1195 cls_rgw_bi_log_list_ret bilog
;
1196 ASSERT_EQ(0, bilog_list(ioctx
, bucket_oid
, &bilog
));
1197 // complete/olh entry for each
1198 EXPECT_EQ(20u, bilog
.entries
.size());
1200 bilog1
.assign(std::make_move_iterator(bilog
.entries
.begin()),
1201 std::make_move_iterator(bilog
.entries
.end()));
1203 // trim front of bilog
1205 const std::string from
= "";
1206 const std::string to
= bilog1
[0].id
;
1207 ASSERT_EQ(0, bilog_trim(ioctx
, bucket_oid
, from
, to
));
1208 cls_rgw_bi_log_list_ret bilog
;
1209 ASSERT_EQ(0, bilog_list(ioctx
, bucket_oid
, &bilog
));
1210 EXPECT_EQ(19u, bilog
.entries
.size());
1211 EXPECT_EQ(bilog1
[1].id
, bilog
.entries
.begin()->id
);
1212 ASSERT_EQ(-ENODATA
, bilog_trim(ioctx
, bucket_oid
, from
, to
));
1214 // trim back of bilog
1216 const std::string from
= bilog1
[18].id
;
1217 const std::string to
= "9";
1218 ASSERT_EQ(0, bilog_trim(ioctx
, bucket_oid
, from
, to
));
1219 cls_rgw_bi_log_list_ret bilog
;
1220 ASSERT_EQ(0, bilog_list(ioctx
, bucket_oid
, &bilog
));
1221 EXPECT_EQ(18u, bilog
.entries
.size());
1222 EXPECT_EQ(bilog1
[18].id
, bilog
.entries
.rbegin()->id
);
1223 ASSERT_EQ(-ENODATA
, bilog_trim(ioctx
, bucket_oid
, from
, to
));
1225 // trim middle of bilog
1227 const std::string from
= bilog1
[13].id
;
1228 const std::string to
= bilog1
[14].id
;
1229 ASSERT_EQ(0, bilog_trim(ioctx
, bucket_oid
, from
, to
));
1230 cls_rgw_bi_log_list_ret bilog
;
1231 ASSERT_EQ(0, bilog_list(ioctx
, bucket_oid
, &bilog
));
1232 EXPECT_EQ(17u, bilog
.entries
.size());
1233 ASSERT_EQ(-ENODATA
, bilog_trim(ioctx
, bucket_oid
, from
, to
));
1237 const std::string from
= "";
1238 const std::string to
= "9";
1239 ASSERT_EQ(0, bilog_trim(ioctx
, bucket_oid
, from
, to
));
1240 cls_rgw_bi_log_list_ret bilog
;
1241 ASSERT_EQ(0, bilog_list(ioctx
, bucket_oid
, &bilog
));
1242 EXPECT_EQ(0u, bilog
.entries
.size());
1243 ASSERT_EQ(-ENODATA
, bilog_trim(ioctx
, bucket_oid
, from
, to
));
1245 // bi list should be the same
1247 list
<rgw_cls_bi_entry
> entries
;
1248 bool truncated
{false};
1249 ASSERT_EQ(0, cls_rgw_bi_list(ioctx
, bucket_oid
, "", "", 128,
1250 &entries
, &truncated
));
1251 EXPECT_EQ(40u, entries
.size());
1252 EXPECT_FALSE(truncated
);