feat(ha-heartbeat-liveness): add validate_heartbeat_config startup check (task 1.2)
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.
This commit is contained in:
parent
bcb45e59d2
commit
9f9a6b3c0f
4 changed files with 129 additions and 4 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
- Observable: project builds; `ha_init()` test verifies `last_activity == 0` after init
|
- Observable: project builds; `ha_init()` test verifies `last_activity == 0` after init
|
||||||
- _Requirements: 1.1, 1.3, 3.1_
|
- _Requirements: 1.1, 1.3, 3.1_
|
||||||
|
|
||||||
- [ ] 1.2 Add heartbeat timing validation at startup
|
- [x] 1.2 Add heartbeat timing validation at startup
|
||||||
- Implement `validate_heartbeat_config(heartbeat_timeout_ms, heartbeat_interval_ms, err_buf, err_len)` in `startup_validation.c`
|
- Implement `validate_heartbeat_config(heartbeat_timeout_ms, heartbeat_interval_ms, err_buf, err_len)` in `startup_validation.c`
|
||||||
- Returns `-EINVAL` when `heartbeat_timeout_ms < 2 * heartbeat_interval_ms`; writes human-readable error into `err_buf`
|
- Returns `-EINVAL` when `heartbeat_timeout_ms < 2 * heartbeat_interval_ms`; writes human-readable error into `err_buf`
|
||||||
- Wire call into `main.c` after `ha_config_t` is populated, before `ha_init()`
|
- Wire call into `main.c` after `ha_config_t` is populated, before `ha_init()`
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,11 @@
|
||||||
#include "xstring.h"
|
#include "xstring.h"
|
||||||
#include "helper.h"
|
#include "helper.h"
|
||||||
|
|
||||||
|
/* startup_validation.c — forward declaration */
|
||||||
|
int validate_heartbeat_config(uint32_t heartbeat_timeout_ms,
|
||||||
|
uint32_t heartbeat_interval_ms,
|
||||||
|
char *err_buf, size_t err_len);
|
||||||
|
|
||||||
static xce_peerinfo_t from_controller
|
static xce_peerinfo_t from_controller
|
||||||
= { .name = "controller", .key = "keepSecret" };
|
= { .name = "controller", .key = "keepSecret" };
|
||||||
|
|
||||||
|
|
@ -217,6 +222,9 @@ static int myreq_handler(vtask_t* task)
|
||||||
reply.status = 0;
|
reply.status = 0;
|
||||||
reply.data_which = RequestReply_data_nodata;
|
reply.data_which = RequestReply_data_nodata;
|
||||||
xce_reply(ep, c->myhost, &reply);
|
xce_reply(ep, c->myhost, &reply);
|
||||||
|
/* Reset heartbeat watchdog when nop arrives on the primary channel */
|
||||||
|
if (ep->fd == c->ha.primary_ctrl_fd)
|
||||||
|
ha_on_heartbeat_received(&c->ha);
|
||||||
break;
|
break;
|
||||||
case OPCode_join:
|
case OPCode_join:
|
||||||
rc = handle_join(c, ep, &request);
|
rc = handle_join(c, ep, &request);
|
||||||
|
|
@ -798,6 +806,18 @@ int cluster_init_phase1(cluster_t* c)
|
||||||
pcfg->candidates_count, my_priority, c->myconf->name);
|
pcfg->candidates_count, my_priority, c->myconf->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Req 2.4: reject misconfigured heartbeat timing before ha_init() */
|
||||||
|
{
|
||||||
|
char hb_err[256];
|
||||||
|
if (validate_heartbeat_config(ha_cfg.heartbeat_timeout_ms,
|
||||||
|
ha_cfg.heartbeat_interval_ms,
|
||||||
|
hb_err, sizeof(hb_err)) != 0) {
|
||||||
|
ERROR("startup validation failed: %s", hb_err);
|
||||||
|
fprintf(stderr, "xce_controller: %s\n", hb_err);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rc = ha_init(&c->ha, &ha_cfg, my_priority);
|
rc = ha_init(&c->ha, &ha_cfg, my_priority);
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
ERROR("ha_init failed: %d", rc);
|
ERROR("ha_init failed: %d", rc);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
/* startup_validation.c — Startup pre-flight validation functions.
|
/* startup_validation.c — Startup pre-flight validation functions.
|
||||||
*
|
*
|
||||||
* Implements validate_vcpus(), validate_startup_config(),
|
* Implements validate_vcpus(), validate_startup_config(),
|
||||||
* validate_peer_count(), and validate_cert_files() for use in main.c
|
* validate_peer_count(), validate_cert_files(), and
|
||||||
* and the controller unit test suite.
|
* validate_heartbeat_config() for use in main.c and the controller
|
||||||
|
* unit test suite.
|
||||||
*
|
*
|
||||||
* Requirements: 4.1 (peer count), 4.2 (cert pre-flight),
|
* Requirements: 4.1 (peer count), 4.2 (cert pre-flight),
|
||||||
* 4.3 (vcpus), 4.4 (vcpus key deprecation warn)
|
* 4.3 (vcpus), 4.4 (vcpus key deprecation warn),
|
||||||
|
* 2.4 (heartbeat timing validation)
|
||||||
*/
|
*/
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
@ -123,3 +126,43 @@ int validate_cert_files(const char *cert_path, const char *key_path,
|
||||||
|
|
||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,62 @@ static void test_nonblocking_election_returns_in_under_10ms(void)
|
||||||
elapsed_ms_val);
|
elapsed_ms_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Req 2.4: heartbeat timing validation at startup ─────────────────────── */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* validate_heartbeat_config: Req 2.4 — startup validation of heartbeat
|
||||||
|
* timing relationship.
|
||||||
|
*
|
||||||
|
* Returns 0 when heartbeat_timeout_ms >= 2 × heartbeat_interval_ms.
|
||||||
|
* Returns -EINVAL when heartbeat_timeout_ms < 2 × heartbeat_interval_ms;
|
||||||
|
* writes human-readable error into err_buf.
|
||||||
|
*/
|
||||||
|
int validate_heartbeat_config(uint32_t heartbeat_timeout_ms,
|
||||||
|
uint32_t heartbeat_interval_ms,
|
||||||
|
char *err_buf, size_t err_len);
|
||||||
|
|
||||||
|
static void test_heartbeat_config_valid_exact_double(void)
|
||||||
|
{
|
||||||
|
/* timeout == 2 × interval — exactly at the boundary; must return 0. */
|
||||||
|
char err_buf[256] = {0};
|
||||||
|
int rc = validate_heartbeat_config(6000, 3000, err_buf, sizeof(err_buf));
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
/* No error message on success */
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, err_buf[0]);
|
||||||
|
printf("test_heartbeat_config_valid_exact_double: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_heartbeat_config_valid_above_double(void)
|
||||||
|
{
|
||||||
|
/* timeout > 2 × interval — clearly valid (3× ratio) */
|
||||||
|
char err_buf[256] = {0};
|
||||||
|
int rc = validate_heartbeat_config(9000, 3000, err_buf, sizeof(err_buf));
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
printf("test_heartbeat_config_valid_above_double: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_heartbeat_config_invalid_equal_interval(void)
|
||||||
|
{
|
||||||
|
/* timeout == interval — below the 2× floor; must reject with -EINVAL.
|
||||||
|
* Req 2.4: heartbeat_timeout_ms >= 2 × heartbeat_interval_ms */
|
||||||
|
char err_buf[256] = {0};
|
||||||
|
int rc = validate_heartbeat_config(3000, 3000, err_buf, sizeof(err_buf));
|
||||||
|
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||||
|
/* err_buf must contain a human-readable message */
|
||||||
|
TEST_ASSERT_GREATER_THAN(0, (int)strlen(err_buf));
|
||||||
|
printf("test_heartbeat_config_invalid_equal_interval: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_heartbeat_config_invalid_less_than_double(void)
|
||||||
|
{
|
||||||
|
/* timeout = 1.5 × interval — also below the floor */
|
||||||
|
char err_buf[256] = {0};
|
||||||
|
int rc = validate_heartbeat_config(4500, 3000, err_buf, sizeof(err_buf));
|
||||||
|
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||||
|
TEST_ASSERT_GREATER_THAN(0, (int)strlen(err_buf));
|
||||||
|
printf("test_heartbeat_config_invalid_less_than_double: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Test suite entry point ───────────────────────────────────────────── */
|
/* ── Test suite entry point ───────────────────────────────────────────── */
|
||||||
|
|
||||||
void test_ha_startup(void)
|
void test_ha_startup(void)
|
||||||
|
|
@ -765,4 +821,10 @@ void test_ha_startup(void)
|
||||||
|
|
||||||
/* Task 1.1 (ha-heartbeat-liveness) — ha_init zero-initializes last_activity */
|
/* Task 1.1 (ha-heartbeat-liveness) — ha_init zero-initializes last_activity */
|
||||||
RUN_TEST(test_ha_init_last_activity_is_zero);
|
RUN_TEST(test_ha_init_last_activity_is_zero);
|
||||||
|
|
||||||
|
/* Req 2.4 — heartbeat timing validation at startup */
|
||||||
|
RUN_TEST(test_heartbeat_config_valid_exact_double);
|
||||||
|
RUN_TEST(test_heartbeat_config_valid_above_double);
|
||||||
|
RUN_TEST(test_heartbeat_config_invalid_equal_interval);
|
||||||
|
RUN_TEST(test_heartbeat_config_invalid_less_than_double);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue