From 9169c1d8ee416349bc7b9bffc96d9c635d4e0627 Mon Sep 17 00:00:00 2001 From: Gregory Thiemonge Date: Wed, 4 Dec 2024 11:26:11 +0100 Subject: [PATCH 1/2] Allow custom-sized CIDR for the management subnets The octavia-operator previously required the user to pass /16 (or /64 for IPv6) CIDRs for the management subnets, now it's more flexible and allows custom sized CIDRs The functional tests now check that the allocation pools are correctly set, based on those CIDRs. JIRA: OSPRH-11711 --- pkg/octavia/network_parameters.go | 50 ++++++++++----------- tests/functional/octavia_controller_test.go | 16 ++++++- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/pkg/octavia/network_parameters.go b/pkg/octavia/network_parameters.go index 724f2ce8..d255bf2b 100644 --- a/pkg/octavia/network_parameters.go +++ b/pkg/octavia/network_parameters.go @@ -55,33 +55,31 @@ func getConfigFromNAD( func GetRangeFromCIDR( cidr netip.Prefix, ) (start netip.Addr, end netip.Addr) { - // For IPv6, a /64 is expected, if the CIDR is aaaa:bbbb:cccc:dddd::/64, - // the range is aaaa:bbbb:cccc:dddd::5 - aaaa:bbbb:cccc:dddd:ffff:ffff:ffff:fffe - // For IPv4, a /16 is expected, if the CIDR is a.b.0.0/16 - // the range is a.b.0.5 - a.b.255.254 - // IPs from from 1 to 5 are reserved for later user - addr := cidr.Addr() - if addr.Is6() { - addrBytes := addr.As16() - for i := 8; i < 15; i++ { - addrBytes[i] = 0 - } - addrBytes[15] = 5 - start = netip.AddrFrom16(addrBytes) - for i := 8; i < 15; i++ { - addrBytes[i] = 0xff - } - addrBytes[15] = 0xfe - end = netip.AddrFrom16(addrBytes) - } else { - addrBytes := addr.As4() - addrBytes[2] = 0 - addrBytes[3] = 5 - start = netip.AddrFrom4(addrBytes) - addrBytes[2] = 0xff - addrBytes[3] = 0xfe - end = netip.AddrFrom4(addrBytes) + // start is the 5th address of the Cidr + start = cidr.Masked().Addr() + for i := 0; i < 5; i++ { + start = start.Next() + } + + bits := cidr.Bits() + if start.Is4() { + // Padding for ipv4 addresses in a [16]bytes table + bits += 96 + } + // convert it to a [16]bytes table, set the remaining bits to 1 + addrBytes := start.As16() + for b := bits; b < 128; b++ { + addrBytes[b/8] |= 1 << uint(7-(b%8)) } + // convert the table to an ip address to get the last IP + // in case of IPv4, the address should be unmapped + last := netip.AddrFrom16(addrBytes) + if start.Is4() { + last = last.Unmap() + } + // end is the 2nd last + end = last.Prev() + return } diff --git a/tests/functional/octavia_controller_test.go b/tests/functional/octavia_controller_test.go index cf26ced9..f2d59ae1 100644 --- a/tests/functional/octavia_controller_test.go +++ b/tests/functional/octavia_controller_test.go @@ -663,7 +663,7 @@ var _ = Describe("Octavia controller", func() { spec["lbMgmtNetwork"].(map[string]interface{})["availabilityZoneCIDRs"] = map[string]string{ "az1": "172.34.0.0/16", - "az2": "172.44.0.0/16", + "az2": "172.44.8.0/21", } spec["lbMgmtNetwork"].(map[string]interface{})["createDefaultLbMgmtNetwork"] = false DeferCleanup(th.DeleteInstance, CreateOctavia(octaviaName, spec)) @@ -763,6 +763,16 @@ var _ = Describe("Octavia controller", func() { CIDR: nadConfig.IPAM.CIDR.String(), }, } + expectedAllocationPools := map[string]subnets.AllocationPool{ + subnetNameAZ1: { + Start: "172.34.0.5", + End: "172.34.255.254", + }, + subnetNameAZ2: { + Start: "172.44.8.5", + End: "172.44.15.254", + }, + } resultSubnets := map[string]subnets.Subnet{} for _, subnet := range apiFixtures.Neutron.Subnets { @@ -777,6 +787,10 @@ var _ = Describe("Octavia controller", func() { Expect(subnet.NetworkID).To(Equal(expectedSubnet.NetworkID)) Expect(subnet.CIDR).To(Equal(expectedSubnet.CIDR)) Expect(subnet.HostRoutes).To(Equal(expectedSubnet.HostRoutes)) + if expectedAllocationPool, ok := expectedAllocationPools[name]; ok { + Expect(subnet.AllocationPools[0].Start).To(Equal(expectedAllocationPool.Start)) + Expect(subnet.AllocationPools[0].End).To(Equal(expectedAllocationPool.End)) + } } // Routers From b3a9cf39143a5b4c192d3b33f3902642b541fe98 Mon Sep 17 00:00:00 2001 From: Gregory Thiemonge Date: Fri, 6 Dec 2024 07:34:16 +0100 Subject: [PATCH 2/2] Don't block CIDR masks other than 16/64 PR 422 allowed computing subnet ranges from CIDRs with any masks, but the code still checks that the NAD ipam.routes[0].dst is a /16 (ipv4) or /64 (ipv6), we can now remove this restriction. JIRA: OSPRH-11711 --- pkg/octavia/network_parameters.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/octavia/network_parameters.go b/pkg/octavia/network_parameters.go index d255bf2b..51a2377e 100644 --- a/pkg/octavia/network_parameters.go +++ b/pkg/octavia/network_parameters.go @@ -135,18 +135,6 @@ func GetNetworkParametersFromNAD( if len(nadConfig.IPAM.Routes) > 0 { networkParameters.TenantCIDR = nadConfig.IPAM.Routes[0].Destination - // For IPv4, we require a /16 subnet, for IPv6 a /64 - var bitlen int - if networkParameters.TenantCIDR.Addr().Is6() { - bitlen = 64 - } else { - bitlen = 16 - } - - if networkParameters.TenantCIDR.Bits() != bitlen { - return nil, fmt.Errorf("the tenant CIDR is /%d, it should be /%d", networkParameters.TenantCIDR.Bits(), bitlen) - } - // Compute an allocation range based on the CIDR start, end := GetRangeFromCIDR(networkParameters.TenantCIDR) networkParameters.TenantAllocationStart = start