test(ha-term-safety): cross-member walcompare divergence tool (task 5.3)

Diffs member WALs by (lsn -> term, pcrc) over the committed prefix, exits non-zero
on any committed-prefix divergence with the diverging LSN + per-member entry
identities. Reuses the canonical waldump decoder via a new per-entry visitor seam
(no second WAL parser). Detects same-LSN/diff-term and same-LSN/diff-content
divergences (the bug-851 class) that an LSN-range check would mask.
This commit is contained in:
Rongsong Shen 2026-06-22 13:05:23 +00:00
parent d39500ba48
commit a166ecb45b
9 changed files with 842 additions and 5 deletions

View file

@ -1,6 +1,7 @@
#include <argp.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
@ -22,6 +23,7 @@
#include "delay.h"
#include "idgen.h"
#include "preason.h"
#include "walcompare.h" /* cross-member committed-prefix divergence (cmd_walcompare) */
#include "waldump.h" /* offline WAL inspector (cmd_waldump) */
#include "xce/cluster.h" /* xce_parse_peers, xce_cluster_config_t */
#include "xce/types.h"
@ -124,6 +126,7 @@ extern int cmd_expect_job(int, char*[]);
extern int cmd_wait_primary(int, char*[]);
extern int cmd_ctrlstatus(int, char*[]);
extern int cmd_waldump(int, char*[]);
extern int cmd_walcompare(int, char*[]);
static int worker_house_keeping(void*);
/* Session verdict / loop state (xce-test-e2e-primitives task 1.1).
@ -182,6 +185,7 @@ static command_t top_commands[] = {
{ "set", cmd_set, "Show/Set settings" },
{ "echo", cmd_echo, "Print a message" },
{ "waldump", cmd_waldump, "Inspect WAL segments" },
{ "walcompare", cmd_walcompare, "Diff member committed WAL prefixes" },
{ "quit", cmd_quit, "Quit xce test" },
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL }
};
@ -220,6 +224,7 @@ static command_t client_commands[] = {
"wait until a primary serves [timeout_ms]" },
{ "ctrlstatus", cmd_ctrlstatus, "show controller/cluster status" },
{ "waldump", cmd_waldump, "Inspect WAL segments" },
{ "walcompare", cmd_walcompare, "Diff member committed WAL prefixes" },
{ "shutdown", cmd_shutdown, "shutdown xce controller" },
{ "jobrun", cmd_jobrun, "run a job" },
{ "jobsub", cmd_jobsub, "submit a job" },
@ -716,6 +721,69 @@ int cmd_waldump(int argc, char* argv[])
return rc;
}
/* walcompare [--committed N] <memberA> <memberB> [more...] — diff member WAL
* committed prefixes by (lsn -> term, pcrc) and FAIL (nonzero) on any
* committed-prefix divergence (ha-term-safety R6.1/R6.2/R6.3), reusing the
* waldump decoder. Each member path may be a seg-*.wal file, a wal/ dir, or a
* node/data dir (same resolution as waldump). With no --committed, the shared
* committed boundary = the minimum CRC-intact tip across members. The diverging
* LSN and per-member (term, pcrc) identities are reported. Exit 0 = identical,
* nonzero = divergence or load error — so it doubles as a scripted assertion. */
int cmd_walcompare(int argc, char* argv[])
{
uint64_t committed = 0; /* 0 = derive the shared boundary */
int ai = 1;
/* Optional global committed boundary applied to every member. */
if (argc > ai && strcmp(argv[ai], "--committed") == 0) {
if (argc <= ai + 1) {
fprintf(stderr, "walcompare: --committed needs an LSN\n");
return -1;
}
char* end = NULL;
unsigned long v = strtoul(argv[ai + 1], &end, 10);
if (end == argv[ai + 1] || *end != '\0') {
fprintf(stderr, "walcompare: bad --committed LSN '%s'\n",
argv[ai + 1]);
return -1;
}
committed = (uint64_t)v;
ai += 2;
}
int n = argc - ai;
if (n < 2) {
fprintf(stderr,
"usage: walcompare [--committed N] <memberA> <memberB> "
"[more...]\n");
return -1;
}
if (n > 16) {
fprintf(stderr, "walcompare: at most 16 members\n");
return -1;
}
walcompare_member_t members[16];
for (int i = 0; i < n; i++) {
members[i].path = argv[ai + i];
members[i].committed_lsn = committed; /* 0 => derive shared boundary */
}
walcompare_divergence_t div;
int rc = walcompare(members, n, stdout, &div);
if (rc == WALCMP_OK)
return 0;
if (rc == WALCMP_DIVERGENT) {
fprintf(stderr,
"walcompare: FAIL — committed-prefix divergence at LSN %" PRIu64
"\n",
div.lsn);
return -1; /* nonzero session verdict */
}
fprintf(stderr, "walcompare: error: %s\n", strerror(-rc));
return -1;
}
int cmd_up(int argc, char* argv[])
{
if (active_ctx->parent != NULL) {