f-29: watchdog: add checksubnet #29

Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
GnomeZworc 2026-08-17 23:28:32 +02:00
commit 0fa84b8320
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632
7 changed files with 497 additions and 2 deletions

View file

@ -0,0 +1,134 @@
package watchdog
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"git.g3e.fr/syonad/two/internal/dhcp"
"git.g3e.fr/syonad/two/internal/netns"
"git.g3e.fr/syonad/two/internal/state"
"git.g3e.fr/syonad/two/internal/watchdog/notify"
"git.g3e.fr/syonad/two/pkg/db/kv"
"github.com/dgraph-io/badger/v4"
)
const (
modeVxlan = "vxlan"
modeBridge = "bridge"
)
func subnetIfaceNames(subnetName string) (hostVeth, nsVeth, bridge string, err error) {
parts := strings.SplitN(subnetName, "-", 2)
if len(parts) < 2 || parts[1] == "" {
return "", "", "", fmt.Errorf("nom de subnet %q sans identifiant après le tiret, interfaces indéductibles", subnetName)
}
id := parts[1]
return "v-" + id + "-e", "v-" + id + "-i", "br-" + id, nil
}
func dnsmasqName(vpc, bridge string) string {
return vpc + "_" + bridge
}
func CheckSubnets(db *badger.DB, u unitChecker, n notify.Notifier) error {
pairs, err := kv.ListByPrefix(db, prefixSubnet)
if err != nil {
return fmt.Errorf("watchdog: lecture des subnets: %w", err)
}
for _, name := range resourceNames(pairs, prefixSubnet) {
st, err := state.Get(db, prefixSubnet+name)
if err != nil {
n.Notify(kindSubnet, name, fmt.Sprintf("état illisible en base: %v", err))
continue
}
if st != state.Running {
continue
}
checkSubnet(db, name, u, n)
}
return nil
}
func checkSubnet(db *badger.DB, name string, u unitChecker, n notify.Notifier) {
hostVeth, nsVeth, bridge, err := subnetIfaceNames(name)
if err != nil {
n.Notify(kindSubnet, name, err.Error())
return
}
vpc, err := kv.GetFromDB(db, prefixSubnet+name+"/vpc")
if err != nil {
n.Notify(kindSubnet, name, fmt.Sprintf("vpc illisible en base: %v", err))
return
}
mode, err := kv.GetFromDB(db, prefixSubnet+name+"/mode")
if err != nil {
n.Notify(kindSubnet, name, fmt.Sprintf("mode illisible en base: %v", err))
return
}
if p := linkProblem(hostVeth); p != "" {
n.Notify(kindSubnet, name, p)
}
switch mode {
case modeVxlan:
if p := linkProblem(bridge); p != "" {
n.Notify(kindSubnet, name, p+" (host)")
}
checkVxlanIface(db, name, n)
case modeBridge:
default:
n.Notify(kindSubnet, name, fmt.Sprintf("mode inconnu %q", mode))
}
checkSubnetNetns(name, vpc, nsVeth, bridge, n)
dnsName := dnsmasqName(vpc, bridge)
conf := filepath.Join(dhcp.DefaultConfDir, dnsName+".conf")
if _, err := os.Stat(conf); err != nil {
n.Notify(kindSubnet, name, fmt.Sprintf("config dnsmasq absente (%s): %v", conf, err))
}
checkUnit(kindSubnet, name, "dnsmasq@"+dnsName+".service", u, n)
}
func checkVxlanIface(db *badger.DB, name string, n notify.Notifier) {
raw, err := kv.GetFromDB(db, prefixSubnet+name+"/vxlan_id")
if err != nil {
n.Notify(kindSubnet, name, fmt.Sprintf("vxlan_id illisible en base: %v", err))
return
}
id, err := strconv.Atoi(raw)
if err != nil {
n.Notify(kindSubnet, name, fmt.Sprintf("vxlan_id invalide %q: %v", raw, err))
return
}
if p := linkProblem(fmt.Sprintf("vxlan-%d", id)); p != "" {
n.Notify(kindSubnet, name, p)
}
}
func checkSubnetNetns(name, vpc, nsVeth, bridge string, n notify.Notifier) {
if !netns.Exist(vpc) {
n.Notify(kindSubnet, name, "netns "+vpc+" absent (/var/run/netns/"+vpc+")")
return
}
if err := netns.Call(vpc, func() error {
for _, iface := range []string{nsVeth, bridge} {
if p := linkProblem(iface); p != "" {
n.Notify(kindSubnet, name, p+" (dans le netns "+vpc+")")
}
}
return nil
}); err != nil {
n.Notify(kindSubnet, name, fmt.Sprintf("entrée dans le netns %s impossible: %v", vpc, err))
}
}