Files
pve-confsync/internal/store/nodes_test.go
T
cedricandClaude Opus 5.5 1e667ce8b9 Réplication de nœuds : réseau et DNS par l'API, multipath, chrony, sysctl par SSH
Deuxième mode de réplication, séparé de la réplication cluster vers cluster
(sélecteur « Clusters (API) | Nœuds (fichiers) » en haut de page) : un profil
réplique la configuration système d'un nœud maître vers des nœuds cibles.

- réseau par /nodes/{node}/network (interfaces.new puis rechargement), adresses
  et passerelles propres à chaque nœud, mapping des noms d'interfaces par cible ;
  DNS par /nodes/{node}/dns ; multipath.conf, wwids (ajout seul), chrony,
  sysctl.d, interfaces.d (hors sdn) par SSH
- découverte des nœuds (/cluster/status), accès SSH par nœud (mot de passe du
  cluster ou propre), clé d'hôte épinglée, contrôle du nom du nœud
- dry-run avec diff des fichiers ; apply (admin) avec contrôle SHA-256,
  sauvegarde et restore.sh sur le nœud, écriture atomique, rechargements,
  restauration si un rechargement échoue, timer systemd de restauration si le
  nœud est injoignable après le rechargement du réseau, confirmation tapée
- vue Nœuds et profils, journal séparé par mode, démo avec nœuds simulés

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 20:29:55 +02:00

67 lines
2.4 KiB
Go

package store
import (
"context"
"path/filepath"
"testing"
"pve-confsync/internal/nodesync"
)
func TestNodesAndProfiles(t *testing.T) {
ctx := context.Background()
st, err := Open(filepath.Join(t.TempDir(), "t.db"), testKey(1))
if err != nil {
t.Fatal(err)
}
if err := st.SaveCluster(ctx, "", Cluster{Name: "lab", Role: ClusterPrimary, URL: "https://lab:8006", Password: "cluster-pass"}); err != nil {
t.Fatal(err)
}
if err := st.SetNodes(ctx, "lab", []Discovered{{"pve1", "10.0.0.11"}, {"pve2", "10.0.0.12"}}); err != nil {
t.Fatal(err)
}
if err := st.PinNode(ctx, "lab", "pve2", "ssh-ed25519 AAAA", nil); err != nil {
t.Fatal(err)
}
// the cluster password by default, the node's own one once set
a, err := st.NodeAccessOf(ctx, "lab", "pve2")
if err != nil || a.SSH.Password != "cluster-pass" || a.SSH.Host != "10.0.0.12" || a.SSH.HostKey == "" {
t.Fatalf("%+v %v", a.SSH, err)
}
if err := st.SaveNode(ctx, "lab", "pve2", "192.168.0.12", 22, "own-pass", false); err != nil {
t.Fatal(err)
}
a, _ = st.NodeAccessOf(ctx, "lab", "pve2")
if a.SSH.Password != "own-pass" || a.SSH.Host != "192.168.0.12" || a.SSH.HostKey != "" {
t.Fatalf("new address must forget the host key: %+v", a.SSH)
}
// a node gone from the cluster is removed
if err := st.SetNodes(ctx, "lab", []Discovered{{"pve1", "10.0.0.11"}}); err != nil {
t.Fatal(err)
}
if ns, _ := st.Nodes(ctx); len(ns) != 1 || ns[0].Key != "lab/pve1" {
t.Fatalf("nodes: %+v", ns)
}
p := nodesync.Profile{Name: "net", Master: "lab/pve1", Targets: []string{"lab/pve2"}, Items: []string{"network"},
Mapping: map[string]nodesync.TargetMap{"lab/pve2": {NICs: map[string]string{"eno1": "enp1s0"}}}}
if err := st.SaveProfile(ctx, "", p); err != nil {
t.Fatal(err)
}
if err := st.SaveProfile(ctx, "", nodesync.Profile{Name: "bad", Master: "lab/pve1", Targets: []string{"lab/pve1"}, Items: []string{"network"}}); err == nil {
t.Error("master as target accepted")
}
// renaming the cluster follows in the nodes and the profiles
c, _, _ := st.Cluster(ctx, "lab")
c.Name = "rennes"
if err := st.SaveCluster(ctx, "lab", c); err != nil {
t.Fatal(err)
}
got, err := st.Profile(ctx, "net")
if err != nil || got.Master != "rennes/pve1" || got.Targets[0] != "rennes/pve2" || got.Mapping["rennes/pve2"].NICs["eno1"] != "enp1s0" {
t.Fatalf("profile after rename: %+v %v", got, err)
}
if ns, _ := st.Nodes(ctx); len(ns) != 1 || ns[0].Key != "rennes/pve1" {
t.Fatalf("nodes after rename: %+v", ns)
}
}