Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't BGP advertise OVN load-balancers when all backends are offline #1376

Merged
merged 7 commits into from
Nov 15, 2024
9 changes: 9 additions & 0 deletions internal/server/bgp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ func (s *Server) AddPrefix(subnet net.IPNet, nexthop net.IP, owner string) error
}

func (s *Server) addPrefix(subnet net.IPNet, nexthop net.IP, owner string) error {
// Check for an existing entry.
for _, path := range s.paths {
if path.owner != owner || path.prefix.String() != subnet.String() || path.nexthop.String() != nexthop.String() {
continue
}

return nil
}

// Prepare the prefix.
prefixLen, _ := subnet.Mask.Size()
prefix := subnet.IP.String()
Expand Down
83 changes: 2 additions & 81 deletions internal/server/network/driver_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,8 +640,8 @@ func (n *common) bgpSetup(oldConfig map[string]string) error {
currentPeers := n.bgpGetPeers(n.config)
oldPeers := n.bgpGetPeers(oldConfig)

// Don't set up BGP when no peers are configured.
if len(currentPeers) == 0 {
// Don't set up BGP on non-OVN networks when no peers are configured.
if n.netType != "ovn" && len(currentPeers) == 0 {
if len(oldPeers) > 0 {
return n.bgpClear(oldConfig)
}
Expand All @@ -666,11 +666,6 @@ func (n *common) bgpSetup(oldConfig map[string]string) error {
return fmt.Errorf("Failed applying BGP prefixes for address forwards: %w", err)
}

err = n.loadBalancerBGPSetupPrefixes()
if err != nil {
return fmt.Errorf("Failed applying BGP prefixes for load balancers: %w", err)
}

return nil
}

Expand Down Expand Up @@ -1416,80 +1411,6 @@ func (n *common) LoadBalancerDelete(listenAddress string, clientType request.Cli
return ErrNotImplemented
}

// loadBalancerBGPSetupPrefixes exports external load balancer addresses as prefixes.
func (n *common) loadBalancerBGPSetupPrefixes() error {
var listenAddresses map[int64]string

err := n.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
var err error

// Retrieve network forwards before clearing existing prefixes, and separate them by IP family.
listenAddresses, err = tx.GetNetworkLoadBalancerListenAddresses(ctx, n.ID(), true)

return err
})
if err != nil {
return fmt.Errorf("Failed loading network forwards: %w", err)
}

listenAddressesByFamily := map[uint][]string{
4: make([]string, 0),
6: make([]string, 0),
}

for _, listenAddress := range listenAddresses {
if strings.Contains(listenAddress, ":") {
listenAddressesByFamily[6] = append(listenAddressesByFamily[6], listenAddress)
} else {
listenAddressesByFamily[4] = append(listenAddressesByFamily[4], listenAddress)
}
}

// Use load balancer specific owner string (different from the network prefixes) so that these can be
// reapplied independently of the network's own prefixes.
bgpOwner := fmt.Sprintf("network_%d_load_balancer", n.id)

// Clear existing address load balancer prefixes for network.
err = n.state.BGP.RemovePrefixByOwner(bgpOwner)
if err != nil {
return err
}

// Add the new prefixes.
for _, ipVersion := range []uint{4, 6} {
nextHopAddr := n.bgpNextHopAddress(ipVersion)
natEnabled := util.IsTrue(n.config[fmt.Sprintf("ipv%d.nat", ipVersion)])
_, netSubnet, _ := net.ParseCIDR(n.config[fmt.Sprintf("ipv%d.address", ipVersion)])

routeSubnetSize := 128
if ipVersion == 4 {
routeSubnetSize = 32
}

// Export external forward listen addresses.
for _, listenAddress := range listenAddressesByFamily[ipVersion] {
listenAddr := net.ParseIP(listenAddress)

// Don't export internal address forwards (those inside the NAT enabled network's subnet).
if natEnabled && netSubnet != nil && netSubnet.Contains(listenAddr) {
continue
}

_, ipRouteSubnet, err := net.ParseCIDR(fmt.Sprintf("%s/%d", listenAddr.String(), routeSubnetSize))
if err != nil {
return err
}

err = n.state.BGP.AddPrefix(*ipRouteSubnet, nextHopAddr, bgpOwner)
if err != nil {
return err
}
}
}

return nil
}

// Leases returns ErrNotImplemented for drivers that don't support address leases.
func (n *common) Leases(projectName string, clientType request.ClientType) ([]api.NetworkLease, error) {
return nil, ErrNotImplemented
Expand Down
215 changes: 215 additions & 0 deletions internal/server/network/driver_ovn.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/flosch/pongo2"
"github.com/mdlayher/netx/eui64"
ovsClient "github.com/ovn-org/libovsdb/client"
ovsdbModel "github.com/ovn-org/libovsdb/model"

"github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/internal/iprange"
Expand All @@ -32,6 +33,7 @@ import (
"github.com/lxc/incus/v6/internal/server/locking"
"github.com/lxc/incus/v6/internal/server/network/acl"
networkOVN "github.com/lxc/incus/v6/internal/server/network/ovn"
ovnSB "github.com/lxc/incus/v6/internal/server/network/ovn/schema/ovn-sb"
"github.com/lxc/incus/v6/internal/server/network/ovs"
"github.com/lxc/incus/v6/internal/server/project"
"github.com/lxc/incus/v6/internal/server/state"
Expand Down Expand Up @@ -3135,6 +3137,111 @@ func (n *ovn) Start() error {
return err
}

err = n.loadBalancerBGPSetupPrefixes()
if err != nil {
return fmt.Errorf("Failed applying BGP prefixes for load balancers: %w", err)
}

// Setup event handler for monitored services.
handler := networkOVN.EventHandler{
Tables: []string{"Service_Monitor"},
Hook: func(action string, table string, oldObject ovsdbModel.Model, newObject ovsdbModel.Model) {
// Skip invalid notifications.
if oldObject == nil && newObject == nil {
return
}

// Get the object.
dbObject := newObject
if dbObject == nil {
dbObject = oldObject
}

srvStatus, ok := dbObject.(*ovnSB.ServiceMonitor)
if !ok {
return
}

// Check if this is our network.
if !strings.HasPrefix(srvStatus.LogicalPort, fmt.Sprintf("incus-net%d-instance-", n.id)) {
return
}

// Locate affected load-balancers.
lbs, err := n.ovnnb.GetLoadBalancersByStatusUpdate(context.TODO(), *srvStatus)
if err != nil {
return
}

for _, lb := range lbs {
// Check for status of all backends on this load-balancer.
online, err := n.ovnsb.CheckLoadBalancerOnline(context.TODO(), lb)
if err != nil {
return
}

// Parse the name.
fields := strings.Split(lb.Name, "-")
listenAddr := net.ParseIP(fields[3])
if listenAddr == nil {
return
}

// Check if we have a matching UDP load-balancer.
fields[4] = "udp"
lbUDP, _ := n.ovnnb.GetLoadBalancer(context.TODO(), networkOVN.OVNLoadBalancer(strings.Join(fields, "-")))
if lbUDP != nil {
// UDP backends can't be checked, so have to assume online.
online = true
}

// Prepare advertisement.
ipVersion := uint(4)
if listenAddr.To4() == nil {
ipVersion = 6
}

bgpOwner := fmt.Sprintf("network_%d_load_balancer", n.id)
nextHopAddr := n.bgpNextHopAddress(ipVersion)
natEnabled := util.IsTrue(n.config[fmt.Sprintf("ipv%d.nat", ipVersion)])
_, netSubnet, _ := net.ParseCIDR(n.config[fmt.Sprintf("ipv%d.address", ipVersion)])

routeSubnetSize := 128
if ipVersion == 4 {
routeSubnetSize = 32
}

// Don't export internal address forwards (those inside the NAT enabled network's subnet).
if natEnabled && netSubnet != nil && netSubnet.Contains(listenAddr) {
return
}

_, ipRouteSubnet, err := net.ParseCIDR(fmt.Sprintf("%s/%d", listenAddr.String(), routeSubnetSize))
if err != nil {
return
}

// Update the BGP state.
if online {
err = n.state.BGP.AddPrefix(*ipRouteSubnet, nextHopAddr, bgpOwner)
if err != nil {
return
}
} else {
err = n.state.BGP.RemovePrefix(*ipRouteSubnet, nextHopAddr)
if err != nil {
return
}
}
}
},
}

err = networkOVN.AddOVNSBHandler(fmt.Sprintf("network_%d", n.id), handler)
if err != nil {
return err
}

revert.Success()

// Ensure network is marked as available now its started.
Expand Down Expand Up @@ -3166,6 +3273,12 @@ func (n *ovn) Stop() error {
return err
}

// Clear event handler for monitored services.
err = networkOVN.RemoveOVNSBHandler(fmt.Sprintf("network_%d", n.id))
if err != nil {
return err
}

return nil
}

Expand Down Expand Up @@ -3480,6 +3593,11 @@ func (n *ovn) Update(newNetwork api.NetworkPut, targetNode string, clientType re
return err
}

err = n.loadBalancerBGPSetupPrefixes()
if err != nil {
return fmt.Errorf("Failed applying BGP prefixes for load balancers: %w", err)
}

revert.Success()
return nil
}
Expand Down Expand Up @@ -6454,3 +6572,100 @@ func (n *ovn) forPeers(f func(targetOVNNet *ovn) error) error {

return nil
}

// loadBalancerBGPSetupPrefixes exports external load balancer addresses as prefixes.
func (n *ovn) loadBalancerBGPSetupPrefixes() error {
var listenAddresses map[int64]string

err := n.state.DB.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
var err error

// Retrieve network forwards before clearing existing prefixes, and separate them by IP family.
listenAddresses, err = tx.GetNetworkLoadBalancerListenAddresses(ctx, n.ID(), true)

return err
})
if err != nil {
return fmt.Errorf("Failed loading network forwards: %w", err)
}

listenAddressesByFamily := map[uint][]string{
4: make([]string, 0),
6: make([]string, 0),
}

for _, listenAddress := range listenAddresses {
if strings.Contains(listenAddress, ":") {
listenAddressesByFamily[6] = append(listenAddressesByFamily[6], listenAddress)
} else {
listenAddressesByFamily[4] = append(listenAddressesByFamily[4], listenAddress)
}
}

// Use load balancer specific owner string (different from the network prefixes) so that these can be
// reapplied independently of the network's own prefixes.
bgpOwner := fmt.Sprintf("network_%d_load_balancer", n.id)

// Clear existing address load balancer prefixes for network.
err = n.state.BGP.RemovePrefixByOwner(bgpOwner)
if err != nil {
return err
}

// Add the new prefixes.
for _, ipVersion := range []uint{4, 6} {
nextHopAddr := n.bgpNextHopAddress(ipVersion)
natEnabled := util.IsTrue(n.config[fmt.Sprintf("ipv%d.nat", ipVersion)])
_, netSubnet, _ := net.ParseCIDR(n.config[fmt.Sprintf("ipv%d.address", ipVersion)])

routeSubnetSize := 128
if ipVersion == 4 {
routeSubnetSize = 32
}

// Export external forward listen addresses.
for _, listenAddress := range listenAddressesByFamily[ipVersion] {
listenAddr := net.ParseIP(listenAddress)

// Don't export internal address forwards (those inside the NAT enabled network's subnet).
if natEnabled && netSubnet != nil && netSubnet.Contains(listenAddr) {
continue
}

// Check health of load-balancer (if enabled).
online := false
for _, protocol := range []string{"tcp", "udp"} {
lb, err := n.ovnnb.GetLoadBalancer(context.TODO(), networkOVN.OVNLoadBalancer(fmt.Sprintf("incus-net%d-lb-%s-%s", n.id, listenAddr.String(), protocol)))
if err != nil {
continue
}

lbOnline, err := n.ovnsb.CheckLoadBalancerOnline(context.TODO(), *lb)
if err != nil {
continue
}

if lbOnline {
online = true
break
}
}

if !online {
continue
}

_, ipRouteSubnet, err := net.ParseCIDR(fmt.Sprintf("%s/%d", listenAddr.String(), routeSubnetSize))
if err != nil {
return err
}

err = n.state.BGP.AddPrefix(*ipRouteSubnet, nextHopAddr, bgpOwner)
if err != nil {
return err
}
}
}

return nil
}
Loading
Loading