Files
vatrog-vm-signaling/src/test/test_discovery.c
T

199 lines
8.0 KiB
C
Raw Normal View History

/* test_discovery.c — discovery state machine (WS3), driven deterministically via the TEST
* hooks (no inotify/timer/threads). A fake host-probe controls config/live verdicts; a
* recording sink captures attach/detach; a CAP_ROSTER subscriber captures the published
* roster. Covers: appear->attach(slot+roster), duplicate, gone->detach(roster+free), bit
* reuse, config-fail drop, stale drop, and the retry-then-attach path. */
#define _GNU_SOURCE
#include "vmsig.h"
#include "vmsig_roster.h"
#include "discovery.h" /* pulls host_probe.h */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
static int g_fail = 0;
#define CHECK(cond, msg) do { if (!(cond)) { printf(" FAIL: %s\n", (msg)); g_fail = 1; } } while (0)
/* ---- fake host-probe ---- */
typedef struct { int config_ok; int live_mode; int live_calls; } fakeprobe;
/* live_mode: 0=ok, 1=stale(dead, no retry), 2=retry-once-then-ok */
static int fp_config(const vmsig_host_probe* p, uint32_t vmid, vmsig_host_facts* out) {
fakeprobe* f = p->ud;
memset(out, 0, sizeof *out);
out->vmid = vmid;
snprintf(out->name, sizeof out->name, "win-%u", vmid);
snprintf(out->ram_path, sizeof out->ram_path, "/tmp/vm-%u-ram", vmid);
snprintf(out->qmp_path, sizeof out->qmp_path, "/tmp/%u.qmp", vmid);
out->cfg_ram_bytes = 4ull << 30;
out->share_on = f->config_ok;
out->ok = f->config_ok;
return 0;
}
static int fp_live(const vmsig_host_probe* p, vmsig_host_facts* io) {
fakeprobe* f = p->ud;
io->retry = 0;
f->live_calls++;
if (f->live_mode == 1) { io->ok = 0; io->vm_state = VMSIG_VM_SHUTDOWN; return 0; }
if (f->live_mode == 2 && f->live_calls == 1) { io->retry = 1; io->ok = 0; return 0; }
io->ok = 1; io->vm_state = VMSIG_VM_RUNNING; io->low = 0x80000000ull;
return 0;
}
/* ---- recording sink ---- */
typedef struct {
int n_attach, n_detach;
uint32_t la_vmid, la_ep, ld_vmid, ld_ep;
} recsink;
static int rs_attach(void* ud, vmsig_core* core, uint32_t vmid, uint32_t ep,
const vmsig_host_facts* f) {
(void)core; (void)f;
recsink* s = ud; s->n_attach++; s->la_vmid = vmid; s->la_ep = ep;
return 0;
}
static void rs_detach(void* ud, vmsig_core* core, uint32_t vmid, uint32_t ep) {
(void)core;
recsink* s = ud; s->n_detach++; s->ld_vmid = vmid; s->ld_ep = ep;
}
/* ---- roster subscriber ---- */
typedef struct { int attach, detach; uint32_t last_vmid, last_ep, last_action; char last_name[32]; } robs;
static int rob_on_ev(void* u, const vmsig_event* ev) {
robs* r = u;
if (ev->kind != VMSIG_EV_ROSTER) return 0;
const vmsig_roster* e = (const vmsig_roster*)ev->inln;
r->last_vmid = e->vmid; r->last_ep = ev->endpoint; r->last_action = e->action;
snprintf(r->last_name, sizeof r->last_name, "%s", e->name);
if (e->action == VMSIG_ROSTER_ATTACH) r->attach++;
else if (e->action == VMSIG_ROSTER_DETACH) r->detach++;
return 0;
}
static void test_discovery(void) {
printf("test_discovery\n");
vmsig_ctx* ctx = vmsig_ctx_new();
vmsig_core* core = vmsig_core_new(ctx);
robs ro; memset(&ro, 0, sizeof ro);
vmsig_inproc_cfg cfg; memset(&cfg, 0, sizeof cfg);
cfg.on_event = rob_on_ev; cfg.user = &ro;
void* ctl = vmsig_inproc_control_new(&cfg);
vmsig_grant g; memset(&g, 0, sizeof g);
g.principal = 1; g.endpoint_mask = ~0ull; g.source_mask = 0xFFFFFFFFu; g.cap_mask = VMSIG_CAP_ROSTER;
vmsig_core_add_control(core, vmsig_inproc_control_ops(), ctl, &g);
fakeprobe fp; memset(&fp, 0, sizeof fp); fp.config_ok = 1; fp.live_mode = 0;
vmsig_host_probe probe = { fp_config, fp_live, &fp };
recsink rs; memset(&rs, 0, sizeof rs);
vmsig_discovery_sink sink = { rs_attach, rs_detach, &rs };
char dir[] = "/tmp/vmsig_disc.XXXXXX";
CHECK(mkdtemp(dir) != NULL, "temp watch dir created");
vmsig_discovery* d = vmsig_discovery_new(core, dir, NULL, NULL, NULL, &probe, &sink);
CHECK(d != NULL, "discovery created");
/* 1) appear 101 -> attach ep0 + roster ATTACH */
vmsig_discovery_feed(d, 101, 1);
CHECK(rs.n_attach == 1 && rs.la_vmid == 101 && rs.la_ep == 0, "101 attached on ep0 (sink)");
CHECK(ro.attach == 1 && ro.last_vmid == 101 && ro.last_ep == 0 &&
ro.last_action == VMSIG_ROSTER_ATTACH, "roster ATTACH 101 ep0");
CHECK(strcmp(ro.last_name, "win-101") == 0, "roster carried the VM name");
CHECK(vmsig_discovery_slot_of_vmid(d, 101) == 0, "slot_of_vmid(101)==0");
/* 2) appear 102 -> ep1 */
vmsig_discovery_feed(d, 102, 1);
CHECK(rs.n_attach == 2 && rs.la_vmid == 102 && rs.la_ep == 1, "102 attached on ep1");
/* duplicate appear 101 -> ignored */
vmsig_discovery_feed(d, 101, 1);
CHECK(rs.n_attach == 2, "duplicate appear ignored");
/* 3) gone 101 -> detach + roster DETACH + slot freed */
vmsig_discovery_feed(d, 101, 0);
CHECK(rs.n_detach == 1 && rs.ld_vmid == 101 && rs.ld_ep == 0, "101 detached (sink)");
CHECK(ro.detach == 1 && ro.last_action == VMSIG_ROSTER_DETACH && ro.last_vmid == 101,
"roster DETACH 101");
CHECK(vmsig_discovery_slot_of_vmid(d, 101) == -1, "slot freed after detach");
/* 4) appear 103 -> reuse freed ep0 */
vmsig_discovery_feed(d, 103, 1);
CHECK(rs.la_ep == 0 && rs.la_vmid == 103, "103 reuses freed ep0 (lowest free)");
/* 5) config-fail -> drop */
fp.config_ok = 0;
int n = rs.n_attach;
vmsig_discovery_feed(d, 999, 1);
CHECK(rs.n_attach == n, "config-fail vmid dropped (no attach)");
fp.config_ok = 1;
/* 6) stale (file present, VM dead) -> drop */
fp.live_mode = 1;
n = rs.n_attach;
vmsig_discovery_feed(d, 105, 1);
CHECK(rs.n_attach == n, "stale VM dropped (no attach)");
fp.live_mode = 0;
/* 7) retry-then-ok: first probe retries, tick re-probes and attaches */
fp.live_mode = 2; fp.live_calls = 0;
n = rs.n_attach;
vmsig_discovery_feed(d, 104, 1);
CHECK(rs.n_attach == n, "retry: not attached on first probe");
CHECK(vmsig_discovery_slot_of_vmid(d, 104) == -1, "retry: no slot yet");
vmsig_discovery_tick(d);
CHECK(rs.n_attach == n + 1 && rs.la_vmid == 104, "retry: attached after re-probe");
vmsig_core_free(core);
vmsig_ctx_free(ctx);
rmdir(dir);
}
/* Bootstrap path: files already present when discovery starts are picked up by the REAL
* readdir + parse_vmid scan (not the test feed hook); junk names are ignored. */
static void touch(const char* dir, const char* name) {
char path[512];
snprintf(path, sizeof path, "%s/%s", dir, name);
int fd = open(path, O_CREAT | O_WRONLY | O_CLOEXEC, 0600);
if (fd >= 0) close(fd);
}
static void rm(const char* dir, const char* name) {
char path[512];
snprintf(path, sizeof path, "%s/%s", dir, name);
unlink(path);
}
static void test_bootstrap(void) {
printf("test_bootstrap\n");
vmsig_ctx* ctx = vmsig_ctx_new();
vmsig_core* core = vmsig_core_new(ctx);
fakeprobe fp; memset(&fp, 0, sizeof fp); fp.config_ok = 1; fp.live_mode = 0;
vmsig_host_probe probe = { fp_config, fp_live, &fp };
recsink rs; memset(&rs, 0, sizeof rs);
vmsig_discovery_sink sink = { rs_attach, rs_detach, &rs };
char dir[] = "/tmp/vmsig_boot.XXXXXX";
CHECK(mkdtemp(dir) != NULL, "temp dir");
touch(dir, "vm-200-ram"); /* valid trigger */
touch(dir, "notavm"); /* ignored */
touch(dir, "vm-bad-ram"); /* non-numeric => ignored */
vmsig_discovery* d = vmsig_discovery_new(core, dir, NULL, NULL, NULL, &probe, &sink);
CHECK(d != NULL, "discovery created");
CHECK(rs.n_attach == 1 && rs.la_vmid == 200, "bootstrap scan attached ONLY vm-200 (real parse)");
CHECK(vmsig_discovery_slot_of_vmid(d, 200) == 0, "200 pinned to ep0 via bootstrap");
vmsig_core_free(core);
vmsig_ctx_free(ctx);
rm(dir, "vm-200-ram"); rm(dir, "notavm"); rm(dir, "vm-bad-ram"); rmdir(dir);
}
int main(void) {
test_discovery();
test_bootstrap();
printf("discovery tests: %s\n", g_fail ? "FAIL" : "PASS");
return g_fail ? 1 : 0;
}