diff --git a/cmd/microcloud/main_init.go b/cmd/microcloud/main_init.go index 43a8a2543..331cf5f11 100644 --- a/cmd/microcloud/main_init.go +++ b/cmd/microcloud/main_init.go @@ -525,19 +525,83 @@ func (c *initConfig) validateSystems(s *service.Handler) (err error) { continue } - _, subnet, err := net.ParseCIDR(sys.MicroCephInternalNetworkSubnet) + ovnUnderlayIP := net.ParseIP(sys.OVNGeneveAddr) + if ovnUnderlayIP == nil { + return fmt.Errorf("OVN underlay IP %q is invalid", sys.OVNGeneveAddr) + } + + // Get the UPLINK network's gateway IP. This is used to check for interface collisions and print eventual warnings + // to the user. + var ovnUplinkIP net.IP + var err error + for _, network := range c.systems[s.Name].Networks { + if network.Type == "physical" && network.Name == service.DefaultUplinkNetwork { + if network.Config["ipv4.gateway"] != "" { + ovnUplinkIP, _, err = net.ParseCIDR(network.Config["ipv4.gateway"]) + if err != nil { + return fmt.Errorf("Failed to parse gateway IPv4 for UPLINK network %q: %w", service.DefaultUplinkNetwork, err) + } + } + + if ovnUplinkIP == nil && network.Config["ipv6.gateway"] != "" { + ovnUplinkIP, _, err = net.ParseCIDR(network.Config["ipv6.gateway"]) + if err != nil { + return fmt.Errorf("Failed to parse gateway IPv6 for UPLINK network %q: %w", service.DefaultUplinkNetwork, err) + } + } + + if ovnUplinkIP == nil { + return fmt.Errorf("Failed to find gateway IP for UPLINK network %q", service.DefaultUplinkNetwork) + } + + break + } + } + + internalCephIP, subnet, err := net.ParseCIDR(sys.MicroCephInternalNetworkSubnet) if err != nil { - return fmt.Errorf("Failed to parse available network interface CIDR address: %q: %w", subnet, err) + return fmt.Errorf("Failed to parse available network interface CIDR address for internal Ceph network: %q: %w", subnet, err) } - underlayIP := net.ParseIP(sys.OVNGeneveAddr) - if underlayIP == nil { - return fmt.Errorf("OVN underlay IP %q is invalid", sys.OVNGeneveAddr) + // Check for interface collisions + allInterfaces := map[string]*ipWithIface{ + "Ceph cluster network": {ifaceName: sys.MicroCephInternalNetworkIface, ip: internalCephIP}, + "OVN underlay": {ifaceName: sys.OVNGeneveIface, ip: ovnUnderlayIP}, + "OVN uplink": {ifaceName: sys.OVNUplinkIface, ip: ovnUplinkIP}, + } + + // The public Ceph network is not always present (partially disaggregated setup) but we still + // wish to check for interface collisions between OVN and the Ceph cluster network. + // If it is present though, add it to the list of interfaces to check for collisions. + if sys.MicroCephPublicNetworkSubnet != "" { + publicCephIP, subnet, err := net.ParseCIDR(sys.MicroCephPublicNetworkSubnet) + if err != nil { + return fmt.Errorf("Failed to parse available network interface CIDR address for public Ceph network: %q: %w", subnet, err) + } + + allInterfaces["Ceph public network"] = &ipWithIface{ifaceName: sys.MicroCephPublicNetworkIface, ip: publicCephIP} } - if subnet.Contains(underlayIP) { - fmt.Printf("Warning: OVN underlay IP (%s) is shared with the Ceph cluster network (%s)\n", underlayIP.String(), subnet.String()) + interfaceCollision := false + for netNameLeft, ipWithIfaceLeft := range allInterfaces { + for netNameRight, ipWithIfaceRight := range allInterfaces { + // Skip self-comparison and already processed pairs + // (e.g. if we already compared "A vs B", we don't need to compare "B vs A"). + if netNameLeft >= netNameRight { + continue + } + + if ipWithIfaceLeft.ifaceName == ipWithIfaceRight.ifaceName { + if !interfaceCollision { + interfaceCollision = true + } + + fmt.Printf("Warning: %s (IP: %s) is shared on the same network interface %q with the %s (IP: %s)\n", netNameLeft, ipWithIfaceLeft.ip.String(), ipWithIfaceLeft.ifaceName, netNameRight, ipWithIfaceRight.ip.String()) + } + } + } + if interfaceCollision { break } }