forked from lei.chen/myxce
feat: add load_primary_config(), myconf_fini(); remove static cf global from myconf.c
- load_primary_config(): resolves conf/current, reads raw bytes, computes SHA-256 tag, decodes Cap'n Proto via load_cluster_config_from_file(), checks reserved names, sets config_generation, checks staleness - myconf_fini(): frees conffile and cluster_config (calls free_ClusterConfig) - Remove static cf global from myconf.c; get_default_conf() now uses a local struct cluster_configuration* via load_cluster_config() - Add load_primary_config_for_test() under XCE_TESTING in config_repl.c to test path resolution without triggering FATAL - Add xce/cluster_def.h include to config_repl.c and test_config_repl.c so cluster_t is complete when XCE_TESTING is active - TDD: test_load_primary_config_missing_returns_error passes (18/18)
This commit is contained in:
parent
ce62406f2c
commit
cd02cf40d2
4 changed files with 192 additions and 7 deletions
|
|
@ -19,6 +19,7 @@
|
|||
#include "byteorder.h"
|
||||
#include "clusterconfig.h"
|
||||
#include "utils.h"
|
||||
#include "xce/cluster_def.h"
|
||||
|
||||
/* ── internal helpers ──────────────────────────── */
|
||||
|
||||
|
|
@ -364,6 +365,29 @@ int config_contains_reserved_name(cluster_config_t *cfg)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* ── load_primary_config_for_test ───────────────── */
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
int load_primary_config_for_test(cluster_t *c)
|
||||
{
|
||||
myconf_t *myconf = c->myconf;
|
||||
char conf_dir[4096];
|
||||
snprintf(conf_dir, sizeof(conf_dir), "%s/%s", myconf->workdir, myconf->name);
|
||||
|
||||
char fname[256] = {0};
|
||||
int rc = conf_read_current_filename(conf_dir, fname, sizeof(fname));
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
free(myconf->conffile);
|
||||
myconf->conffile = NULL;
|
||||
if (asprintf(&myconf->conffile, "%s/conf/%s", conf_dir, fname) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ── sanitise_cn ────────────────────────────────── */
|
||||
|
||||
char *sanitise_cn(const char *src, size_t maxlen)
|
||||
|
|
|
|||
|
|
@ -90,3 +90,13 @@ int config_contains_reserved_name(cluster_config_t *cfg);
|
|||
* Returns pointer to the static buffer. NOT thread-safe (static storage).
|
||||
*/
|
||||
char *sanitise_cn(const char *src, size_t maxlen);
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/*
|
||||
* 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);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <tlog.h>
|
||||
|
||||
#include "byteorder.h"
|
||||
#include "config_repl.h"
|
||||
#include "configobj.h"
|
||||
#include "controller.capnp.h"
|
||||
#include "myconf.h"
|
||||
|
|
@ -131,12 +137,10 @@ int load_cluster_config(cluster_t* c,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct cluster_configuration* cf = NULL;
|
||||
|
||||
cluster_config_t* get_default_conf(void* cl)
|
||||
{
|
||||
cluster_config_t* c;
|
||||
cluster_t* cls = (cluster_t*)cl;
|
||||
struct cluster_configuration* cf = NULL;
|
||||
cluster_t* cls = (cluster_t*)cl;
|
||||
|
||||
load_cluster_config(cls, &cf);
|
||||
if (cf == NULL) {
|
||||
|
|
@ -144,7 +148,109 @@ cluster_config_t* get_default_conf(void* cl)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
c = cf->cluster;
|
||||
|
||||
return c;
|
||||
return cf->cluster;
|
||||
}
|
||||
|
||||
/* ── load_primary_config ────────────────────────── */
|
||||
|
||||
int load_primary_config(cluster_t *c)
|
||||
{
|
||||
myconf_t *myconf = c->myconf;
|
||||
|
||||
/* Build conf dir path: <workdir>/<name> */
|
||||
char conf_dir[4096];
|
||||
snprintf(conf_dir, sizeof(conf_dir), "%s/%s", myconf->workdir, myconf->name);
|
||||
|
||||
/* Resolve conf/current → versioned filename */
|
||||
char fname[256] = {0};
|
||||
int rc = conf_read_current_filename(conf_dir, fname, sizeof(fname));
|
||||
if (rc != 0) {
|
||||
FATAL("Primary has no config at '%s/conf/current' (rc=%d) — "
|
||||
"provision the config before starting",
|
||||
conf_dir, rc);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* Heap-allocate conffile so it survives past this stack frame */
|
||||
free(myconf->conffile);
|
||||
myconf->conffile = NULL;
|
||||
if (asprintf(&myconf->conffile, "%s/conf/%s", conf_dir, fname) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Read raw bytes and check size */
|
||||
uint8_t *bytes = NULL;
|
||||
size_t blen = 0;
|
||||
rc = conf_read_raw(myconf->conffile, &bytes, &blen);
|
||||
if (rc != 0) {
|
||||
FATAL("Primary config '%s' unreadable: %d", myconf->conffile, rc);
|
||||
free(myconf->conffile);
|
||||
myconf->conffile = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Compute and cache SHA-256 */
|
||||
SHA256(bytes, blen, myconf->config_sha256);
|
||||
uint64_t tag;
|
||||
memcpy(&tag, myconf->config_sha256, 8);
|
||||
myconf->config_version_tag = be64toh(tag);
|
||||
|
||||
/* Decode Cap'n Proto directly into myconf->cluster_config */
|
||||
rc = load_cluster_config_from_file(myconf->conffile, &myconf->cluster_config);
|
||||
if (rc != 0) {
|
||||
FATAL("Primary config '%s' failed Cap'n Proto decode: %d",
|
||||
myconf->conffile, rc);
|
||||
free(bytes);
|
||||
free(myconf->conffile);
|
||||
myconf->conffile = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (myconf->cluster_config == NULL) {
|
||||
FATAL("Primary config '%s' decoded to NULL", myconf->conffile);
|
||||
free(bytes);
|
||||
free(myconf->conffile);
|
||||
myconf->conffile = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
free(bytes);
|
||||
|
||||
fill_default_cluster_config(myconf->cluster_config);
|
||||
|
||||
/* Check for reserved lost_and_found name */
|
||||
if (config_contains_reserved_name(myconf->cluster_config)) {
|
||||
FATAL("Config '%s' contains reserved name 'lost_and_found'",
|
||||
myconf->conffile);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Default generation to 1 if not set */
|
||||
if (myconf->cluster_config->config_generation == 0)
|
||||
myconf->cluster_config->config_generation = 1;
|
||||
myconf->config_generation = myconf->cluster_config->config_generation;
|
||||
|
||||
/* Check staleness */
|
||||
myconf->conf_stale = conf_check_stale(conf_dir, myconf->conf_stale_timeout);
|
||||
if (myconf->conf_stale)
|
||||
WARN("Primary config appears stale (age > %lds)",
|
||||
(long)myconf->conf_stale_timeout);
|
||||
|
||||
INFO("Primary config loaded: tag=%016" PRIx64 " gen=%" PRIu64,
|
||||
myconf->config_version_tag, myconf->config_generation);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── myconf_fini ────────────────────────────────── */
|
||||
|
||||
void myconf_fini(myconf_t *myconf)
|
||||
{
|
||||
if (!myconf)
|
||||
return;
|
||||
free(myconf->conffile);
|
||||
myconf->conffile = NULL;
|
||||
if (myconf->cluster_config) {
|
||||
free_ClusterConfig(myconf->cluster_config);
|
||||
free(myconf->cluster_config);
|
||||
myconf->cluster_config = NULL;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "config_repl.h"
|
||||
#include "myconf.h"
|
||||
#include "xce/cluster_def.h"
|
||||
|
||||
/* Per-test directory helpers — not Unity setUp/tearDown (those are in
|
||||
* controller_test.c). Each test that needs a temp dir calls cr_setup()
|
||||
|
|
@ -24,6 +25,9 @@
|
|||
static char g_tmpdir[256];
|
||||
static char g_conf_dir[300];
|
||||
|
||||
/* tmpdir string for load_primary_config tests (no conf/current) */
|
||||
static char tmpdir[256];
|
||||
|
||||
static void cr_setup(void)
|
||||
{
|
||||
snprintf(g_tmpdir, sizeof(g_tmpdir), "/tmp/test_cr_%d_%ld",
|
||||
|
|
@ -367,6 +371,44 @@ static void test_sanitise_cn_truncated(void)
|
|||
printf("test_sanitise_cn_truncated: PASS\n");
|
||||
}
|
||||
|
||||
/* ── load_primary_config (via test-only helper) ── */
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
static void test_load_primary_config_missing_returns_error(void)
|
||||
{
|
||||
/* Create a minimal cluster_t with no conf/current file */
|
||||
cluster_t c;
|
||||
memset(&c, 0, sizeof(c));
|
||||
myconf_t myconf;
|
||||
memset(&myconf, 0, sizeof(myconf));
|
||||
|
||||
/* Use a fresh tmpdir with no conf/current */
|
||||
snprintf(tmpdir, sizeof(tmpdir), "/tmp/test_lpc_%d_%ld",
|
||||
(int)getpid(), (long)time(NULL));
|
||||
mkdir(tmpdir, 0700);
|
||||
|
||||
myconf.workdir = tmpdir;
|
||||
myconf.name = "ctrl0";
|
||||
myconf.conf_keep_n = 2;
|
||||
myconf.conf_stale_timeout = 86400;
|
||||
myconf.conffile = NULL;
|
||||
c.myconf = &myconf;
|
||||
|
||||
/* No conf/current exists — must fail (not FATAL in test context) */
|
||||
int rc = load_primary_config_for_test(&c);
|
||||
TEST_ASSERT_TRUE(rc < 0);
|
||||
/* conffile must still be NULL (no heap allocation on failure) */
|
||||
TEST_ASSERT_NULL(myconf.conffile);
|
||||
|
||||
/* Cleanup */
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", tmpdir);
|
||||
system(cmd);
|
||||
|
||||
printf("test_load_primary_config_missing_returns_error: PASS\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ── dispatch ───────────────────────────────────── */
|
||||
|
||||
void test_config_repl(void)
|
||||
|
|
@ -388,4 +430,7 @@ void test_config_repl(void)
|
|||
RUN_TEST(test_sanitise_cn_printable);
|
||||
RUN_TEST(test_sanitise_cn_control_chars);
|
||||
RUN_TEST(test_sanitise_cn_truncated);
|
||||
#ifdef XCE_TESTING
|
||||
RUN_TEST(test_load_primary_config_missing_returns_error);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue