ha_stream_wal_to_standby() reads WAL from committed_lsn range, accumulates entries to 256/64KB per batch, enqueues all via xce_request() (non-blocking). Partial final batch sent immediately. Wired into STREAM_WAL case. 3 tests: 500-entries/2-batches, partial batch, empty range. Fixed stale task-number comments per review.
227 lines
9 KiB
C
227 lines
9 KiB
C
#pragma once
|
|
/* config_repl.h — config storage, SHA-256, and replication functions */
|
|
#define _GNU_SOURCE
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
|
|
#include "myconf.h"
|
|
#include "clusterconfig.h"
|
|
#include "netio.h"
|
|
#include "request.h"
|
|
|
|
/* Max config blob size — reject before allocating */
|
|
#define CONF_MAX_SIZE (1 * 1024 * 1024) /* 1 MiB */
|
|
|
|
/* Sentinel name for the synthetic lost-and-found queue and host */
|
|
#define XCE_LOST_AND_FOUND_NAME "lost_and_found"
|
|
|
|
/* Compile-time gate for OP_upgradeConfig (enabled: standbys apply pushed config) */
|
|
#ifndef CONF_ENABLE_UPGRADE_CONFIG
|
|
#define CONF_ENABLE_UPGRADE_CONFIG 1
|
|
#endif
|
|
|
|
/* Push timeout for standby fan-out (ms) */
|
|
#ifndef CONF_PUSH_TIMEOUT_MS
|
|
#define CONF_PUSH_TIMEOUT_MS 500
|
|
#endif
|
|
|
|
/* Certificate expiry warning threshold (days) */
|
|
#define CERT_WARN_DAYS 30
|
|
|
|
/* ── Phase 1: Storage ────────────────────────────────────────── */
|
|
|
|
/*
|
|
* conf_read_current_filename: read HEAD-style pointer file at
|
|
* <workdir>/conf/current. Strips trailing newline. Validates format:
|
|
* must start with "cluster.bin." followed by exactly 12 hex chars.
|
|
* Returns 0 on success; -ENOENT if absent; -ENODATA if empty (zero-length);
|
|
* -EINVAL if malformed (path separator, wrong prefix, wrong hex length).
|
|
* out must be at least NAME_MAX bytes.
|
|
*/
|
|
int conf_read_current_filename(const char *workdir, char *out, size_t outsz);
|
|
|
|
/*
|
|
* conf_save_config: atomically write versioned file and update HEAD pointer.
|
|
* 1. Writes <conf_dir>/cluster.bin.<hex>.tmp, fsyncs, renames to versioned path.
|
|
* 2. Writes <conf_dir>/current.<tid>.tmp, fsyncs, renames to conf/current.
|
|
* 3. fsyncs the conf/ directory.
|
|
* 4. Trims old versions.
|
|
* 5. Updates myconf->conffile (heap-allocated via asprintf; caller must free).
|
|
* 6. Updates myconf->config_version_tag and myconf->config_sha256.
|
|
* Returns 0 on success, -errno on failure.
|
|
*/
|
|
int conf_save_config(myconf_t *myconf, const uint8_t *bytes, size_t len,
|
|
const uint8_t sha256[32]);
|
|
|
|
/*
|
|
* conf_read_raw: read versioned file at conffile path.
|
|
* Allocates *bytes (caller must free). Enforces CONF_MAX_SIZE.
|
|
* Returns 0 on success; -EFBIG if exceeds limit; -errno on I/O error.
|
|
*/
|
|
int conf_read_raw(const char *conffile, uint8_t **bytes, size_t *len);
|
|
|
|
/*
|
|
* conf_get_saved_tag: return first 8 bytes of SHA-256 of the current
|
|
* versioned file in host byte order. Returns 0 if conf/current absent.
|
|
* This is the "no config saved" zero-tag sentinel.
|
|
*/
|
|
uint64_t conf_get_saved_tag(const char *workdir);
|
|
|
|
/*
|
|
* conf_check_stale: returns 1 if the current versioned file's mtime is
|
|
* older than stale_timeout_s seconds. Returns 0 if within timeout or
|
|
* if stale_timeout_s == 0 (disabled). Returns 0 if conf/current absent.
|
|
*/
|
|
int conf_check_stale(const char *workdir, time_t stale_timeout_s);
|
|
|
|
/*
|
|
* conf_trim_old_versions: keep at most keep_n versioned cluster.bin.* files
|
|
* sorted by mtime (newest first). Always preserves the file named by the
|
|
* current conf/current pointer. Also removes stale .tmp files.
|
|
*/
|
|
void conf_trim_old_versions(const char *workdir, int keep_n);
|
|
|
|
/*
|
|
* config_contains_reserved_name: returns 1 if ClusterConfig contains a
|
|
* queue or host named "lost_and_found". Returns 0 for NULL config.
|
|
* Checks queues[i]->queue (queue name) and hconfigs[i]->name (host name).
|
|
*/
|
|
int config_contains_reserved_name(cluster_config_t *cfg);
|
|
|
|
/*
|
|
* create_lost_and_found: ensure the synthetic "lost_and_found" queue and
|
|
* host exist before persist_recover() runs. Stub for Task 10.
|
|
*/
|
|
void create_lost_and_found(cluster_t *c);
|
|
|
|
/*
|
|
* handle_config_request: handle an OP_replConfig request from a standby.
|
|
* Reads the current versioned config blob and sends it back.
|
|
* Stub for Task 8 (full implementation in Task 9).
|
|
*/
|
|
int handle_config_request(cluster_t *c, endpoint_t *ep, xce_request_t *req);
|
|
|
|
/*
|
|
* handle_repl_config_push: standby handler for OPCode_replConfigPush.
|
|
* Verifies SHA-256, saves config blob to disk, and reloads cluster_config.
|
|
*/
|
|
int handle_repl_config_push(cluster_t *c, endpoint_t *ep, xce_request_t *req);
|
|
|
|
/*
|
|
* push_config_to_standbys: fan-out a new config blob to all connected standby
|
|
* controllers in parallel. Best-effort — standbys that miss the push will
|
|
* pull on reconnect via handle_config_request().
|
|
* Returns the number of standbys that successfully received the push.
|
|
* Only meaningful when CONF_ENABLE_UPGRADE_CONFIG is set, but always declared
|
|
* so callers can reference it without an #if guard.
|
|
*/
|
|
int push_config_to_standbys(cluster_t *c, const uint8_t *bytes, size_t blen,
|
|
const uint8_t sha256[32], uint64_t new_tag);
|
|
|
|
/*
|
|
* handle_upgrade_config: handle an OP_upgradeConfig request from an admin tool.
|
|
* xce_decode() now extracts sha256 + bytes into req->body.upgradeconfigrequestmsg
|
|
* before freeing the capnp segment, so no raw packet copy is needed.
|
|
* Only callable when CONF_ENABLE_UPGRADE_CONFIG is set.
|
|
*/
|
|
int handle_upgrade_config(cluster_t *c, endpoint_t *ep, xce_request_t *req);
|
|
|
|
/*
|
|
* ha_stream_wal_to_standby: batch-stream WAL entries [standby_lsn+1, committed_lsn]
|
|
* to a standby endpoint via xce_request() (non-blocking).
|
|
*
|
|
* Accumulates WAL entries up to WAL_STREAM_BATCH_ENTRIES (256) entries or
|
|
* WAL_STREAM_BATCH_BYTES (65536) bytes per batch, then enqueues each batch
|
|
* as a single OPCode_walCatchupEntry message with n_entries set.
|
|
* All batches for the full range are enqueued in a single invocation — never
|
|
* blocks the vtaskmgr thread.
|
|
*
|
|
* A partial final batch (fewer entries than the batch limit) is sent immediately
|
|
* without waiting for the batch to fill (Req 5.4).
|
|
*
|
|
* Parameters:
|
|
* ep — outbound endpoint to the standby
|
|
* wal_path — directory containing WAL segment files (seg-*.wal)
|
|
* standby_lsn — standby's last applied LSN (stream starts at standby_lsn+1)
|
|
* committed_lsn— primary's committed LSN (stream ends at committed_lsn inclusive)
|
|
*
|
|
* Returns 0 on success; negative on internal error (e.g., NULL ep).
|
|
* Requirements: 5.1, 5.2, 5.4, 5.5 (ha-repl-hardening spec)
|
|
* Boundary: WAL Streaming (config_repl.c)
|
|
*/
|
|
#define WAL_STREAM_BATCH_ENTRIES 256
|
|
#define WAL_STREAM_BATCH_BYTES 65536
|
|
int ha_stream_wal_to_standby(endpoint_t *ep, const char *wal_path,
|
|
uint64_t standby_lsn, uint64_t committed_lsn);
|
|
|
|
/*
|
|
* handle_ctrlhello: primary-side handler for OP_hello from a joining standby.
|
|
*
|
|
* Implements the state sync sequence (Req 6.5, 6.6, 6.7):
|
|
* 1. repl_add_standby() + repl_start_catchup()
|
|
* 2. Reply CtrlHelloReply{committed_lsn, needs_snapshot, config_tag}
|
|
* 3. If needs_snapshot: persist_checkpoint() + stream OP_snapshotTransfer
|
|
* 4. Stream OP_walCatchupEntry [applied_lsn+1 → committed_lsn]
|
|
* 5. On catch-up complete: repl_complete_catchup()
|
|
* 6. If config_tag mismatch: push_config_to_standbys()
|
|
*/
|
|
int handle_ctrlhello(cluster_t *c, endpoint_t *ep, xce_request_t *req);
|
|
|
|
/*
|
|
* sanitise_cn: copy at most maxlen printable ASCII chars from src to a
|
|
* static buffer, replacing non-printable/control chars with '?'.
|
|
* Returns pointer to the static buffer. NOT thread-safe (static storage).
|
|
*/
|
|
char *sanitise_cn(const char *src, size_t maxlen);
|
|
|
|
#ifdef XCE_TESTING
|
|
#include <pthread.h>
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
|
|
/*
|
|
* xce_probe_config_tag_hook: test-only injection point for config tag probing.
|
|
* When non-NULL, xce_probe_config_tag() calls this instead of the real
|
|
* network probe, allowing unit tests to return controlled tag values
|
|
* without real network connections.
|
|
* Set by test infrastructure (e.g. test_ha_bootstrap_support.c) before tests run.
|
|
*/
|
|
extern int (*xce_probe_config_tag_hook)(const char *host, uint16_t port,
|
|
uint64_t *tag_out);
|
|
|
|
/*
|
|
* load_primary_config_for_test: test-only wrapper that exercises the path
|
|
* resolution logic of load_primary_config() without calling FATAL.
|
|
* Returns 0 if conf/current resolves to a valid filename, negative errno
|
|
* otherwise. Does NOT perform Cap'n Proto decode or SHA-256 computation.
|
|
*/
|
|
int load_primary_config_for_test(cluster_t *c);
|
|
|
|
/*
|
|
* push_ctx_t: exposed under XCE_TESTING so unit tests can construct
|
|
* synthetic push contexts to drive wait_all_with_timeout_for_test().
|
|
* In production builds this type is private to config_repl.c.
|
|
*/
|
|
typedef struct {
|
|
endpoint_t *ep;
|
|
xce_peerinfo_t *from;
|
|
uint64_t new_tag;
|
|
const uint8_t *bytes;
|
|
size_t blen;
|
|
const uint8_t *sha256;
|
|
int acked;
|
|
int started;
|
|
int done;
|
|
} push_ctx_t;
|
|
|
|
/*
|
|
* wait_all_with_timeout_for_test: test-only shim exposing the static
|
|
* wait_all_with_timeout() helper for unit tests.
|
|
* Joins all started push threads within timeout_ms (CLOCK_MONOTONIC).
|
|
* Sets *done = 1 when deadline expires. Returns number of acked threads.
|
|
*/
|
|
int wait_all_with_timeout_for_test(pthread_t *threads, push_ctx_t *ctxs,
|
|
int n, uint32_t timeout_ms,
|
|
atomic_int *done);
|
|
#endif
|