feat(config-upgrade-snapshot): define HostAdminState/QueueAdminState capnp structs, bump snapshot format version
Adds independently-extensible Cap'n Proto frames for the upgrade snapshot's host/queue admin-state sections (frame-only, not reusing Worker/QueueConf which bundle dynamic state) and bumps SNAPSHOT_FORMAT_VERSION 1->2 so a checkpoint written under the prior raw queue-frame layout is fail-loud rejected rather than misinterpreted. Task 1 of 19. Requirements: 1.9, 5.7
This commit is contained in:
parent
e0c5e8b7a4
commit
9e68335aee
6 changed files with 322 additions and 8 deletions
|
|
@ -3282,7 +3282,7 @@ static void test_snapshot_recv_two_chunks_assembles_and_renames(void)
|
|||
* header) but is now correctly rejected as an unrecognized
|
||||
* format_version. Layout (design.md "Checkpoint File Layout"): the
|
||||
* 57-byte legacy header (cfgref_present=0 at byte 16, so bytes 17-56 are
|
||||
* unread and left zero) + format_version=1 + job_count=0 + queue_count=0
|
||||
* unread and left zero) + format_version=2 + job_count=0 + queue_count=0
|
||||
* (69 bytes total), padded with zero filler out to 128 bytes to keep the
|
||||
* two-64-byte-chunk shape this test exercises — the padding is never
|
||||
* read (load_snapshot_entities stops right after the queue_count=0
|
||||
|
|
@ -3294,7 +3294,8 @@ static void test_snapshot_recv_two_chunks_assembles_and_renames(void)
|
|||
memcpy(body, "XCESNAP\0", 8);
|
||||
memcpy(body + 8, &snap_lsn, 8);
|
||||
/* body[16] (cfgref_present) left 0 by the memset above. */
|
||||
uint32_t fake_fmt = 1u; /* must match persist.c's SNAPSHOT_FORMAT_VERSION */
|
||||
uint32_t fake_fmt = 2u; /* must match persist.c's SNAPSHOT_FORMAT_VERSION
|
||||
* (config-upgrade-snapshot task 1 bumped 1->2) */
|
||||
uint32_t fake_job_count = 0u;
|
||||
uint32_t fake_queue_count = 0u;
|
||||
memcpy(body + 57, &fake_fmt, sizeof(fake_fmt));
|
||||
|
|
@ -3376,6 +3377,107 @@ static void test_snapshot_recv_two_chunks_assembles_and_renames(void)
|
|||
printf("test_snapshot_recv_two_chunks_assembles_and_renames: PASS\n");
|
||||
}
|
||||
|
||||
/* config-upgrade-snapshot task 1 (Req 1.9, 5.7): SNAPSHOT_FORMAT_VERSION was
|
||||
* bumped 1->2 to reserve the version for the HostAdminState/QueueAdminState
|
||||
* capnp migration of the host/queue admin-state frames. A checkpoint written
|
||||
* under the PRIOR format_version (1 -- the raw queue-frame layout this spec
|
||||
* migrates off of) must be hard-rejected fail-loud by a binary built against
|
||||
* the bumped SNAPSHOT_FORMAT_VERSION=2, never silently reinterpreted (Design
|
||||
* Invariant 4; persist.c:2541's existing `fmt != SNAPSHOT_FORMAT_VERSION`
|
||||
* check, unchanged logic, new constant). Same well-formed-except-for-
|
||||
* format-version fixture shape as
|
||||
* test_snapshot_recv_two_chunks_assembles_and_renames above, but the
|
||||
* embedded format_version is pinned to the literal PRIOR value (1)
|
||||
* regardless of whatever persist.c's SNAPSHOT_FORMAT_VERSION currently is —
|
||||
* this is exactly the "old on-disk checkpoint, new binary" skew the version
|
||||
* bump must reject. */
|
||||
static void test_snapshot_recv_old_format_version_rejected_fail_loud(void)
|
||||
{
|
||||
char tmpdir[] = "/tmp/xcectest_snaprecv_oldfmt_XXXXXX";
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(tmpdir));
|
||||
|
||||
char snap_dir[512];
|
||||
snprintf(snap_dir, sizeof(snap_dir), "%s/snap", tmpdir);
|
||||
TEST_ASSERT_EQUAL_INT(0, mkdir(snap_dir, 0700));
|
||||
|
||||
uint64_t snap_lsn = 200;
|
||||
size_t total_size = 128;
|
||||
uint8_t body[128];
|
||||
memset(body, 0, sizeof(body));
|
||||
memcpy(body, "XCESNAP\0", 8);
|
||||
memcpy(body + 8, &snap_lsn, 8);
|
||||
/* body[16] (cfgref_present) left 0 by the memset above. */
|
||||
uint32_t old_fmt = 1u; /* the pre-migration raw queue-frame layout --
|
||||
* deliberately NOT persist.c's current
|
||||
* SNAPSHOT_FORMAT_VERSION */
|
||||
uint32_t fake_job_count = 0u;
|
||||
uint32_t fake_queue_count = 0u;
|
||||
memcpy(body + 57, &old_fmt, sizeof(old_fmt));
|
||||
memcpy(body + 61, &fake_job_count, sizeof(fake_job_count));
|
||||
memcpy(body + 65, &fake_queue_count, sizeof(fake_queue_count));
|
||||
|
||||
uint8_t chunk0[64];
|
||||
uint8_t chunk1[64];
|
||||
memcpy(chunk0, body, 64);
|
||||
memcpy(chunk1, body + 64, 64);
|
||||
|
||||
persist_t *p = calloc(1, sizeof(persist_t));
|
||||
TEST_ASSERT_NOT_NULL(p);
|
||||
persist_config_t pcfg = persist_config_defaults();
|
||||
persist_set_workdir(p, tmpdir, "ctrl-snaprecv-oldfmt");
|
||||
persist_init(p, &pcfg, NULL);
|
||||
persist_start(p);
|
||||
|
||||
ha_state_t ha;
|
||||
snap_recv_ha_init(&ha);
|
||||
|
||||
/* Chunk 0 — assembling snap.part never itself parses the body, so this
|
||||
* must still succeed regardless of what format_version the bytes carry. */
|
||||
snapshot_transfer_t c0;
|
||||
memset(&c0, 0, sizeof(c0));
|
||||
c0.snapshot_lsn = snap_lsn;
|
||||
c0.chunk_index = 0;
|
||||
c0.chunk_offset = 0;
|
||||
c0.total_size = (uint64_t)total_size;
|
||||
c0.is_last = 0;
|
||||
|
||||
int rc = ha_handle_snapshot_chunk(&ha, p, snap_dir, &c0, chunk0, 64);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "assembling a chunk never parses the body");
|
||||
|
||||
/* Chunk 1 (isLast=true) triggers rename + persist_load_snapshot — this is
|
||||
* where the format-version check fires and must fail loud. */
|
||||
snapshot_transfer_t c1;
|
||||
memset(&c1, 0, sizeof(c1));
|
||||
c1.snapshot_lsn = snap_lsn;
|
||||
c1.chunk_index = 1;
|
||||
c1.chunk_offset = 64;
|
||||
c1.total_size = (uint64_t)total_size;
|
||||
c1.is_last = 1;
|
||||
|
||||
rc = ha_handle_snapshot_chunk(&ha, p, snap_dir, &c1, chunk1, 64);
|
||||
TEST_ASSERT_TRUE_MESSAGE(rc != 0,
|
||||
"a checkpoint encoded under the prior SNAPSHOT_FORMAT_VERSION must be "
|
||||
"rejected fail-loud by a binary built against the bumped version, "
|
||||
"never silently reinterpreted (Requirement 5.7)");
|
||||
|
||||
/* A rejected load must never advance applied_lsn (no partial/silent
|
||||
* acceptance of mismatched-format bytes). */
|
||||
uint64_t got_lsn = (uint64_t)atomic_load_explicit(&ha.applied_lsn,
|
||||
memory_order_acquire);
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0, got_lsn,
|
||||
"a rejected format-mismatched checkpoint must never advance applied_lsn");
|
||||
|
||||
ha_fini(&ha);
|
||||
persist_stop(p);
|
||||
persist_fini(p);
|
||||
free(p);
|
||||
|
||||
char cmd2[512];
|
||||
snprintf(cmd2, sizeof(cmd2), "rm -rf %s", tmpdir);
|
||||
system(cmd2);
|
||||
printf("test_snapshot_recv_old_format_version_rejected_fail_loud: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 6.1: a legacy (pre-ha-entity-snapshots) position-only snapshot —
|
||||
* exactly the 16-byte XCESNAP magic+lsn header, no entity payload — driven
|
||||
* through the real receive handler end-to-end. persist_load_snapshot's
|
||||
|
|
@ -3571,7 +3673,8 @@ static void test_snapshot_recv_128kb_two_chunks(void)
|
|||
memcpy(chunk0, "XCESNAP\0", 8);
|
||||
memcpy(chunk0 + 8, &snap_lsn, 8);
|
||||
/* chunk0[16] (cfgref_present) already 0 from calloc. */
|
||||
const uint32_t fake_fmt = 1u; /* must match persist.c's SNAPSHOT_FORMAT_VERSION */
|
||||
const uint32_t fake_fmt = 2u; /* must match persist.c's SNAPSHOT_FORMAT_VERSION
|
||||
* (config-upgrade-snapshot task 1 bumped 1->2) */
|
||||
memcpy(chunk0 + 57, &fake_fmt, sizeof(fake_fmt));
|
||||
|
||||
/* ── Chunk 0: not last ── */
|
||||
|
|
@ -3662,7 +3765,7 @@ static void test_snapshot_recv_out_of_order_chunks_assemble_correctly(void)
|
|||
/* 4 chunks of 64 bytes each = 256-byte body. Must be a WELL-FORMED
|
||||
* entity-bearing checkpoint (same reason as
|
||||
* test_snapshot_recv_two_chunks_assembles_and_renames above): legacy
|
||||
* 57-byte header (cfgref_present=0) + format_version=1 + job_count=0 +
|
||||
* 57-byte header (cfgref_present=0) + format_version=2 + job_count=0 +
|
||||
* queue_count=0, then filler out to 256 bytes. The filler is
|
||||
* position-dependent (distinct per source byte, not per chunk) so ANY
|
||||
* chunk landing at the wrong file offset is immediately detectable via
|
||||
|
|
@ -3676,7 +3779,8 @@ static void test_snapshot_recv_out_of_order_chunks_assemble_correctly(void)
|
|||
memcpy(body + 8, &snap_lsn, 8);
|
||||
for (int i = 16; i < 256; i++)
|
||||
body[i] = (uint8_t)(i * 3 + 7); /* position-dependent, non-repeating */
|
||||
uint32_t fake_fmt = 1u;
|
||||
uint32_t fake_fmt = 2u; /* must match persist.c's SNAPSHOT_FORMAT_VERSION
|
||||
* (config-upgrade-snapshot task 1 bumped 1->2) */
|
||||
uint32_t fake_job_count = 0u;
|
||||
uint32_t fake_queue_count = 0u;
|
||||
memcpy(body + 57, &fake_fmt, sizeof(fake_fmt));
|
||||
|
|
@ -4268,6 +4372,9 @@ void test_config_repl(void)
|
|||
RUN_TEST(test_snapshot_transfer_stops_and_propagates_send_failure);
|
||||
/* Task 3.5c: standby snapshot receive handler */
|
||||
RUN_TEST(test_snapshot_recv_two_chunks_assembles_and_renames);
|
||||
/* config-upgrade-snapshot task 1: prior SNAPSHOT_FORMAT_VERSION rejected
|
||||
* fail-loud by a binary built against the bumped version */
|
||||
RUN_TEST(test_snapshot_recv_old_format_version_rejected_fail_loud);
|
||||
/* Task 6.1: legacy position-only file consumed end-to-end via the
|
||||
* receive handler — entities_restored=false path */
|
||||
RUN_TEST(test_snapshot_recv_legacy_file_advances_without_entities);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue