Minimal-Slice für Phase-3-Cluster: * internal/cluster/node_id.go — stable UUID 'n-<16hex>' in /var/lib/edgeguard/node-id, idempotent über reboots. * internal/cluster/store.go — ha_nodes-Repo (List/Get/UpsertSelf) via pgxpool. EnsureSelfRegistered upsertet die lokale Row beim Boot mit FQDN aus setup.json. * internal/handlers/cluster.go — GET /api/v1/cluster/nodes liefert alle ha_nodes plus local_id (für UI-Highlighting). * main.go: nach DB-Pool-Open wird EnsureSelfRegistered (nur wenn setup.completed) ausgeführt, ClusterHandler registriert. * management-ui/src/pages/Cluster/index.tsx — Tabelle mit Node-ID, FQDN, Rolle, Beitrittszeit; eigene Node mit "diese Node"-Tag markiert. Sidebar-Eintrag + i18n de/en. Bewusst NICHT in dieser Runde: cluster-init/cluster-join CLIs, KeyDB Active-Active config-gen, PG streaming replication, mTLS zwischen Peers, License-Leader-Election. Diese kommen mit dem ersten echten Multi-Node-Test (Phase 3.1) — sonst Code ohne Smoke-Möglichkeit. End-to-end-Smoke: setup → restart → ha_nodes hat 1 Row mit fqdn=eg.example.com, /cluster/nodes liefert sie korrekt mit local_id-Markierung. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
49 lines
1.0 KiB
Go
49 lines
1.0 KiB
Go
package cluster
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestEnsureNodeID_GeneratesAndPersists(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "node-id")
|
|
|
|
id1, err := EnsureNodeID(path)
|
|
if err != nil {
|
|
t.Fatalf("first call: %v", err)
|
|
}
|
|
if !validNodeID(id1) {
|
|
t.Fatalf("invalid node id minted: %q", id1)
|
|
}
|
|
|
|
id2, err := EnsureNodeID(path)
|
|
if err != nil {
|
|
t.Fatalf("second call: %v", err)
|
|
}
|
|
if id1 != id2 {
|
|
t.Errorf("node id should be stable: %q vs %q", id1, id2)
|
|
}
|
|
}
|
|
|
|
func TestEnsureNodeID_RejectsCorruptFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "node-id")
|
|
if err := os.WriteFile(path, []byte("not a real id\n"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
id, err := EnsureNodeID(path)
|
|
if err != nil {
|
|
t.Fatalf("EnsureNodeID: %v", err)
|
|
}
|
|
if !validNodeID(id) {
|
|
t.Errorf("expected fresh id when file was junk, got %q", id)
|
|
}
|
|
// Re-read should now match the regenerated id.
|
|
id2, _ := EnsureNodeID(path)
|
|
if id != id2 {
|
|
t.Errorf("regenerated id not persisted: %q vs %q", id, id2)
|
|
}
|
|
}
|