diff --git a/cmd/microcloud/main_init.go b/cmd/microcloud/main_init.go index b3a295a81..6aa216d18 100644 --- a/cmd/microcloud/main_init.go +++ b/cmd/microcloud/main_init.go @@ -515,24 +515,81 @@ func validateGatewayNet(config map[string]string, ipPrefix string, cidrValidator } func (c *initConfig) validateSystems(s *service.Handler) (err error) { - for _, sys := range c.systems { + // subnetsCollisionMap maps a subnet CIDR notation to a map of subnet type to peer names. + // + // Example: + // { + // "10.0.1.0/24": { + // "OVN underlay": ["system1", "system2"], + // "Ceph cluster network": ["system3"] + // }, + // "10.0.2.0/24": { + // "Ceph public network": ["system1", "system2", "system3"], + // } + // } + // + // In this example, we have a collision for the subnet "10.0.1.0/24": + // - system1 and system2 are using it for the OVN underlay, whereas system3 is using it for the Ceph cluster network. + // Everything is fine for the subnet "10.0.2.0/24" as all systems are using it for the Ceph public network. + subnetsCollisionMap := make(map[string]map[string][]string) + for peer, sys := range c.systems { if sys.MicroCephInternalNetwork == nil || sys.OVNGeneveNetwork == nil { continue } - _, subnet, err := net.ParseCIDR(sys.MicroCephInternalNetworkSubnet) - if err != nil { - return fmt.Errorf("Failed to parse available network interface CIDR address: %q: %w", subnet, err) + // We also check for interface collisions at a local level. + // This means that we check if the same interface is used for multiple networks in a member. + netTypeToNet := map[string]*Network{ + "Ceph cluster network": sys.MicroCephInternalNetwork, + "OVN underlay": sys.OVNGeneveNetwork, } - underlayIP := net.ParseIP(sys.OVNGeneveAddr) - if underlayIP == nil { - return fmt.Errorf("OVN underlay IP %q is invalid", sys.OVNGeneveAddr) + // 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.MicroCephPublicNetwork != nil { + netTypeToNet["Ceph public network"] = sys.MicroCephPublicNetwork } - if sys.MicroCephInternalNetwork.Subnet.Contains(sys.OVNGeneveNetwork.IP) { - tui.PrintWarning(fmt.Sprintf("OVN underlay IP (%s) is shared with the Ceph cluster network (%s)\n", sys.OVNGeneveNetwork.IP.String(), sys.MicroCephInternalNetwork.Subnet.String())) - break + // This is checking the local collisions (whether the same interface is used for multiple networks in a member). + for typeLeft, netLeft := range netTypeToNet { + for typeRight, netRight := range netTypeToNet { + // 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 typeLeft >= typeRight { + continue + } + + if netLeft.Interface == netRight.Interface { + tui.PrintWarning(fmt.Sprintf("%s is shared with the same network interface %q with the %s\n", typeLeft, netLeft.Interface, typeRight)) + } + + // Populate the subnetsCollisionMap with the subnet type and the peer using it. + subnetTypeToPeers, ok := subnetsCollisionMap[netLeft.Subnet.String()] + if !ok { + subnetTypeToPeers = make(map[string][]string) + subnetTypeToPeers[typeLeft] = make([]string, 0) + subnetTypeToPeers[typeLeft] = append(subnetTypeToPeers[typeLeft], peer) + subnetsCollisionMap[netLeft.Subnet.String()] = subnetTypeToPeers + } else { + subnetTypeToPeers[typeLeft] = append(subnetTypeToPeers[typeLeft], peer) + } + } + } + } + + // Check for subnet collisions at the cluster level. + for subnet, subnetTypeToPeers := range subnetsCollisionMap { + // If there are multiple subnet types on the same subnet, we have a collision. + if len(subnetTypeToPeers) > 1 { + var sb strings.Builder + sb.WriteString("WARNING: Subnet collision detected:\n") + for subnetType, peers := range subnetTypeToPeers { + sb.WriteString(fmt.Sprintf("- Members %s are using the %q subnet for %s\n", strings.Join(peers, ", "), subnet, subnetType)) + } + + sb.WriteString("Please ensure that each subnet is used for a single network type.") + tui.PrintWarning(fmt.Sprintf("%s\n", sb.String())) } }