Enforce heartbeat_timeout_ms >= 2 * heartbeat_interval_ms at startup. validate_heartbeat_config() in startup_validation.c returns -EINVAL with error message on violation; wired in cluster_init_phase1 before ha_init(). 4 unit tests added to test_ha_startup.c.
168 lines
5.6 KiB
C
168 lines
5.6 KiB
C
/* startup_validation.c — Startup pre-flight validation functions.
|
|
*
|
|
* Implements validate_vcpus(), validate_startup_config(),
|
|
* validate_peer_count(), validate_cert_files(), and
|
|
* validate_heartbeat_config() for use in main.c and the controller
|
|
* unit test suite.
|
|
*
|
|
* Requirements: 4.1 (peer count), 4.2 (cert pre-flight),
|
|
* 4.3 (vcpus), 4.4 (vcpus key deprecation warn),
|
|
* 2.4 (heartbeat timing validation)
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
/* ── Req 4.3: --vcpus validation ─────────────────────────────────────── */
|
|
|
|
/*
|
|
* validate_vcpus: returns 0 if vcpus >= 1, -EINVAL otherwise.
|
|
* Called from main.c after argp_parse() sets arguments.vcpus.
|
|
*/
|
|
int validate_vcpus(int vcpus)
|
|
{
|
|
if (vcpus <= 0) {
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ── Req 4.4: vcpus key in config file — warn only ───────────────────── */
|
|
|
|
/*
|
|
* validate_startup_config: scan config_path for a "vcpus" key.
|
|
* If found, write a deprecation warning into warn_buf (must contain "vcpus").
|
|
* Always returns 0 — the presence of a vcpus key is not a fatal error.
|
|
* warn_buf is left empty (first byte = '\0') when no vcpus key is found.
|
|
*
|
|
* The scan is line-based: a line contains the vcpus key if it starts with
|
|
* optional whitespace followed by the literal string "vcpus".
|
|
*/
|
|
int validate_startup_config(const char *config_path,
|
|
char *warn_buf, size_t warn_len)
|
|
{
|
|
if (warn_buf && warn_len > 0) {
|
|
warn_buf[0] = '\0';
|
|
}
|
|
|
|
if (!config_path) {
|
|
return 0;
|
|
}
|
|
|
|
FILE *fp = fopen(config_path, "r");
|
|
if (!fp) {
|
|
return 0; /* can't open — not a fatal error at this stage */
|
|
}
|
|
|
|
char line[1024];
|
|
while (fgets(line, sizeof(line), fp)) {
|
|
/* skip leading whitespace */
|
|
const char *p = line;
|
|
while (*p == ' ' || *p == '\t') {
|
|
p++;
|
|
}
|
|
if (strncmp(p, "vcpus", 5) == 0) {
|
|
/* Found a vcpus key — write deprecation warning */
|
|
if (warn_buf && warn_len > 0) {
|
|
snprintf(warn_buf, warn_len,
|
|
"config file contains deprecated 'vcpus' key; "
|
|
"use --vcpus CLI parameter instead");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
/* ── Req 4.1: even-N peer count rejection ─────────────────────────────── */
|
|
|
|
/*
|
|
* validate_peer_count: returns 0 for valid odd counts (1, 3, 5, ...),
|
|
* -EINVAL for even counts > 1.
|
|
* n_peers == 1 is single-node mode — always valid.
|
|
* Even counts > 1 cannot form a safe majority partition.
|
|
*/
|
|
int validate_peer_count(int n_peers)
|
|
{
|
|
if (n_peers > 1 && (n_peers % 2) == 0) {
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ── Req 4.2: TLS cert pre-flight ─────────────────────────────────────── */
|
|
|
|
/*
|
|
* validate_cert_files: check that cert_path and key_path are readable.
|
|
* On the first missing/unreadable file, sets err_buf to a message
|
|
* containing the full file path and returns -ENOENT.
|
|
* Returns 0 if both files are accessible.
|
|
*/
|
|
int validate_cert_files(const char *cert_path, const char *key_path,
|
|
char *err_buf, size_t err_len)
|
|
{
|
|
if (access(cert_path, R_OK) != 0) {
|
|
if (err_buf && err_len > 0) {
|
|
snprintf(err_buf, err_len,
|
|
"TLS certificate not found or unreadable: %s",
|
|
cert_path);
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (access(key_path, R_OK) != 0) {
|
|
if (err_buf && err_len > 0) {
|
|
snprintf(err_buf, err_len,
|
|
"TLS key not found or unreadable: %s",
|
|
key_path);
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ── Req 2.4: heartbeat timing validation ─────────────────────────────── */
|
|
|
|
/*
|
|
* validate_heartbeat_config: enforce heartbeat_timeout_ms >= 2 * heartbeat_interval_ms.
|
|
*
|
|
* A timeout smaller than two intervals means the standby could declare the
|
|
* primary dead before a single missed heartbeat has elapsed — that is an
|
|
* operator misconfiguration that must be caught at startup.
|
|
*
|
|
* Returns 0 when the relationship holds (valid config).
|
|
* Returns -EINVAL when heartbeat_timeout_ms < 2 * heartbeat_interval_ms;
|
|
* writes a human-readable error message into err_buf.
|
|
*/
|
|
int validate_heartbeat_config(uint32_t heartbeat_timeout_ms,
|
|
uint32_t heartbeat_interval_ms,
|
|
char *err_buf, size_t err_len)
|
|
{
|
|
if (err_buf && err_len > 0) {
|
|
err_buf[0] = '\0';
|
|
}
|
|
|
|
if (heartbeat_timeout_ms < 2 * heartbeat_interval_ms) {
|
|
if (err_buf && err_len > 0) {
|
|
snprintf(err_buf, err_len,
|
|
"heartbeat_timeout_ms (%u) must be >= 2 * heartbeat_interval_ms (%u); "
|
|
"current ratio %.2f is below the minimum of 2.0 — "
|
|
"set heartbeat_timeout_ms >= %u",
|
|
heartbeat_timeout_ms,
|
|
heartbeat_interval_ms,
|
|
heartbeat_interval_ms > 0
|
|
? (double)heartbeat_timeout_ms / (double)heartbeat_interval_ms
|
|
: 0.0,
|
|
2 * heartbeat_interval_ms);
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|