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.h"
17 using namespace librados
;
19 librados::Rados rados
;
20 librados::IoCtx ioctx
;
24 /* must be the first test! */
27 pool_name
= get_temp_pool_name();
29 ASSERT_EQ("", create_one_pool_pp(pool_name
, rados
));
30 ASSERT_EQ(0, rados
.ioctx_create(pool_name
.c_str(), ioctx
));
34 string
str_int(string s
, int i
)
37 snprintf(buf
, sizeof(buf
), "-%d", i
);
45 vector
<ObjectOperation
*> ops
;
50 vector
<ObjectOperation
*>::iterator iter
;
51 for (iter
= ops
.begin(); iter
!= ops
.end(); ++iter
) {
52 ObjectOperation
*op
= *iter
;
57 ObjectReadOperation
*read_op() {
58 ObjectReadOperation
*op
= new ObjectReadOperation
;
63 ObjectWriteOperation
*write_op() {
64 ObjectWriteOperation
*op
= new ObjectWriteOperation
;
70 void test_stats(librados::IoCtx
& ioctx
, string
& oid
, int category
, uint64_t num_entries
, uint64_t total_size
)
72 map
<int, struct rgw_cls_list_ret
> results
;
73 map
<int, string
> oids
;
75 ASSERT_EQ(0, CLSRGWIssueGetDirHeader(ioctx
, oids
, results
, 8)());
79 map
<int, struct rgw_cls_list_ret
>::iterator iter
= results
.begin();
80 for (; iter
!= results
.end(); ++iter
) {
81 entries
+= (iter
->second
).dir
.header
.stats
[category
].num_entries
;
82 size
+= (iter
->second
).dir
.header
.stats
[category
].total_size
;
84 ASSERT_EQ(total_size
, size
);
85 ASSERT_EQ(num_entries
, entries
);
88 void index_prepare(OpMgr
& mgr
, librados::IoCtx
& ioctx
, string
& oid
, RGWModifyOp index_op
, string
& tag
, string
& obj
, string
& loc
)
90 ObjectWriteOperation
*op
= mgr
.write_op();
91 cls_rgw_obj_key
key(obj
, string());
92 rgw_zone_set zones_trace
;
93 cls_rgw_bucket_prepare_op(*op
, index_op
, tag
, key
, loc
, true, 0, zones_trace
);
94 ASSERT_EQ(0, ioctx
.operate(oid
, op
));
97 void index_complete(OpMgr
& mgr
, librados::IoCtx
& ioctx
, string
& oid
, RGWModifyOp index_op
, string
& tag
, int epoch
, string
& obj
, rgw_bucket_dir_entry_meta
& meta
)
99 ObjectWriteOperation
*op
= mgr
.write_op();
100 cls_rgw_obj_key
key(obj
, string());
101 rgw_bucket_entry_ver ver
;
102 ver
.pool
= ioctx
.get_id();
104 meta
.accounted_size
= meta
.size
;
105 cls_rgw_bucket_complete_op(*op
, index_op
, tag
, ver
, key
, meta
, nullptr, true, 0, nullptr);
106 ASSERT_EQ(0, ioctx
.operate(oid
, op
));
109 TEST(cls_rgw
, index_basic
)
111 string bucket_oid
= str_int("bucket", 0);
115 ObjectWriteOperation
*op
= mgr
.write_op();
116 cls_rgw_bucket_init(*op
);
117 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, op
));
121 uint64_t obj_size
= 1024;
124 for (int i
= 0; i
< NUM_OBJS
; i
++) {
125 string obj
= str_int("obj", i
);
126 string tag
= str_int("tag", i
);
127 string loc
= str_int("loc", i
);
129 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
131 test_stats(ioctx
, bucket_oid
, 0, i
, obj_size
* i
);
134 rgw_bucket_dir_entry_meta meta
;
136 meta
.size
= obj_size
;
137 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, epoch
, obj
, meta
);
140 test_stats(ioctx
, bucket_oid
, 0, NUM_OBJS
, obj_size
* NUM_OBJS
);
143 TEST(cls_rgw
, index_multiple_obj_writers
)
145 string bucket_oid
= str_int("bucket", 1);
149 ObjectWriteOperation
*op
= mgr
.write_op();
150 cls_rgw_bucket_init(*op
);
151 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, op
));
153 uint64_t obj_size
= 1024;
155 string obj
= str_int("obj", 0);
156 string loc
= str_int("loc", 0);
157 /* multi prepare on a single object */
158 for (int i
= 0; i
< NUM_OBJS
; i
++) {
159 string tag
= str_int("tag", i
);
161 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
163 test_stats(ioctx
, bucket_oid
, 0, 0, 0);
166 for (int i
= NUM_OBJS
; i
> 0; i
--) {
167 string tag
= str_int("tag", i
- 1);
169 rgw_bucket_dir_entry_meta meta
;
171 meta
.size
= obj_size
* i
;
173 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, i
, obj
, meta
);
175 /* verify that object size doesn't change, as we went back with epoch */
176 test_stats(ioctx
, bucket_oid
, 0, 1, obj_size
* NUM_OBJS
);
180 TEST(cls_rgw
, index_remove_object
)
182 string bucket_oid
= str_int("bucket", 2);
186 ObjectWriteOperation
*op
= mgr
.write_op();
187 cls_rgw_bucket_init(*op
);
188 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, op
));
190 uint64_t obj_size
= 1024;
191 uint64_t total_size
= 0;
195 /* prepare multiple objects */
196 for (int i
= 0; i
< NUM_OBJS
; i
++) {
197 string obj
= str_int("obj", i
);
198 string tag
= str_int("tag", i
);
199 string loc
= str_int("loc", i
);
201 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
203 test_stats(ioctx
, bucket_oid
, 0, i
, total_size
);
205 rgw_bucket_dir_entry_meta meta
;
207 meta
.size
= i
* obj_size
;
208 total_size
+= i
* obj_size
;
210 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, ++epoch
, obj
, meta
);
212 test_stats(ioctx
, bucket_oid
, 0, i
+ 1, total_size
);
215 int i
= NUM_OBJS
/ 2;
216 string tag_remove
= "tag-rm";
217 string tag_modify
= "tag-mod";
218 string obj
= str_int("obj", i
);
219 string loc
= str_int("loc", i
);
221 /* prepare both removal and modification on the same object */
222 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_remove
, obj
, loc
);
223 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag_modify
, obj
, loc
);
225 test_stats(ioctx
, bucket_oid
, 0, NUM_OBJS
, total_size
);
227 rgw_bucket_dir_entry_meta meta
;
229 /* complete object removal */
230 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_remove
, ++epoch
, obj
, meta
);
232 /* verify stats correct */
233 total_size
-= i
* obj_size
;
234 test_stats(ioctx
, bucket_oid
, 0, NUM_OBJS
- 1, total_size
);
239 /* complete object modification */
240 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag_modify
, ++epoch
, obj
, meta
);
242 /* verify stats correct */
243 total_size
+= meta
.size
;
244 test_stats(ioctx
, bucket_oid
, 0, NUM_OBJS
, total_size
);
247 /* prepare both removal and modification on the same object, this time we'll
248 * first complete modification then remove*/
249 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_remove
, obj
, loc
);
250 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_modify
, obj
, loc
);
252 /* complete modification */
253 total_size
-= meta
.size
;
254 meta
.size
= i
* obj_size
* 2;
257 /* complete object modification */
258 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag_modify
, ++epoch
, obj
, meta
);
260 /* verify stats correct */
261 total_size
+= meta
.size
;
262 test_stats(ioctx
, bucket_oid
, 0, NUM_OBJS
, total_size
);
264 /* complete object removal */
265 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag_remove
, ++epoch
, obj
, meta
);
267 /* verify stats correct */
268 total_size
-= meta
.size
;
269 test_stats(ioctx
, bucket_oid
, 0, NUM_OBJS
- 1, total_size
);
272 TEST(cls_rgw
, index_suggest
)
274 string bucket_oid
= str_int("bucket", 3);
278 ObjectWriteOperation
*op
= mgr
.write_op();
279 cls_rgw_bucket_init(*op
);
280 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, op
));
282 uint64_t total_size
= 0;
288 uint64_t obj_size
= 1024;
290 /* create multiple objects */
291 for (int i
= 0; i
< num_objs
; i
++) {
292 string obj
= str_int("obj", i
);
293 string tag
= str_int("tag", i
);
294 string loc
= str_int("loc", i
);
296 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
298 test_stats(ioctx
, bucket_oid
, 0, i
, total_size
);
300 rgw_bucket_dir_entry_meta meta
;
302 meta
.size
= obj_size
;
303 total_size
+= meta
.size
;
305 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, ++epoch
, obj
, meta
);
307 test_stats(ioctx
, bucket_oid
, 0, i
+ 1, total_size
);
310 /* prepare (without completion) some of the objects */
311 for (int i
= 0; i
< num_objs
; i
+= 2) {
312 string obj
= str_int("obj", i
);
313 string tag
= str_int("tag-prepare", i
);
314 string loc
= str_int("loc", i
);
316 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
318 test_stats(ioctx
, bucket_oid
, 0, num_objs
, total_size
);
321 int actual_num_objs
= num_objs
;
322 /* remove half of the objects */
323 for (int i
= num_objs
/ 2; i
< num_objs
; i
++) {
324 string obj
= str_int("obj", i
);
325 string tag
= str_int("tag-rm", i
);
326 string loc
= str_int("loc", i
);
328 index_prepare(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_ADD
, tag
, obj
, loc
);
330 test_stats(ioctx
, bucket_oid
, 0, actual_num_objs
, total_size
);
332 rgw_bucket_dir_entry_meta meta
;
333 index_complete(mgr
, ioctx
, bucket_oid
, CLS_RGW_OP_DEL
, tag
, ++epoch
, obj
, meta
);
335 total_size
-= obj_size
;
337 test_stats(ioctx
, bucket_oid
, 0, actual_num_objs
, total_size
);
342 for (int i
= 0; i
< num_objs
; i
+= 2) {
343 string obj
= str_int("obj", i
);
344 string tag
= str_int("tag-rm", i
);
345 string loc
= str_int("loc", i
);
347 rgw_bucket_dir_entry dirent
;
348 dirent
.key
.name
= obj
;
349 dirent
.locator
= loc
;
350 dirent
.exists
= (i
< num_objs
/ 2); // we removed half the objects
351 dirent
.meta
.size
= 1024;
352 dirent
.meta
.accounted_size
= 1024;
354 char suggest_op
= (i
< num_objs
/ 2 ? CEPH_RGW_UPDATE
: CEPH_RGW_REMOVE
);
355 cls_rgw_encode_suggestion(suggest_op
, dirent
, updates
);
358 map
<int, string
> bucket_objs
;
359 bucket_objs
[0] = bucket_oid
;
360 int r
= CLSRGWIssueSetTagTimeout(ioctx
, bucket_objs
, 8 /* max aio */, 1)();
365 /* suggest changes! */
367 cls_rgw_suggest_changes(*op
, updates
);
368 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, op
));
370 /* suggest changes twice! */
372 cls_rgw_suggest_changes(*op
, updates
);
373 ASSERT_EQ(0, ioctx
.operate(bucket_oid
, op
));
375 test_stats(ioctx
, bucket_oid
, 0, num_objs
/ 2, total_size
);
378 /* test garbage collection */
379 static void create_obj(cls_rgw_obj
& obj
, int i
, int j
)
382 snprintf(buf
, sizeof(buf
), "-%d.%d", i
, j
);
384 obj
.pool
.append(buf
);
385 obj
.key
.name
= "oid";
386 obj
.key
.name
.append(buf
);
391 static bool cmp_objs(cls_rgw_obj
& obj1
, cls_rgw_obj
& obj2
)
393 return (obj1
.pool
== obj2
.pool
) &&
394 (obj1
.key
== obj2
.key
) &&
395 (obj1
.loc
== obj2
.loc
);
399 TEST(cls_rgw
, gc_set
)
403 for (int i
= 0; i
< 10; i
++) {
405 snprintf(buf
, sizeof(buf
), "chain-%d", i
);
407 librados::ObjectWriteOperation op
;
408 cls_rgw_gc_obj_info info
;
410 cls_rgw_obj obj1
, obj2
;
411 create_obj(obj1
, i
, 1);
412 create_obj(obj2
, i
, 2);
413 info
.chain
.objs
.push_back(obj1
);
414 info
.chain
.objs
.push_back(obj2
);
416 op
.create(false); // create object
419 cls_rgw_gc_set_entry(op
, 0, info
);
421 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
425 list
<cls_rgw_gc_obj_info
> entries
;
429 /* list chains, verify truncated */
430 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 8, true, entries
, &truncated
, next_marker
));
431 ASSERT_EQ(8, (int)entries
.size());
432 ASSERT_EQ(1, truncated
);
437 /* list all chains, verify not truncated */
438 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 10, true, entries
, &truncated
, next_marker
));
439 ASSERT_EQ(10, (int)entries
.size());
440 ASSERT_EQ(0, truncated
);
442 /* verify all chains are valid */
443 list
<cls_rgw_gc_obj_info
>::iterator iter
= entries
.begin();
444 for (int i
= 0; i
< 10; i
++, ++iter
) {
445 cls_rgw_gc_obj_info
& entry
= *iter
;
447 /* create expected chain name */
449 snprintf(buf
, sizeof(buf
), "chain-%d", i
);
452 /* verify chain name as expected */
453 ASSERT_EQ(entry
.tag
, tag
);
455 /* verify expected num of objects in chain */
456 ASSERT_EQ(2, (int)entry
.chain
.objs
.size());
458 list
<cls_rgw_obj
>::iterator oiter
= entry
.chain
.objs
.begin();
459 cls_rgw_obj obj1
, obj2
;
461 /* create expected objects */
462 create_obj(obj1
, i
, 1);
463 create_obj(obj2
, i
, 2);
465 /* assign returned object names */
466 cls_rgw_obj
& ret_obj1
= *oiter
++;
467 cls_rgw_obj
& ret_obj2
= *oiter
;
469 /* verify objects are as expected */
470 ASSERT_EQ(1, (int)cmp_objs(obj1
, ret_obj1
));
471 ASSERT_EQ(1, (int)cmp_objs(obj2
, ret_obj2
));
475 TEST(cls_rgw
, gc_list
)
479 for (int i
= 0; i
< 10; i
++) {
481 snprintf(buf
, sizeof(buf
), "chain-%d", i
);
483 librados::ObjectWriteOperation op
;
484 cls_rgw_gc_obj_info info
;
486 cls_rgw_obj obj1
, obj2
;
487 create_obj(obj1
, i
, 1);
488 create_obj(obj2
, i
, 2);
489 info
.chain
.objs
.push_back(obj1
);
490 info
.chain
.objs
.push_back(obj2
);
492 op
.create(false); // create object
495 cls_rgw_gc_set_entry(op
, 0, info
);
497 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
501 list
<cls_rgw_gc_obj_info
> entries
;
502 list
<cls_rgw_gc_obj_info
> entries2
;
506 /* list chains, verify truncated */
507 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 8, true, entries
, &truncated
, next_marker
));
508 ASSERT_EQ(8, (int)entries
.size());
509 ASSERT_EQ(1, truncated
);
511 marker
= next_marker
;
514 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 8, true, entries2
, &truncated
, next_marker
));
515 ASSERT_EQ(2, (int)entries2
.size());
516 ASSERT_EQ(0, truncated
);
518 entries
.splice(entries
.end(), entries2
);
520 /* verify all chains are valid */
521 list
<cls_rgw_gc_obj_info
>::iterator iter
= entries
.begin();
522 for (int i
= 0; i
< 10; i
++, ++iter
) {
523 cls_rgw_gc_obj_info
& entry
= *iter
;
525 /* create expected chain name */
527 snprintf(buf
, sizeof(buf
), "chain-%d", i
);
530 /* verify chain name as expected */
531 ASSERT_EQ(entry
.tag
, tag
);
533 /* verify expected num of objects in chain */
534 ASSERT_EQ(2, (int)entry
.chain
.objs
.size());
536 list
<cls_rgw_obj
>::iterator oiter
= entry
.chain
.objs
.begin();
537 cls_rgw_obj obj1
, obj2
;
539 /* create expected objects */
540 create_obj(obj1
, i
, 1);
541 create_obj(obj2
, i
, 2);
543 /* assign returned object names */
544 cls_rgw_obj
& ret_obj1
= *oiter
++;
545 cls_rgw_obj
& ret_obj2
= *oiter
;
547 /* verify objects are as expected */
548 ASSERT_EQ(1, (int)cmp_objs(obj1
, ret_obj1
));
549 ASSERT_EQ(1, (int)cmp_objs(obj2
, ret_obj2
));
553 TEST(cls_rgw
, gc_defer
)
555 librados::IoCtx ioctx
;
556 librados::Rados rados
;
558 string gc_pool_name
= get_temp_pool_name();
560 ASSERT_EQ("", create_one_pool_pp(gc_pool_name
, rados
));
561 ASSERT_EQ(0, rados
.ioctx_create(gc_pool_name
.c_str(), ioctx
));
564 string tag
= "mychain";
566 librados::ObjectWriteOperation op
;
567 cls_rgw_gc_obj_info info
;
574 cls_rgw_gc_set_entry(op
, 0, info
);
576 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
579 list
<cls_rgw_gc_obj_info
> entries
;
583 /* list chains, verify num entries as expected */
584 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 1, true, entries
, &truncated
, next_marker
));
585 ASSERT_EQ(1, (int)entries
.size());
586 ASSERT_EQ(0, truncated
);
588 librados::ObjectWriteOperation op2
;
591 cls_rgw_gc_defer_entry(op2
, 5, tag
);
592 ASSERT_EQ(0, ioctx
.operate(oid
, &op2
));
597 /* verify list doesn't show deferred entry (this may fail if cluster is thrashing) */
598 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 1, true, entries
, &truncated
, next_marker
));
599 ASSERT_EQ(0, (int)entries
.size());
600 ASSERT_EQ(0, truncated
);
606 /* verify list shows deferred entry */
607 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 1, true, entries
, &truncated
, next_marker
));
608 ASSERT_EQ(1, (int)entries
.size());
609 ASSERT_EQ(0, truncated
);
611 librados::ObjectWriteOperation op3
;
616 cls_rgw_gc_remove(op3
, tags
);
617 ASSERT_EQ(0, ioctx
.operate(oid
, &op3
));
622 /* verify entry was removed */
623 ASSERT_EQ(0, cls_rgw_gc_list(ioctx
, oid
, marker
, 1, true, entries
, &truncated
, next_marker
));
624 ASSERT_EQ(0, (int)entries
.size());
625 ASSERT_EQ(0, truncated
);
629 ASSERT_EQ(0, destroy_one_pool_pp(gc_pool_name
, rados
));
632 TEST(cls_rgw
, usage_basic
)
634 string oid
="usage.1";
636 uint64_t start_epoch
{0}, end_epoch
{(uint64_t) -1};
637 constexpr auto total_usage_entries
= 512;
638 uint64_t max_entries
= 2000;
640 rgw_usage_log_info info
;
642 for (int i
=0; i
< total_usage_entries
; i
++){
643 auto bucket
= str_int("bucket", i
);
644 string p
; // we are not testing bucket payer here
645 info
.entries
.emplace_back(rgw_usage_log_entry(user
, p
, bucket
));
647 ObjectWriteOperation op
;
648 cls_rgw_usage_log_add(op
, info
);
649 ASSERT_EQ(0, ioctx
.operate(oid
, &op
));
652 map
<rgw_user_bucket
, rgw_usage_log_entry
> usage
, usage2
;
656 int ret
= cls_rgw_usage_log_read(ioctx
, oid
, user
, start_epoch
, end_epoch
,
657 max_entries
, read_iter
, usage
, &truncated
);
658 // read the entries, and see that we have all the added entries
660 ASSERT_FALSE(truncated
);
661 ASSERT_EQ(total_usage_entries
, usage
.size());
663 // delete and read to assert that we've deleted all the values
664 ASSERT_EQ(0, cls_rgw_usage_log_trim(ioctx
, oid
, user
, start_epoch
, end_epoch
));
667 ret
= cls_rgw_usage_log_read(ioctx
, oid
, user
, start_epoch
, end_epoch
,
668 max_entries
, read_iter
, usage2
, &truncated
);
670 ASSERT_EQ(0, usage2
.size());
674 /* must be last test! */
676 TEST(cls_rgw
, finalize
)
680 ASSERT_EQ(0, destroy_one_pool_pp(pool_name
, rados
));