Skip to content

Commit 192f575

Browse files
pliurhjcaamano
authored andcommitted
Fix hybrid overlay node subnets collision with cluster subnets
When hybrid overlay clustersubnets is undefined, the hybrid overlay node subnet allocation is not managed by ovn-k. But we need to ensures hybrid overlay node subnets are properly marked as allocated in the cluster subnet allocator when they overlap with the cluster subnet ranges, preventing potential subnet allocation conflicts. The fix helps maintain proper subnet allocation tracking when hybrid overlay nodes use subnets that fall within the cluster's subnet ranges. Signed-off-by: Peng Liu <[email protected]>
1 parent c57e83c commit 192f575

File tree

5 files changed

+282
-41
lines changed

5 files changed

+282
-41
lines changed

go-controller/pkg/clustermanager/node/node_allocator.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ type NodeAllocator struct {
4747
networkID int
4848

4949
netInfo util.NetInfo
50+
51+
// nodeSubnets is a list of node subnets that are managed by the cluster subnet allocator
52+
nodeSubnets []*net.IPNet
5053
}
5154

5255
func NewNodeAllocator(networkID int, netInfo util.NetInfo, nodeLister listers.NodeLister, kube kube.Interface, tunnelIDAllocator id.Allocator) *NodeAllocator {
@@ -123,6 +126,10 @@ func (na *NodeAllocator) hasHybridOverlayAllocation() bool {
123126
return config.HybridOverlay.Enabled && !na.netInfo.IsSecondary() && len(config.HybridOverlay.ClusterSubnets) > 0
124127
}
125128

129+
func (na *NodeAllocator) hasHybridOverlayAllocationUnmanaged() bool {
130+
return config.HybridOverlay.Enabled && !na.netInfo.IsSecondary() && len(config.HybridOverlay.ClusterSubnets) == 0
131+
}
132+
126133
func (na *NodeAllocator) recordSubnetCount() {
127134
// only for L3 networks
128135
if na.hasNodeSubnetAllocation() {
@@ -230,6 +237,12 @@ func (na *NodeAllocator) HandleAddUpdateNodeEvent(node *corev1.Node) error {
230237
}
231238
return fmt.Errorf("failed to set hybrid overlay annotations for node %s: %v", node.Name, err)
232239
}
240+
} else if na.hasHybridOverlayAllocationUnmanaged() {
241+
// this is a hybrid overlay node but not managed by the hybrid overlay subnet allocator
242+
// Hybrid overlay is only available for IPv4 for now, so we only need to check for IPv4 subnets
243+
if err := na.markAllocatedNetworksForUnmanagedHONode(node); err != nil {
244+
return fmt.Errorf("failed to mark the subnet %v as allocated in the cluster subnet allocator for node %s: %v", na.nodeSubnets, node.Name, err)
245+
}
233246
}
234247
return nil
235248
}
@@ -380,7 +393,7 @@ func (na *NodeAllocator) HandleDeleteNode(node *corev1.Node) error {
380393
return nil
381394
}
382395

383-
if na.hasNodeSubnetAllocation() {
396+
if na.hasNodeSubnetAllocation() || na.hasHybridOverlayAllocationUnmanaged() {
384397
na.clusterSubnetAllocator.ReleaseAllNetworks(node.Name)
385398
na.recordSubnetUsage()
386399
}
@@ -411,10 +424,17 @@ func (na *NodeAllocator) Sync(nodes []interface{}) error {
411424
klog.Errorf("Failed to parse hybrid overlay for node %s: %v", node.Name, err)
412425
} else if hostSubnet != nil {
413426
klog.V(5).Infof("Node %s contains subnets: %v", node.Name, hostSubnet)
414-
if err := na.hybridOverlaySubnetAllocator.ReleaseNetworks(node.Name, hostSubnet); err != nil {
427+
if err := na.hybridOverlaySubnetAllocator.MarkAllocatedNetworks(node.Name, hostSubnet); err != nil {
415428
klog.Errorf("Failed to mark the subnet %v as allocated in the hybrid subnet allocator for node %s: %v", hostSubnet, node.Name, err)
416429
}
417430
}
431+
} else if na.hasHybridOverlayAllocationUnmanaged() {
432+
// this is a hybrid overlay node but not managed by the hybrid overlay subnet allocator
433+
// Hybrid overlay is only available for IPv4 for now, so we only need to check for IPv4 subnets
434+
if err := na.markAllocatedNetworksForUnmanagedHONode(node); err != nil {
435+
klog.Errorf("Failed to mark the subnet as allocated in the cluster subnet allocator for hybrid overlay node %s: %v", node.Name, err)
436+
}
437+
418438
}
419439
} else {
420440
hostSubnets, _ := util.ParseNodeHostSubnetAnnotation(node, networkName)
@@ -634,3 +654,37 @@ func (na *NodeAllocator) hasJoinSubnetAllocation() bool {
634654
// we allocate join subnets for L3/L2 primary user defined networks or default network
635655
return na.netInfo.IsDefault() || (util.IsNetworkSegmentationSupportEnabled() && na.netInfo.IsPrimaryNetwork())
636656
}
657+
658+
func (na *NodeAllocator) markAllocatedNetworksForUnmanagedHONode(node *corev1.Node) error {
659+
hostSubnet, err := houtil.ParseHybridOverlayHostSubnet(node)
660+
if err != nil {
661+
return fmt.Errorf("failed to parse hybrid overlay for node %s: %v", node.Name, err)
662+
}
663+
664+
var overlaps []*net.IPNet
665+
for _, clusterSubnet := range na.netInfo.Subnets() {
666+
if overlaps = util.IPNetOverlaps(hostSubnet, clusterSubnet.CIDR); len(overlaps) != 0 {
667+
break
668+
}
669+
}
670+
if len(overlaps) == 0 {
671+
// if the host subnet does not overlap with any cluster subnet, return
672+
return nil
673+
}
674+
675+
if hostSubnet != nil {
676+
if na.nodeSubnets == nil {
677+
// initialize the nodeSubnets variable at the first called
678+
na.nodeSubnets = na.clusterSubnetAllocator.ListAllIPv4Networks()
679+
}
680+
// check if the host subnet overlaps with any node subnet that is managed by the cluster subnet allocator
681+
// if it does, mark it as allocated in the cluster subnet allocator
682+
if overlaps := util.IPNetOverlaps(hostSubnet, na.nodeSubnets...); overlaps != nil {
683+
klog.Infof("Hybrid overlay node %s overlaps with subnets: %v", node.Name, overlaps)
684+
if err := na.clusterSubnetAllocator.MarkAllocatedNetworks(node.Name, overlaps...); err != nil {
685+
return fmt.Errorf("failed to mark the subnet %v as allocated: %v", hostSubnet, err)
686+
}
687+
}
688+
}
689+
return nil
690+
}

go-controller/pkg/clustermanager/node/node_allocator_test.go

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import (
88

99
cnitypes "github.com/containernetworking/cni/pkg/types"
1010

11+
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/client-go/listers/core/v1"
14+
"k8s.io/client-go/tools/cache"
15+
1116
ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types"
1217
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config"
1318
ovntest "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing"
@@ -45,9 +50,10 @@ func TestController_allocateNodeSubnets(t *testing.T) {
4550
existingNets []*net.IPNet
4651
alreadyOwned *existingAllocation
4752
// to be converted during the test to []*net.IPNet
48-
wantStr []string
49-
allocated int
50-
wantErr bool
53+
wantStr []string
54+
allocated int
55+
wantErr bool
56+
existingNodes []*corev1.Node
5157
}{
5258
{
5359
name: "new node, IPv4 only cluster",
@@ -60,6 +66,56 @@ func TestController_allocateNodeSubnets(t *testing.T) {
6066
allocated: 1,
6167
wantErr: false,
6268
},
69+
{
70+
name: "new node, IPv4 only cluster, the test node is added when a hybrid overlay node with overlapped node subnet exists",
71+
networkRanges: []string{"172.16.0.0/16"},
72+
networkLens: []int{24},
73+
configIPv4: true,
74+
configIPv6: false,
75+
existingNets: nil,
76+
wantStr: []string{"172.16.1.0/24"},
77+
allocated: 1,
78+
wantErr: false,
79+
existingNodes: []*corev1.Node{
80+
{
81+
ObjectMeta: metav1.ObjectMeta{
82+
Name: "ho_node1",
83+
Annotations: map[string]string{
84+
"k8s.ovn.org/hybrid-overlay-node-subnet": "172.16.0.0/24",
85+
},
86+
Labels: map[string]string{
87+
"hybrid-overlay-node": "true",
88+
},
89+
},
90+
Spec: corev1.NodeSpec{},
91+
},
92+
},
93+
},
94+
{
95+
name: "new node, IPv4 only cluster, the test node is added when a hybrid overlay node with overlapped and differernt mask length node subnet exists",
96+
networkRanges: []string{"172.16.0.0/16"},
97+
networkLens: []int{24},
98+
configIPv4: true,
99+
configIPv6: false,
100+
existingNets: nil,
101+
wantStr: []string{"172.16.2.0/24"},
102+
allocated: 1,
103+
wantErr: false,
104+
existingNodes: []*corev1.Node{
105+
{
106+
ObjectMeta: metav1.ObjectMeta{
107+
Name: "ho_node1",
108+
Annotations: map[string]string{
109+
"k8s.ovn.org/hybrid-overlay-node-subnet": "172.16.0.0/23",
110+
},
111+
Labels: map[string]string{
112+
"hybrid-overlay-node": "true",
113+
},
114+
},
115+
Spec: corev1.NodeSpec{},
116+
},
117+
},
118+
},
63119
{
64120
name: "new node, IPv6 only cluster",
65121
networkRanges: []string{"2001:db2::/56"},
@@ -208,6 +264,12 @@ func TestController_allocateNodeSubnets(t *testing.T) {
208264

209265
for _, tt := range tests {
210266
t.Run(tt.name, func(t *testing.T) {
267+
config.HybridOverlay.Enabled = true
268+
config.Kubernetes.NoHostSubnetNodes, _ = metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
269+
MatchLabels: map[string]string{"hybrid-overlay-node": "true"},
270+
})
271+
config.HybridOverlay.ClusterSubnets = nil
272+
211273
ranges, err := rangesFromStrings(tt.networkRanges, tt.networkLens)
212274
if err != nil {
213275
t.Fatal(err)
@@ -231,6 +293,15 @@ func TestController_allocateNodeSubnets(t *testing.T) {
231293
if err := na.Init(); err != nil {
232294
t.Fatalf("Failed to initialize node allocator: %v", err)
233295
}
296+
nodeInterfaces := make([]interface{}, len(tt.existingNodes))
297+
for i, node := range tt.existingNodes {
298+
nodeInterfaces[i] = node
299+
}
300+
// Sync existing nodes before allocating subnets
301+
err = na.Sync(nodeInterfaces)
302+
if err != nil {
303+
t.Fatal(err)
304+
}
234305

235306
if tt.alreadyOwned != nil {
236307
err := na.clusterSubnetAllocator.MarkAllocatedNetworks(tt.alreadyOwned.owner, ovntest.MustParseIPNets(tt.alreadyOwned.subnet)...)
@@ -291,6 +362,7 @@ func TestController_allocateNodeSubnets_ReleaseOnError(t *testing.T) {
291362
na := &NodeAllocator{
292363
netInfo: netInfo,
293364
clusterSubnetAllocator: NewSubnetAllocator(),
365+
nodeLister: newFakeNodeLister([]*corev1.Node{}),
294366
}
295367

296368
if err := na.Init(); err != nil {
@@ -323,3 +395,11 @@ func TestController_allocateNodeSubnets_ReleaseOnError(t *testing.T) {
323395
t.Fatalf("Expected %d v6 allocated subnets, but got %d", v6usedBefore, v6usedAfter)
324396
}
325397
}
398+
399+
func newFakeNodeLister(nodes []*corev1.Node) v1.NodeLister {
400+
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
401+
for _, node := range nodes {
402+
_ = indexer.Add(node)
403+
}
404+
return v1.NewNodeLister(indexer)
405+
}

go-controller/pkg/clustermanager/node/subnet_allocator.go

Lines changed: 88 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ type SubnetAllocator interface {
2323
AllocateNetworks(string) ([]*net.IPNet, error)
2424
AllocateIPv4Network(string) (*net.IPNet, error)
2525
AllocateIPv6Network(string) (*net.IPNet, error)
26+
ListAllIPv4Networks() []*net.IPNet
27+
ListAllIPv6Networks() []*net.IPNet
2628
// ReleaseNetworks releases the given networks if they are owned by the
2729
// given owner
2830
ReleaseNetworks(string, ...*net.IPNet) error
@@ -192,6 +194,22 @@ func (sna *BaseSubnetAllocator) AllocateIPv6Network(owner string) (*net.IPNet, e
192194
return nil, ErrSubnetAllocatorFull
193195
}
194196

197+
func (sna *BaseSubnetAllocator) ListAllIPv4Networks() []*net.IPNet {
198+
var subnets []*net.IPNet
199+
for _, snr := range sna.v4ranges {
200+
subnets = append(subnets, snr.listAllNetworks()...)
201+
}
202+
return subnets
203+
}
204+
205+
func (sna *BaseSubnetAllocator) ListAllIPv6Networks() []*net.IPNet {
206+
var subnets []*net.IPNet
207+
for _, snr := range sna.v6ranges {
208+
subnets = append(subnets, snr.listAllNetworks()...)
209+
}
210+
return subnets
211+
}
212+
195213
func (sna *BaseSubnetAllocator) ReleaseNetworks(owner string, subnets ...*net.IPNet) error {
196214
sna.Lock()
197215
defer sna.Unlock()
@@ -379,48 +397,20 @@ func (snr *subnetAllocatorRange) allocateNetwork(owner string) *net.IPNet {
379397
return subnet
380398
}
381399
}
382-
netMaskSize, addrLen := snr.network.Mask.Size()
383-
numSubnets := uint32(1) << snr.subnetBits
384-
if snr.subnetBits > 24 {
385-
// We need to make sure that the uint32 math below won't overflow. If
386-
// snr.subnetBits > 32 then numSubnets has already overflowed, but also if
387-
// numSubnets is between 1<<24 and 1<<32 then "base << (snr.hostBits % 8)"
388-
// below could overflow if snr.hostBits%8 is non-0. So we cap numSubnets
389-
// at 1<<24. "16M subnets ought to be enough for anybody."
390-
numSubnets = 1 << 24
391-
}
392-
393-
var i uint32
394-
for i = 0; i < numSubnets; i++ {
395-
n := (i + snr.next) % numSubnets
396-
base := n
397-
if snr.leftShift != 0 {
398-
base = ((base << snr.leftShift) & snr.leftMask) | ((base >> snr.rightShift) & snr.rightMask)
399-
} else if addrLen == 128 && snr.subnetBits >= 16 {
400-
// Skip the 0 subnet (and other subnets with all 0s in the low word)
401-
// since the extra 0 word will get compressed out and make the address
402-
// look different from addresses on other subnets.
403-
if (base & 0xFFFF) == 0 {
404-
continue
405-
}
406-
}
407400

408-
genIP := append([]byte{}, []byte(snr.network.IP)...)
409-
subnetBits := base << (snr.hostBits % 8)
410-
b := (uint32(addrLen) - snr.hostBits - 1) / 8
411-
for subnetBits != 0 {
412-
genIP[b] |= byte(subnetBits)
413-
subnetBits >>= 8
414-
b--
415-
}
416-
417-
genSubnet := &net.IPNet{IP: genIP, Mask: net.CIDRMask(int(snr.subnetBits)+netMaskSize, addrLen)}
401+
var subnet *net.IPNet
402+
snr.foreach(snr.next, func(n uint32, genSubnet *net.IPNet) bool {
418403
if _, ok := snr.allocMap[genSubnet.String()]; !ok {
419404
snr.allocMap[genSubnet.String()] = owner
420405
snr.next = n + 1
421406
snr.used++
422-
return genSubnet
407+
subnet = genSubnet
408+
return false
423409
}
410+
return true
411+
})
412+
if subnet != nil {
413+
return subnet
424414
}
425415

426416
snr.next = 0
@@ -456,3 +446,65 @@ func (snr *subnetAllocatorRange) releaseAllNetworks(owner string) {
456446
}
457447
}
458448
}
449+
450+
// generateSubnet generates a subnet for the given base number using the allocator's parameters
451+
func (snr *subnetAllocatorRange) generateSubnet(base uint32) *net.IPNet {
452+
netMaskSize, addrLen := snr.network.Mask.Size()
453+
454+
if snr.leftShift != 0 {
455+
base = ((base << snr.leftShift) & snr.leftMask) | ((base >> snr.rightShift) & snr.rightMask)
456+
} else if addrLen == 128 && snr.subnetBits >= 16 {
457+
// Skip the 0 subnet (and other subnets with all 0s in the low word)
458+
// since the extra 0 word will get compressed out and make the address
459+
// look different from addresses on other subnets.
460+
if (base & 0xFFFF) == 0 {
461+
return nil
462+
}
463+
}
464+
465+
genIP := append([]byte{}, []byte(snr.network.IP)...)
466+
subnetBits := base << (snr.hostBits % 8)
467+
b := (uint32(addrLen) - snr.hostBits - 1) / 8
468+
for subnetBits != 0 {
469+
genIP[b] |= byte(subnetBits)
470+
subnetBits >>= 8
471+
b--
472+
}
473+
474+
return &net.IPNet{
475+
IP: genIP,
476+
Mask: net.CIDRMask(int(snr.subnetBits)+netMaskSize, addrLen),
477+
}
478+
}
479+
480+
func (snr *subnetAllocatorRange) foreach(next uint32, do func(uint32, *net.IPNet) bool) {
481+
numSubnets := uint32(1) << snr.subnetBits
482+
if snr.subnetBits > 24 {
483+
// We need to make sure that the uint32 math below won't overflow. If
484+
// snr.subnetBits > 32 then numSubnets has already overflowed, but also if
485+
// numSubnets is between 1<<24 and 1<<32 then "base << (snr.hostBits % 8)"
486+
// below could overflow if snr.hostBits%8 is non-0. So we cap numSubnets
487+
// at 1<<24. "16M subnets ought to be enough for anybody."
488+
numSubnets = 1 << 24
489+
}
490+
491+
var i uint32
492+
for i = 0; i < numSubnets; i++ {
493+
n := (i + next) % numSubnets
494+
base := n
495+
genSubnet := snr.generateSubnet(base)
496+
if genSubnet != nil && !do(n, genSubnet) {
497+
return
498+
}
499+
}
500+
}
501+
502+
// listAllNetworks returns all networks in the range
503+
func (snr *subnetAllocatorRange) listAllNetworks() []*net.IPNet {
504+
var subnets []*net.IPNet
505+
snr.foreach(0, func(_ uint32, genSubnet *net.IPNet) bool {
506+
subnets = append(subnets, genSubnet)
507+
return true
508+
})
509+
return subnets
510+
}

0 commit comments

Comments
 (0)