128 lines
3.5 KiB
Go
128 lines
3.5 KiB
Go
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))
|
|
}
|
|
}
|