package watchdog import ( "fmt" "strconv" "strings" configuration "git.g3e.fr/syonad/two/internal/config/agent" "git.g3e.fr/syonad/two/internal/dhcpbackend" "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("subnet name %q has no identifier after the dash, interface names cannot be derived", subnetName) } id := parts[1] return "v-" + id + "-e", "v-" + id + "-i", "br-" + id, nil } func CheckSubnets(db *badger.DB, cfg *configuration.Config, u unitChecker, n notify.Notifier) error { pairs, err := kv.ListByPrefix(db, prefixSubnet) if err != nil { return fmt.Errorf("watchdog: listing subnets: %w", err) } backend, backendErr := dhcpbackend.New(cfg) for _, name := range resourceNames(pairs, prefixSubnet) { st, err := state.Get(db, prefixSubnet+name) if err != nil { n.Notify(kindSubnet, name, fmt.Sprintf("state unreadable in database: %v", err)) continue } if st != state.Running { continue } checkSubnet(db, name, backend, u, n) if backendErr != nil { n.Notify(kindSubnet, name, fmt.Sprintf("dhcp checks skipped, backend unusable: %v", backendErr)) } } return nil } func checkSubnet(db *badger.DB, name string, backend dhcpbackend.Backend, 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 unreadable in database: %v", err)) return } mode, err := kv.GetFromDB(db, prefixSubnet+name+"/mode") if err != nil { n.Notify(kindSubnet, name, fmt.Sprintf("mode unreadable in database: %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("unknown mode %q", mode)) } checkSubnetNetns(name, vpc, nsVeth, bridge, n) checkDHCP(db, name, dhcpbackend.Subnet{Name: name, VPC: vpc, Bridge: bridge}, backend, 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 unreadable in database: %v", err)) return } id, err := strconv.Atoi(raw) if err != nil { n.Notify(kindSubnet, name, fmt.Sprintf("invalid vxlan_id %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+" missing (/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+" (in netns "+vpc+")") } } return nil }); err != nil { n.Notify(kindSubnet, name, fmt.Sprintf("cannot enter netns %s: %v", vpc, err)) } }