feat(config-upgrade-snapshot): add configcompare tool, extend snapverify (task 8.4)
Build configcompare_paths(), an offline sibling to snapdiff/walcompare that recomputes each member's active-config sha256 fresh from its own on-disk workdir/name/conf bytes and reports identical vs. divergent (Req 4.2). Wire it as a cmd_configcompare CLI subcommand in test.c, matching its siblings, after review caught it shipped as an unreachable library function in round 1. Extend snapverify to match the current on-disk checkpoint format: bump SNAP_FORMAT_VERSION to 2, replace the stale raw queue-frame decode with the capnp length-prefixed walk, add host-frame and config-bytes-section structural walks, and decode (not just presence-check) the header's cfgref tag/sha. Without this fix snapverify rejected every checkpoint written by the current codebase.
This commit is contained in:
parent
c5d8ecbd7d
commit
1d09631df0
10 changed files with 997 additions and 47 deletions
74
src/controller/testsuite/configcompare.h
Normal file
74
src/controller/testsuite/configcompare.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
/*
|
||||
* configcompare.h — cross-member active-configuration comparator
|
||||
* (config-upgrade-snapshot task 8.4, Requirement 4.2).
|
||||
*
|
||||
* Compares each cluster member's locally-held ACTIVE configuration content
|
||||
* hash, recomputed FRESH from that member's own on-disk configuration file
|
||||
* (never trusted from a cached/embedded tag), and reports whether every
|
||||
* member agrees.
|
||||
*
|
||||
* A new sibling tool to walcompare, NOT an extension of it: walcompare's
|
||||
* internal model is WAL-entry-indexed (lsn -> term, pcrc) -- an awkward fit
|
||||
* for a single (tag, sha256) value per member with no LSN dimension
|
||||
* (design.md "Regression Tooling"). Mirrors snapdiff_paths's exact
|
||||
* signature/reporting shape instead.
|
||||
*
|
||||
* Offline/file-based: no live RPC to any cluster member, consistent with
|
||||
* every other tool in this family (walcompare, snapdiff, snapverify).
|
||||
*
|
||||
* Strictly read-only: opens files O_RDONLY, mutates nothing.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Bound on the number of members a single call can compare -- generous for
|
||||
* any real cluster size this codebase targets; a caller with more members
|
||||
* than this is almost certainly passing a malformed argument list. */
|
||||
#define CONFIGCOMPARE_MAX_MEMBERS 32
|
||||
|
||||
typedef struct {
|
||||
int n; /* member count passed in */
|
||||
int read_rc[CONFIGCOMPARE_MAX_MEMBERS]; /* 0 = resolved+hashed ok,
|
||||
* else -errno; entries at
|
||||
* or past a short-circuit
|
||||
* failure are unset (0)
|
||||
* and must be ignored --
|
||||
* see all_present */
|
||||
uint8_t sha256[CONFIGCOMPARE_MAX_MEMBERS][32]; /* valid only where
|
||||
* read_rc[i] == 0 */
|
||||
int all_present; /* 1 iff every member (0..n-1) was resolved and
|
||||
* hashed successfully */
|
||||
int all_identical; /* 1 iff all_present && every member's sha256
|
||||
* equals the first member's */
|
||||
int first_divergent_index; /* -1 if all_identical or !all_present;
|
||||
* else the first index (after the
|
||||
* reference, member 0) whose hash
|
||||
* differs from member 0's */
|
||||
} configcompare_report_t;
|
||||
|
||||
/* Compare the active-configuration content hash of `n` members. Each
|
||||
* `conf_paths[i]` is a member's `workdir/name` directory -- the SAME
|
||||
* directory persist.c's `read_active_config_bytes()` and config_repl.c's
|
||||
* `local_config_sha256()` both resolve `conf/current` (and the versioned
|
||||
* config file it points at) under, matching where this spec's snapshot-embed
|
||||
* step also reads from. NOT the raw `conf/current` file itself -- this
|
||||
* function resolves `<path>/conf/current` and `<path>/conf/<versioned-file>`
|
||||
* internally, exactly like those two functions do.
|
||||
*
|
||||
* Prints a human-readable report to `out` (may be NULL) and fills `*rep`
|
||||
* (may be NULL). Returns:
|
||||
* 0 if every member's config was readable and all hashes are identical
|
||||
* 1 if every member's config was readable but at least one diverges
|
||||
* (see `*rep->first_divergent_index` for which)
|
||||
* <0 (negative errno) if `n < 2` (nothing to compare), `n` exceeds
|
||||
* CONFIGCOMPARE_MAX_MEMBERS, or any member's config could not be
|
||||
* resolved/read -- mirrors snapdiff_paths's early-return-on-open-
|
||||
* failure style: the first unreadable member short-circuits the scan,
|
||||
* and `*rep` is still filled with whatever was learned about the
|
||||
* members checked before the failure, so the report can say which
|
||||
* member and why. A member that could not be read is NOT evidence of
|
||||
* matching OR mismatching configuration -- it is a distinct third
|
||||
* outcome from "identical" and "divergent". */
|
||||
int configcompare_paths(const char* const* conf_paths, int n, FILE* out,
|
||||
configcompare_report_t* rep);
|
||||
Loading…
Add table
Add a link
Reference in a new issue