Add a regression test for LOC_CORRUPTED cleared on second scrub
This commit is contained in:
@@ -19,6 +19,8 @@
|
|||||||
// for without having to drive the rest of the startup sequence.
|
// for without having to drive the rest of the startup sequence.
|
||||||
void test_load_global_config()
|
void test_load_global_config()
|
||||||
{
|
{
|
||||||
|
printf("test_load_global_config\n");
|
||||||
|
|
||||||
osd_test_fixture_t f;
|
osd_test_fixture_t f;
|
||||||
f.st_cli->pause();
|
f.st_cli->pause();
|
||||||
f.start(json11::Json::object {
|
f.start(json11::Json::object {
|
||||||
@@ -64,6 +66,8 @@ static osd_op_t *make_write_op(inode_t inode, uint64_t offset, uint64_t len, uin
|
|||||||
// Layout: pool 1, replicated x2, primary = OSD 1 (us), secondary = OSD 2.
|
// Layout: pool 1, replicated x2, primary = OSD 1 (us), secondary = OSD 2.
|
||||||
void test_replicated_write()
|
void test_replicated_write()
|
||||||
{
|
{
|
||||||
|
printf("test_replicated_write\n");
|
||||||
|
|
||||||
osd_test_fixture_t f;
|
osd_test_fixture_t f;
|
||||||
f.configure_replicated_pool(/*pool_id*/ 1, /*pg_size*/ 2, /*pg_minsize*/ 1, /*pg_count*/ 1,
|
f.configure_replicated_pool(/*pool_id*/ 1, /*pg_size*/ 2, /*pg_minsize*/ 1, /*pg_count*/ 1,
|
||||||
{ { 1, 2 } });
|
{ { 1, 2 } });
|
||||||
@@ -141,9 +145,160 @@ void test_replicated_write()
|
|||||||
printf("test_replicated_write passed\n");
|
printf("test_replicated_write passed\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for a bug where a second scrub on a still-failing replica
|
||||||
|
// (already marked LOC_CORRUPTED) would clear the corruption flag.
|
||||||
|
void test_scrub_corruption_persists()
|
||||||
|
{
|
||||||
|
printf("test_scrub_corruption_persists\n");
|
||||||
|
|
||||||
|
osd_test_fixture_t f;
|
||||||
|
f.configure_replicated_pool(/*pool_id*/ 1, /*pg_size*/ 2, /*pg_minsize*/ 1, /*pg_count*/ 1,
|
||||||
|
{ { 1, 2 } });
|
||||||
|
f.start(json11::Json::object {
|
||||||
|
{ "osd_num", 1 },
|
||||||
|
{ "etcd_address", "127.0.0.1:2379" },
|
||||||
|
{ "immediate_commit", "all" },
|
||||||
|
{ "block_size", 131072 },
|
||||||
|
{ "bitmap_granularity", 4096 },
|
||||||
|
});
|
||||||
|
f.connect_peer(2);
|
||||||
|
f.complete_peering_empty();
|
||||||
|
assert(f.pg(1, 1).state & PG_ACTIVE);
|
||||||
|
|
||||||
|
// ---- Write one object so there is something to scrub ----
|
||||||
|
inode_t inode = INODE_WITH_POOL(1, 1);
|
||||||
|
auto *write_op = make_write_op(inode, 0, 4096, 0xab);
|
||||||
|
int final_retval = -1;
|
||||||
|
write_op->callback = [&final_retval](osd_op_t *op) {
|
||||||
|
final_retval = op->reply.hdr.retval;
|
||||||
|
};
|
||||||
|
f.exec(write_op);
|
||||||
|
|
||||||
|
// Stage 1: zero-length read for version resolution
|
||||||
|
assert(f.bs->queued.size() == 1);
|
||||||
|
auto *zero_read = f.bs->take();
|
||||||
|
assert(zero_read->opcode == BS_OP_READ);
|
||||||
|
assert(zero_read->len == 0);
|
||||||
|
zero_read->version = 0;
|
||||||
|
zero_read->retval = 0;
|
||||||
|
zero_read->callback(zero_read);
|
||||||
|
|
||||||
|
// Stage 2: local write + peer write
|
||||||
|
assert(f.bs->queued.size() == 1);
|
||||||
|
assert(f.peer(2)->sent_ops.size() == 1);
|
||||||
|
auto *local_write = f.bs->take();
|
||||||
|
auto *peer_write = f.peer_take(2, OSD_OP_SEC_WRITE_STABLE);
|
||||||
|
|
||||||
|
// Complete both
|
||||||
|
local_write->retval = local_write->len;
|
||||||
|
local_write->callback(local_write);
|
||||||
|
assert(final_retval == -1); // still waiting for peer
|
||||||
|
peer_write->reply.hdr.retval = peer_write->req.sec_rw.len;
|
||||||
|
peer_write->reply.sec_rw.version = 1;
|
||||||
|
peer_write->callback(peer_write);
|
||||||
|
|
||||||
|
assert(final_retval == 4096);
|
||||||
|
assert(f.pg(1, 1).inflight == 0);
|
||||||
|
assert(f.pg(1, 1).write_queue.empty());
|
||||||
|
delete write_op;
|
||||||
|
|
||||||
|
object_id oid = { .inode = inode, .stripe = 0 };
|
||||||
|
|
||||||
|
// ============ SCRUB 1 — peer returns -EIO ============
|
||||||
|
|
||||||
|
auto *scrub1 = new osd_op_t();
|
||||||
|
scrub1->op_type = OSD_OP_IN;
|
||||||
|
scrub1->client_id = 0;
|
||||||
|
scrub1->req.rw.header.magic = SECONDARY_OSD_OP_MAGIC;
|
||||||
|
scrub1->req.rw.header.id = 2;
|
||||||
|
scrub1->req.rw.header.opcode = OSD_OP_SCRUB;
|
||||||
|
scrub1->req.rw.inode = inode;
|
||||||
|
scrub1->req.rw.offset = 0;
|
||||||
|
scrub1->req.rw.len = 0;
|
||||||
|
|
||||||
|
int scrub1_retval = -1;
|
||||||
|
scrub1->callback = [&scrub1_retval](osd_op_t *op) {
|
||||||
|
scrub1_retval = op->reply.hdr.retval;
|
||||||
|
};
|
||||||
|
f.exec(scrub1);
|
||||||
|
|
||||||
|
// submit_scrub_subops submitted local BS_OP_READ + peer OSD_OP_SEC_READ
|
||||||
|
assert(f.bs->queued.size() == 1);
|
||||||
|
auto *local_r1 = f.bs->take(BS_OP_READ);
|
||||||
|
memset(local_r1->buf, 0xab, local_r1->len);
|
||||||
|
local_r1->retval = local_r1->len;
|
||||||
|
local_r1->version = 1;
|
||||||
|
local_r1->callback(local_r1);
|
||||||
|
|
||||||
|
auto *peer_r1 = f.peer_take(2, OSD_OP_SEC_READ);
|
||||||
|
peer_r1->reply.hdr.retval = -EIO;
|
||||||
|
peer_r1->callback(peer_r1);
|
||||||
|
|
||||||
|
assert(scrub1_retval == 0);
|
||||||
|
|
||||||
|
// Verify: OSD 2 chunk is now LOC_CORRUPTED
|
||||||
|
auto check_corrupted = [&]()
|
||||||
|
{
|
||||||
|
if (!(f.pg(1, 1).state & PG_HAS_CORRUPTED))
|
||||||
|
return false;
|
||||||
|
auto st_it = f.pg(1, 1).degraded_objects.find(oid);
|
||||||
|
if (st_it == f.pg(1, 1).degraded_objects.end())
|
||||||
|
return false;
|
||||||
|
for (auto & chunk : st_it->second->osd_set)
|
||||||
|
if (chunk.osd_num == 2 && (chunk.loc_bad & LOC_CORRUPTED))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
bool osd2_corrupted = check_corrupted();
|
||||||
|
assert(osd2_corrupted);
|
||||||
|
printf("test_scrub_corruption_persists: scrub 1 -> LOC_CORRUPTED set\n");
|
||||||
|
delete scrub1;
|
||||||
|
|
||||||
|
// ============ SCRUB 2 — peer still returns -EIO ============
|
||||||
|
|
||||||
|
auto *scrub2 = new osd_op_t();
|
||||||
|
scrub2->op_type = OSD_OP_IN;
|
||||||
|
scrub2->client_id = 0;
|
||||||
|
scrub2->req.rw.header.magic = SECONDARY_OSD_OP_MAGIC;
|
||||||
|
scrub2->req.rw.header.id = 3;
|
||||||
|
scrub2->req.rw.header.opcode = OSD_OP_SCRUB;
|
||||||
|
scrub2->req.rw.inode = inode;
|
||||||
|
scrub2->req.rw.offset = 0;
|
||||||
|
scrub2->req.rw.len = 0;
|
||||||
|
|
||||||
|
int scrub2_retval = -1;
|
||||||
|
scrub2->callback = [&scrub2_retval](osd_op_t *op) {
|
||||||
|
scrub2_retval = op->reply.hdr.retval;
|
||||||
|
};
|
||||||
|
f.exec(scrub2);
|
||||||
|
|
||||||
|
assert(f.bs->queued.size() == 1);
|
||||||
|
auto *local_r2 = f.bs->take(BS_OP_READ);
|
||||||
|
memset(local_r2->buf, 0xab, local_r2->len);
|
||||||
|
local_r2->retval = local_r2->len;
|
||||||
|
local_r2->version = 1;
|
||||||
|
local_r2->callback(local_r2);
|
||||||
|
|
||||||
|
auto *peer_r2 = f.peer_take(2, OSD_OP_SEC_READ);
|
||||||
|
peer_r2->reply.hdr.retval = -EIO;
|
||||||
|
peer_r2->callback(peer_r2);
|
||||||
|
|
||||||
|
assert(scrub2_retval == 0);
|
||||||
|
|
||||||
|
// THE KEY ASSERTION — LOC_CORRUPTED must persist
|
||||||
|
osd2_corrupted = check_corrupted();
|
||||||
|
if (!osd2_corrupted)
|
||||||
|
printf("BUG: LOC_CORRUPTED cleared on second scrub\n");
|
||||||
|
assert(osd2_corrupted);
|
||||||
|
|
||||||
|
delete scrub2;
|
||||||
|
printf("test_scrub_corruption_persists passed\n");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int narg, char *args[])
|
int main(int narg, char *args[])
|
||||||
{
|
{
|
||||||
test_load_global_config();
|
test_load_global_config();
|
||||||
test_replicated_write();
|
test_replicated_write();
|
||||||
|
test_scrub_corruption_persists();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,11 +185,11 @@ struct osd_test_fixture_t
|
|||||||
auto *op = *it;
|
auto *op = *it;
|
||||||
if (op->opcode == BS_OP_LIST)
|
if (op->opcode == BS_OP_LIST)
|
||||||
{
|
{
|
||||||
it = bs->queued.erase(it);
|
it = bs->queued.erase(it++);
|
||||||
op->retval = 0;
|
op->retval = 0;
|
||||||
op->version = 0;
|
op->version = 0;
|
||||||
op->buf = NULL;
|
op->buf = NULL;
|
||||||
op->callback(op);
|
std::function<void(blockstore_op_t*)>(op->callback)(op);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
++it;
|
++it;
|
||||||
|
|||||||
Reference in New Issue
Block a user