diff --git a/src/test/cls_rgw/test_cls_rgw.cc b/src/test/cls_rgw/test_cls_rgw.cc index 6e99dde2f143..bf60dfdd0424 100644 --- a/src/test/cls_rgw/test_cls_rgw.cc +++ b/src/test/cls_rgw/test_cls_rgw.cc @@ -1252,3 +1252,91 @@ TEST_F(cls_rgw, bi_log_trim) EXPECT_FALSE(truncated); } } + +TEST_F(cls_rgw, index_racing_removes) +{ + string bucket_oid = str_int("bucket", 8); + + ObjectWriteOperation op; + cls_rgw_bucket_init_index(op); + ASSERT_EQ(0, ioctx.operate(bucket_oid, &op)); + + int epoch = 0; + rgw_bucket_dir_entry dirent; + rgw_bucket_dir_entry_meta meta; + + // prepare/complete add for single object + const cls_rgw_obj_key obj{"obj"}; + std::string loc = "loc"; + { + std::string tag = "tag-add"; + index_prepare(ioctx, bucket_oid, CLS_RGW_OP_ADD, tag, obj, loc); + index_complete(ioctx, bucket_oid, CLS_RGW_OP_ADD, tag, ++epoch, obj, meta); + test_stats(ioctx, bucket_oid, RGWObjCategory::None, 1, 0); + } + + // list to verify no pending ops + { + std::map results; + list_entries(ioctx, bucket_oid, 1, results); + ASSERT_EQ(1, results.size()); + const auto& entries = results.begin()->second.dir.m; + ASSERT_EQ(1, entries.size()); + dirent = std::move(entries.begin()->second); + ASSERT_EQ(obj, dirent.key); + ASSERT_TRUE(dirent.exists); + ASSERT_TRUE(dirent.pending_map.empty()); + } + + // prepare three racing removals + std::string tag1 = "tag-rm1"; + std::string tag2 = "tag-rm2"; + std::string tag3 = "tag-rm3"; + index_prepare(ioctx, bucket_oid, CLS_RGW_OP_DEL, tag1, obj, loc); + index_prepare(ioctx, bucket_oid, CLS_RGW_OP_DEL, tag2, obj, loc); + index_prepare(ioctx, bucket_oid, CLS_RGW_OP_DEL, tag3, obj, loc); + + test_stats(ioctx, bucket_oid, RGWObjCategory::None, 1, 0); + + // complete on tag2 + index_complete(ioctx, bucket_oid, CLS_RGW_OP_DEL, tag2, ++epoch, obj, meta); + { + std::map results; + list_entries(ioctx, bucket_oid, 1, results); + ASSERT_EQ(1, results.size()); + const auto& entries = results.begin()->second.dir.m; + ASSERT_EQ(1, entries.size()); + dirent = std::move(entries.begin()->second); + ASSERT_EQ(obj, dirent.key); + ASSERT_FALSE(dirent.exists); + ASSERT_FALSE(dirent.pending_map.empty()); + } + + // cancel on tag1 + index_complete(ioctx, bucket_oid, CLS_RGW_OP_CANCEL, tag1, ++epoch, obj, meta); + { + std::map results; + list_entries(ioctx, bucket_oid, 1, results); + ASSERT_EQ(1, results.size()); + const auto& entries = results.begin()->second.dir.m; + ASSERT_EQ(1, entries.size()); + dirent = std::move(entries.begin()->second); + ASSERT_EQ(obj, dirent.key); + ASSERT_FALSE(dirent.exists); + ASSERT_FALSE(dirent.pending_map.empty()); + } + + // final cancel on tag3 + index_complete(ioctx, bucket_oid, CLS_RGW_OP_CANCEL, tag3, ++epoch, obj, meta); + + // verify that the key was removed + { + std::map results; + list_entries(ioctx, bucket_oid, 1, results); + EXPECT_EQ(1, results.size()); + const auto& entries = results.begin()->second.dir.m; + ASSERT_EQ(0, entries.size()); + } + + test_stats(ioctx, bucket_oid, RGWObjCategory::None, 0, 0); +}