• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubeovn / kube-ovn / 20916475920

12 Jan 2026 10:44AM UTC coverage: 22.568% (-0.2%) from 22.747%
20916475920

Pull #6153

github

zbb88888
feat(controller): add VPC NAT gateway and iptables EIP handling in daemon controller

Signed-off-by: zbb88888 <jmdxjsjgcxy@gmail.com>
Pull Request #6153: feat(controller): allow SSH from node to Iptables eip

42 of 594 new or added lines in 7 files covered. (7.07%)

8 existing lines in 2 files now uncovered.

12226 of 54173 relevant lines covered (22.57%)

0.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

8.86
/pkg/daemon/node_route_to_eip_linux.go
1
package daemon
2

3
// This file implements node-local EIP access for VPC NAT Gateway.
4
//
5
// Problem: When a VPC NAT Gateway pod runs on a node, the node itself cannot
6
// directly access the EIP addresses configured on the gateway because the
7
// EIP traffic goes through the external macvlan interface attached to the pod.
8
//
9
// Solution: Create a macvlan sub-interface on the node with the same master
10
// interface as the NAT Gateway's external network, and add host routes for
11
// each EIP pointing to this sub-interface. This allows the node to reach
12
// the EIP addresses via Layer 2 on the external network.
13
//
14
// Control flow:
15
//  1. Subnet event (subnet with NadMacvlanMasterAnnotation):
16
//     enqueueAddSubnet/enqueueUpdateSubnet/enqueueDeleteSubnet
17
//     → enqueueMacvlanSubnet() → macvlanSubnetQueue
18
//     → runMacvlanSubnetWorker() → reconcileMacvlanSubnet()
19
//     → createMacvlanSubInterface() / deleteMacvlanSubInterface()
20
//
21
//  2. IptablesEIP event:
22
//     - Add/Update: enqueueAddIptablesEip / enqueueUpdateIptablesEip → iptablesEipQueue
23
//       → runIptablesEipWorker() → handleAddIptablesEipRoute() → addEIPRoute()
24
//     - Delete: enqueueDeleteIptablesEip → iptablesEipDeleteQueue
25
//       → runIptablesEipDeleteWorker() → handleDeleteIptablesEipRoute() → deleteEIPRoute()
26
//
27
//  3. NAT GW Pod event (pod update with VpcNatGatewayLabel):
28
//     enqueueUpdatePod() → handleNatGwPodUpdate() → enqueueEipsForNatGw()
29
//     → handleAddIptablesEipRoute() → addEIPRoute() / deleteEIPRoute()
30
//
31
// Prerequisites:
32
//   - Subnet controller must set NadMacvlanMasterAnnotation when provider NAD is macvlan type
33
//   - EnableNodeLocalAccessVpcNatGwEIP config flag must be true (default)
34

35
import (
36
        "encoding/hex"
37
        "errors"
38
        "fmt"
39
        "net"
40
        "strings"
41
        "sync"
42
        "syscall"
43

44
        "github.com/vishvananda/netlink"
45
        corev1 "k8s.io/api/core/v1"
46
        "k8s.io/apimachinery/pkg/labels"
47
        "k8s.io/client-go/tools/cache"
48
        "k8s.io/klog/v2"
49

50
        kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
51
        "github.com/kubeovn/kube-ovn/pkg/util"
52
)
53

54
const (
55
        // macvlanLinkPrefix is the prefix for macvlan sub-interfaces created for node local EIP access
56
        // Using short prefix "mac" (3 chars), leaving 8 chars for hex-encoded IPv4 address
57
        macvlanLinkPrefix = "mac"
58
)
59

60
// deletedEIPs tracks EIPs that have been deleted to prevent race conditions
61
// between delete events and queued add events
62
var deletedEIPs sync.Map
63

64
// eipRouteInfo holds information needed to add or delete an EIP route.
65
// We store the macvlan interface name directly (computed at enqueue time) because:
66
// 1. The EIP object may be gone from the API server after deletion
67
// 2. Avoids redundant computation of interface name on each retry
68
type eipRouteInfo struct {
69
        eipName     string // EIP name, used for add operations to check current state
70
        v4ip        string // IPv4 address of the EIP
71
        macvlanName string // macvlan sub-interface name for the route
72
}
73

74
// generateMacvlanName generates macvlan sub-interface name from IPv4 CIDR.
75
// Format: "mac" (3 chars) + hex encoded IPv4 address (8 chars) = 11 chars.
76
// Linux interface names are limited to 15 characters.
77
//
78
// Using hex encoding avoids collisions that could occur with simple digit concatenation
79
// (e.g., "192.168.1.0" and "19.216.81.0" would both produce "19216810").
80
//
81
// Examples:
82
//
83
//        CIDR "192.168.1.0/24" -> "macc0a80100"
84
//        CIDR "10.0.0.0/8"      -> "mac0a000000"
85
//        CIDR "172.16.0.0/16"   -> "macac100000"
86
//
87
// Returns an error for invalid CIDRs or IPv6 addresses (not yet supported).
88
// TODO: For IPv6 support, consider using FNV-1a hash to handle longer addresses.
89
func generateMacvlanName(cidr string) (string, error) {
1✔
90
        // Extract network address part (before /)
1✔
91
        networkAddrStr := cidr
1✔
92
        if idx := strings.Index(cidr, "/"); idx != -1 {
2✔
93
                networkAddrStr = cidr[:idx]
1✔
94
        }
1✔
95

96
        ip := net.ParseIP(networkAddrStr)
1✔
97
        // Currently this feature only supports IPv4.
1✔
98
        if ip == nil || ip.To4() == nil {
2✔
99
                return "", fmt.Errorf("generateMacvlanName: cidr=%s is not a valid IPv4, IPv6 is not supported yet", cidr)
1✔
100
        }
1✔
101

102
        // Use hex encoding of the IPv4 address to avoid collisions.
103
        name := macvlanLinkPrefix + hex.EncodeToString(ip.To4())
1✔
104
        klog.V(3).Infof("generateMacvlanName: cidr=%s -> name=%s", cidr, name)
1✔
105
        return name, nil
1✔
106
}
107

108
// parseEIPDestination parses an EIP address into a host route destination (/32 for IPv4, /128 for IPv6)
109
func parseEIPDestination(eip string) (*net.IPNet, error) {
1✔
110
        ip := net.ParseIP(eip)
1✔
111
        if ip == nil {
2✔
112
                err := fmt.Errorf("invalid EIP address: %s", eip)
1✔
113
                klog.Error(err)
1✔
114
                return nil, err
1✔
115
        }
1✔
116

117
        mask := net.CIDRMask(32, 32)
1✔
118
        if ip.To4() == nil {
2✔
119
                mask = net.CIDRMask(128, 128)
1✔
120
        }
1✔
121
        return &net.IPNet{IP: ip, Mask: mask}, nil
1✔
122
}
123

124
// ensureMacvlanSubInterfaceUp ensures the macvlan sub-interface is up
NEW
125
func ensureMacvlanSubInterfaceUp(link netlink.Link) error {
×
NEW
126
        if link.Attrs().OperState != netlink.OperUp {
×
NEW
127
                if err := netlink.LinkSetUp(link); err != nil {
×
NEW
128
                        err = fmt.Errorf("failed to set link %s up: %w", link.Attrs().Name, err)
×
NEW
129
                        klog.Error(err)
×
NEW
130
                        return err
×
NEW
131
                }
×
132
        }
NEW
133
        return nil
×
134
}
135

136
// addNodeIPToLink adds node IP with host mask (/32 for IPv4, /128 for IPv6) to the macvlan sub-interface.
137
// This allows the node to respond to ARP/NDP requests for its IP on the macvlan interface.
NEW
138
func (c *Controller) addNodeIPToLink(link netlink.Link) error {
×
NEW
139
        nodeIP := c.config.NodeIPv4
×
NEW
140
        mask := "/32"
×
NEW
141
        if nodeIP == "" {
×
NEW
142
                nodeIP = c.config.NodeIPv6
×
NEW
143
                mask = "/128"
×
NEW
144
        }
×
NEW
145
        if nodeIP == "" {
×
NEW
146
                return nil
×
NEW
147
        }
×
148

NEW
149
        addr, err := netlink.ParseAddr(nodeIP + mask)
×
NEW
150
        if err != nil {
×
NEW
151
                err = fmt.Errorf("failed to parse node IP %s: %w", nodeIP, err)
×
NEW
152
                klog.Error(err)
×
NEW
153
                return err
×
NEW
154
        }
×
NEW
155
        if err := netlink.AddrAdd(link, addr); err != nil && !errors.Is(err, syscall.EEXIST) {
×
NEW
156
                err = fmt.Errorf("failed to add address %s to link %s: %w", nodeIP, link.Attrs().Name, err)
×
NEW
157
                klog.Error(err)
×
NEW
158
                return err
×
NEW
159
        }
×
NEW
160
        return nil
×
161
}
162

163
// createMacvlanSubInterface creates a macvlan sub-interface for node local EIP access.
NEW
164
func (c *Controller) createMacvlanSubInterface(masterIface, cidr string) error {
×
NEW
165
        macvlanName, err := generateMacvlanName(cidr)
×
NEW
166
        if err != nil {
×
NEW
167
                klog.Error(err)
×
NEW
168
                return err
×
NEW
169
        }
×
170

171
        // Check if sub-interface already exists
NEW
172
        if link, err := netlink.LinkByName(macvlanName); err == nil {
×
NEW
173
                klog.V(3).Infof("macvlan sub-interface %s already exists", macvlanName)
×
NEW
174
                return ensureMacvlanSubInterfaceUp(link)
×
NEW
175
        }
×
176

NEW
177
        master, err := netlink.LinkByName(masterIface)
×
NEW
178
        if err != nil {
×
NEW
179
                err = fmt.Errorf("failed to get master interface %s: %w", masterIface, err)
×
NEW
180
                klog.Error(err)
×
NEW
181
                return err
×
NEW
182
        }
×
183

NEW
184
        macvlan := &netlink.Macvlan{
×
NEW
185
                LinkAttrs: netlink.LinkAttrs{
×
NEW
186
                        Name:        macvlanName,
×
NEW
187
                        ParentIndex: master.Attrs().Index,
×
NEW
188
                },
×
NEW
189
                Mode: netlink.MACVLAN_MODE_BRIDGE,
×
NEW
190
        }
×
NEW
191

×
NEW
192
        if err := netlink.LinkAdd(macvlan); err != nil {
×
NEW
193
                err = fmt.Errorf("failed to create macvlan sub-interface %s: %w", macvlanName, err)
×
NEW
194
                klog.Error(err)
×
NEW
195
                return err
×
NEW
196
        }
×
197

NEW
198
        if err := netlink.LinkSetUp(macvlan); err != nil {
×
NEW
199
                if delErr := netlink.LinkDel(macvlan); delErr != nil {
×
NEW
200
                        klog.Warningf("failed to cleanup macvlan interface %s after setup failed: %v", macvlanName, delErr)
×
NEW
201
                }
×
NEW
202
                err = fmt.Errorf("failed to set macvlan sub-interface %s up: %w", macvlanName, err)
×
NEW
203
                klog.Error(err)
×
NEW
204
                return err
×
205
        }
206

207
        // Add node IP to macvlan interface for ARP/NDP responses.
NEW
208
        if c.config.EnableMacvlanNodeLocalIP {
×
NEW
209
                if err := c.addNodeIPToLink(macvlan); err != nil {
×
NEW
210
                        err = fmt.Errorf("failed to add node IP to macvlan %s: %w", macvlanName, err)
×
NEW
211
                        klog.Error(err)
×
NEW
212
                        return err
×
NEW
213
                }
×
214
        }
215

NEW
216
        klog.Infof("created macvlan sub-interface %s with master %s for cidr %s", macvlanName, masterIface, cidr)
×
NEW
217
        return nil
×
218
}
219

220
// deleteMacvlanSubInterface deletes the macvlan sub-interface.
NEW
221
func deleteMacvlanSubInterface(cidr string) error {
×
NEW
222
        macvlanName, err := generateMacvlanName(cidr)
×
NEW
223
        if err != nil {
×
NEW
224
                klog.Error(err)
×
NEW
225
                return err
×
NEW
226
        }
×
227

NEW
228
        link, err := netlink.LinkByName(macvlanName)
×
NEW
229
        if err != nil {
×
NEW
230
                if _, ok := err.(netlink.LinkNotFoundError); ok {
×
NEW
231
                        return nil
×
NEW
232
                }
×
NEW
233
                err = fmt.Errorf("failed to get macvlan sub-interface %s: %w", macvlanName, err)
×
NEW
234
                klog.Error(err)
×
NEW
235
                return err
×
236
        }
237

NEW
238
        if err := netlink.LinkDel(link); err != nil {
×
NEW
239
                err = fmt.Errorf("failed to delete macvlan sub-interface %s: %w", macvlanName, err)
×
NEW
240
                klog.Error(err)
×
NEW
241
                return err
×
NEW
242
        }
×
243

NEW
244
        klog.Infof("deleted macvlan sub-interface %s", macvlanName)
×
NEW
245
        return nil
×
246
}
247

248
// addEIPRoute adds a route for EIP via the specified macvlan sub-interface.
NEW
249
func addEIPRoute(eip, macvlanSubIfName string) error {
×
NEW
250
        link, err := netlink.LinkByName(macvlanSubIfName)
×
NEW
251
        if err != nil {
×
NEW
252
                err = fmt.Errorf("failed to get macvlan sub-interface %s for EIP %s: %w", macvlanSubIfName, eip, err)
×
NEW
253
                klog.Error(err)
×
NEW
254
                return err
×
NEW
255
        }
×
256

NEW
257
        dst, err := parseEIPDestination(eip)
×
NEW
258
        if err != nil {
×
NEW
259
                klog.Error(err)
×
NEW
260
                return err
×
NEW
261
        }
×
262

NEW
263
        route := &netlink.Route{
×
NEW
264
                LinkIndex: link.Attrs().Index,
×
NEW
265
                Dst:       dst,
×
NEW
266
                Scope:     netlink.SCOPE_LINK,
×
NEW
267
        }
×
NEW
268

×
NEW
269
        if err := netlink.RouteReplace(route); err != nil {
×
NEW
270
                err = fmt.Errorf("failed to add route for EIP %s via %s: %w", eip, macvlanSubIfName, err)
×
NEW
271
                klog.Error(err)
×
NEW
272
                return err
×
NEW
273
        }
×
274

NEW
275
        klog.Infof("added route for EIP %s via macvlan sub-interface %s", eip, macvlanSubIfName)
×
NEW
276
        return nil
×
277
}
278

279
// deleteEIPRoute deletes the route for EIP from the specified macvlan sub-interface.
NEW
280
func deleteEIPRoute(eip, macvlanSubIfName string) error {
×
NEW
281
        link, err := netlink.LinkByName(macvlanSubIfName)
×
NEW
282
        if err != nil {
×
NEW
283
                if _, ok := err.(netlink.LinkNotFoundError); ok {
×
NEW
284
                        klog.V(3).Infof("macvlan interface %s not found, route for EIP %s may already be deleted", macvlanSubIfName, eip)
×
NEW
285
                        return nil
×
NEW
286
                }
×
NEW
287
                err = fmt.Errorf("failed to get macvlan interface %s for deleting EIP %s route: %w", macvlanSubIfName, eip, err)
×
NEW
288
                klog.Error(err)
×
NEW
289
                return err
×
290
        }
291

NEW
292
        dst, err := parseEIPDestination(eip)
×
NEW
293
        if err != nil {
×
NEW
294
                return err
×
NEW
295
        }
×
296

NEW
297
        route := &netlink.Route{
×
NEW
298
                LinkIndex: link.Attrs().Index,
×
NEW
299
                Dst:       dst,
×
NEW
300
                Scope:     netlink.SCOPE_LINK,
×
NEW
301
        }
×
NEW
302
        if err := netlink.RouteDel(route); err != nil {
×
NEW
303
                if errors.Is(err, syscall.ESRCH) {
×
NEW
304
                        klog.V(3).Infof("route for EIP %s not found on %s", eip, macvlanSubIfName)
×
NEW
305
                        return nil
×
NEW
306
                }
×
NEW
307
                err = fmt.Errorf("failed to delete route for EIP %s from %s: %w", eip, macvlanSubIfName, err)
×
NEW
308
                klog.Error(err)
×
NEW
309
                return err
×
310
        }
NEW
311
        klog.Infof("deleted route for EIP %s from %s", eip, macvlanSubIfName)
×
NEW
312
        return nil
×
313
}
314

315
// reconcileMacvlanSubnet is the main handler for macvlan subnet events.
316
// It processes subnet add/update/delete events and manages macvlan sub-interfaces accordingly.
317
// This function is called by the macvlan subnet worker with independent retry mechanism.
318
//
319
// Handles all transitions of NadMacvlanMasterAnnotation and CIDR changes:
320
//   - Annotation addition: create macvlan sub-interface
321
//   - Annotation removal: delete macvlan sub-interface
322
//   - Annotation value change: delete old macvlan, create new one with new master
323
//   - CIDR change: delete old macvlan (with old CIDR-based name), create new one
NEW
324
func (c *Controller) reconcileMacvlanSubnet(event *subnetEvent) error {
×
NEW
325
        var oldSubnet, newSubnet *kubeovnv1.Subnet
×
NEW
326
        if event.oldObj != nil {
×
NEW
327
                oldSubnet, _ = event.oldObj.(*kubeovnv1.Subnet)
×
NEW
328
        }
×
NEW
329
        if event.newObj != nil {
×
NEW
330
                newSubnet, _ = event.newObj.(*kubeovnv1.Subnet)
×
NEW
331
        }
×
332

NEW
333
        oldMaster := ""
×
NEW
334
        oldCIDR := ""
×
NEW
335
        if oldSubnet != nil {
×
NEW
336
                oldMaster = oldSubnet.Annotations[util.NadMacvlanMasterAnnotation]
×
NEW
337
                oldCIDR = getIPv4CIDR(oldSubnet)
×
NEW
338
        }
×
NEW
339
        newMaster := ""
×
NEW
340
        newCIDR := ""
×
NEW
341
        if newSubnet != nil {
×
NEW
342
                newMaster = newSubnet.Annotations[util.NadMacvlanMasterAnnotation]
×
NEW
343
                newCIDR = getIPv4CIDR(newSubnet)
×
NEW
344
        }
×
345

346
        // No change in annotation or CIDR, nothing to do
NEW
347
        if oldMaster == newMaster && oldCIDR == newCIDR {
×
NEW
348
                return nil
×
NEW
349
        }
×
350

351
        // Annotation removed or changed, or CIDR changed: cleanup old macvlan interface
NEW
352
        if oldMaster != "" && oldCIDR != "" {
×
NEW
353
                if err := deleteMacvlanSubInterface(oldCIDR); err != nil {
×
NEW
354
                        klog.Errorf("failed to cleanup macvlan for subnet %s (cidr=%s): %v", oldSubnet.Name, oldCIDR, err)
×
NEW
355
                        return err
×
NEW
356
                }
×
357
        }
358

359
        // Annotation added or changed: create new macvlan interface
NEW
360
        if newMaster != "" && newCIDR != "" {
×
NEW
361
                klog.V(3).Infof("creating macvlan sub-interface for subnet %s (master=%s, cidr=%s)", newSubnet.Name, newMaster, newCIDR)
×
NEW
362
                if err := c.createMacvlanSubInterface(newMaster, newCIDR); err != nil {
×
NEW
363
                        klog.Errorf("failed to create macvlan for subnet %s: %v", newSubnet.Name, err)
×
NEW
364
                        return err
×
NEW
365
                }
×
366
        }
367

NEW
368
        return nil
×
369
}
370

371
// getIPv4CIDR extracts IPv4 CIDR from subnet.
372
// For dual-stack, uses util.SplitStringIP to extract IPv4 part.
373
// Returns empty string if subnet is IPv6-only.
374
func getIPv4CIDR(subnet *kubeovnv1.Subnet) string {
1✔
375
        v4, _ := util.SplitStringIP(subnet.Spec.CIDRBlock)
1✔
376
        return v4
1✔
377
}
1✔
378

379
// hasNatGwPodOnLocalNode checks if the NAT GW pod for the given NatGwDp is scheduled on the local node.
380
// NAT GW pod name is generated from NatGwDp field using the pattern: vpc-nat-gw-{natGwDp}-0
381
// Note: This only checks NodeName, not pod phase. The caller should check EIP Ready status
382
// to ensure the NAT GW pod has successfully configured iptables rules.
NEW
383
func (c *Controller) hasNatGwPodOnLocalNode(natGwDp string) bool {
×
NEW
384
        if natGwDp == "" {
×
NEW
385
                return false
×
NEW
386
        }
×
387

388
        // In the current vpc-nat-gw CRD implementation, the StatefulSet has replicas=1,
389
        // so there is only one NAT GW pod running at any time. The pod name is deterministic
390
        // with suffix "-0" (e.g., vpc-nat-gw-mygateway-0).
NEW
391
        podName := util.GenNatGwPodName(natGwDp)
×
NEW
392
        pod, err := c.podsLister.Pods(c.config.PodNamespace).Get(podName)
×
NEW
393
        if err != nil {
×
NEW
394
                klog.V(3).Infof("failed to get NAT GW pod %s: %v", podName, err)
×
NEW
395
                return false
×
NEW
396
        }
×
397

NEW
398
        return pod.Spec.NodeName == c.config.NodeName
×
399
}
400

401
// shouldEnqueueIptablesEip checks if an EIP should be enqueued for route processing.
402
// Returns true if EIP is ready and has ExternalSubnet configured.
403
func shouldEnqueueIptablesEip(eip *kubeovnv1.IptablesEIP) bool {
1✔
404
        return eip.Spec.ExternalSubnet != "" && eip.Status.Ready
1✔
405
}
1✔
406

407
// buildEipRouteInfo builds eipRouteInfo from an EIP object.
408
// Returns nil if the EIP cannot be processed (e.g., subnet not found or no IPv4 CIDR).
NEW
409
func (c *Controller) buildEipRouteInfo(eip *kubeovnv1.IptablesEIP) *eipRouteInfo {
×
NEW
410
        if eip.Spec.V4ip == "" {
×
NEW
411
                return nil
×
NEW
412
        }
×
413

NEW
414
        subnet, err := c.subnetsLister.Get(eip.Spec.ExternalSubnet)
×
NEW
415
        if err != nil {
×
NEW
416
                klog.Errorf("failed to get subnet %s for EIP %s: %v", eip.Spec.ExternalSubnet, eip.Name, err)
×
NEW
417
                return nil
×
NEW
418
        }
×
419

NEW
420
        cidr := getIPv4CIDR(subnet)
×
NEW
421
        if cidr == "" {
×
NEW
422
                klog.Errorf("subnet %s has no IPv4 CIDR for EIP %s, IPv6 is not supported yet", subnet.Name, eip.Name)
×
NEW
423
                return nil
×
NEW
424
        }
×
425

NEW
426
        macvlanName, err := generateMacvlanName(cidr)
×
NEW
427
        if err != nil {
×
NEW
428
                klog.Errorf("failed to generate macvlan name for EIP %s (cidr=%s): %v", eip.Name, cidr, err)
×
NEW
429
                return nil
×
NEW
430
        }
×
431

NEW
432
        return &eipRouteInfo{
×
NEW
433
                eipName:     eip.Name,
×
NEW
434
                v4ip:        eip.Spec.V4ip,
×
NEW
435
                macvlanName: macvlanName,
×
NEW
436
        }
×
437
}
438

439
// enqueueAddIptablesEip handles add events for IptablesEIP.
440
// This is primarily for daemon restart recovery: when the daemon restarts, the informer
441
// triggers Add events for all existing resources. Ready EIPs need to be re-processed
442
// to restore their routes on the node.
443
// Note: For newly created EIPs, they start with Ready=false and become Ready via an
444
// Update event, which is handled by enqueueUpdateIptablesEip.
NEW
445
func (c *Controller) enqueueAddIptablesEip(obj any) {
×
NEW
446
        eip := obj.(*kubeovnv1.IptablesEIP)
×
NEW
447

×
NEW
448
        // Clear deleted mark if EIP exists (handles edge case of stale mark)
×
NEW
449
        deletedEIPs.Delete(eip.Name)
×
NEW
450

×
NEW
451
        if !shouldEnqueueIptablesEip(eip) {
×
NEW
452
                return
×
NEW
453
        }
×
454

NEW
455
        info := c.buildEipRouteInfo(eip)
×
NEW
456
        if info == nil {
×
NEW
457
                return
×
NEW
458
        }
×
459

NEW
460
        klog.V(3).Infof("enqueue add iptables-eip %s for route recovery", eip.Name)
×
NEW
461
        c.iptablesEipQueue.Add(*info)
×
462
}
463

464
// enqueueUpdateIptablesEip handles update events for IptablesEIP.
465
// This is for normal runtime: when an EIP transitions to Ready state (after NAT Gateway
466
// configures iptables rules), add its route to the node.
NEW
467
func (c *Controller) enqueueUpdateIptablesEip(_, newObj any) {
×
NEW
468
        eip := newObj.(*kubeovnv1.IptablesEIP)
×
NEW
469

×
NEW
470
        // Skip EIPs that are being deleted
×
NEW
471
        if eip.DeletionTimestamp != nil {
×
NEW
472
                return
×
NEW
473
        }
×
474

475
        // Clear deleted mark if EIP was recreated with the same name
NEW
476
        deletedEIPs.Delete(eip.Name)
×
NEW
477

×
NEW
478
        if !shouldEnqueueIptablesEip(eip) {
×
NEW
479
                return
×
NEW
480
        }
×
481

NEW
482
        info := c.buildEipRouteInfo(eip)
×
NEW
483
        if info == nil {
×
NEW
484
                return
×
NEW
485
        }
×
486

NEW
487
        klog.V(3).Infof("enqueue update iptables-eip %s", eip.Name)
×
NEW
488
        c.iptablesEipQueue.Add(*info)
×
489
}
490

491
// enqueueDeleteIptablesEip handles delete events for IptablesEIP.
492
// It marks the EIP as deleted to prevent race conditions with queued add events,
493
// then enqueues the V4ip for route deletion with retry support.
NEW
494
func (c *Controller) enqueueDeleteIptablesEip(obj any) {
×
NEW
495
        var eip *kubeovnv1.IptablesEIP
×
NEW
496
        switch t := obj.(type) {
×
NEW
497
        case *kubeovnv1.IptablesEIP:
×
NEW
498
                eip = t
×
NEW
499
        case cache.DeletedFinalStateUnknown:
×
NEW
500
                e, ok := t.Obj.(*kubeovnv1.IptablesEIP)
×
NEW
501
                if !ok {
×
NEW
502
                        klog.Warningf("unexpected object type: %T", t.Obj)
×
NEW
503
                        return
×
NEW
504
                }
×
NEW
505
                eip = e
×
NEW
506
        default:
×
NEW
507
                klog.Warningf("unexpected type: %T", obj)
×
NEW
508
                return
×
509
        }
510

511
        // Mark EIP as deleted to prevent race conditions with queued add events
NEW
512
        deletedEIPs.Store(eip.Name, true)
×
NEW
513

×
NEW
514
        // Build route info - subnet must exist (protected by finalizer)
×
NEW
515
        info := c.buildEipRouteInfo(eip)
×
NEW
516
        if info == nil {
×
NEW
517
                return
×
NEW
518
        }
×
519

NEW
520
        klog.V(3).Infof("enqueue delete iptables-eip %s", eip.Name)
×
NEW
521
        c.iptablesEipDeleteQueue.Add(*info)
×
522
}
523

524
// runIptablesEipDeleteWorker runs the worker for deleting IptablesEIP routes.
NEW
525
func (c *Controller) runIptablesEipDeleteWorker() {
×
NEW
526
        for c.processNextIptablesEipDeleteItem() {
×
NEW
527
        }
×
528
}
529

530
// processNextIptablesEipDeleteItem processes the next delete work item.
NEW
531
func (c *Controller) processNextIptablesEipDeleteItem() bool {
×
NEW
532
        item, shutdown := c.iptablesEipDeleteQueue.Get()
×
NEW
533
        if shutdown {
×
NEW
534
                return false
×
NEW
535
        }
×
536

NEW
537
        err := func(info eipRouteInfo) error {
×
NEW
538
                defer c.iptablesEipDeleteQueue.Done(info)
×
NEW
539
                if err := c.handleDeleteIptablesEipRoute(info); err != nil {
×
NEW
540
                        c.iptablesEipDeleteQueue.AddRateLimited(info)
×
NEW
541
                        return fmt.Errorf("error deleting EIP route for %q: %w, requeuing", info.v4ip, err)
×
NEW
542
                }
×
NEW
543
                c.iptablesEipDeleteQueue.Forget(info)
×
NEW
544
                return nil
×
545
        }(item)
NEW
546
        if err != nil {
×
NEW
547
                klog.Error(err)
×
NEW
548
                return true
×
NEW
549
        }
×
NEW
550
        return true
×
551
}
552

553
// handleDeleteIptablesEipRoute deletes the route for an IptablesEIP.
NEW
554
func (c *Controller) handleDeleteIptablesEipRoute(info eipRouteInfo) error {
×
NEW
555
        klog.Infof("deleting iptables-eip route for %s (v4ip=%s, macvlan=%s)", info.eipName, info.v4ip, info.macvlanName)
×
NEW
556
        return deleteEIPRoute(info.v4ip, info.macvlanName)
×
NEW
557
}
×
558

559
// runIptablesEipWorker runs the worker for syncing IptablesEIP routes.
NEW
560
func (c *Controller) runIptablesEipWorker() {
×
NEW
561
        for c.processNextIptablesEipItem() {
×
NEW
562
        }
×
563
}
564

565
// processNextIptablesEipItem processes the next work item.
NEW
566
func (c *Controller) processNextIptablesEipItem() bool {
×
NEW
567
        item, shutdown := c.iptablesEipQueue.Get()
×
NEW
568
        if shutdown {
×
NEW
569
                return false
×
NEW
570
        }
×
571

NEW
572
        err := func(info eipRouteInfo) error {
×
NEW
573
                defer c.iptablesEipQueue.Done(info)
×
NEW
574
                if err := c.handleAddIptablesEipRoute(info); err != nil {
×
NEW
575
                        c.iptablesEipQueue.AddRateLimited(info)
×
NEW
576
                        return fmt.Errorf("error adding EIP route for %q: %w, requeuing", info.eipName, err)
×
NEW
577
                }
×
NEW
578
                c.iptablesEipQueue.Forget(info)
×
NEW
579
                return nil
×
580
        }(item)
NEW
581
        if err != nil {
×
NEW
582
                klog.Error(err)
×
NEW
583
                return true
×
NEW
584
        }
×
NEW
585
        return true
×
586
}
587

588
// handleAddIptablesEipRoute adds the route for an IptablesEIP.
NEW
589
func (c *Controller) handleAddIptablesEipRoute(info eipRouteInfo) error {
×
NEW
590
        klog.Infof("handling add iptables-eip route for %s", info.eipName)
×
NEW
591

×
NEW
592
        // Check if EIP was deleted - skip if it was to prevent race conditions
×
NEW
593
        if _, deleted := deletedEIPs.Load(info.eipName); deleted {
×
NEW
594
                klog.V(3).Infof("iptables-eip %s was deleted, skipping add", info.eipName)
×
NEW
595
                return nil
×
NEW
596
        }
×
597

NEW
598
        eip, err := c.iptablesEipsLister.Get(info.eipName)
×
NEW
599
        if err != nil {
×
NEW
600
                klog.V(3).Infof("iptables-eip %s not found: %v", info.eipName, err)
×
NEW
601
                return nil
×
NEW
602
        }
×
603

604
        // Only add routes for ready EIPs. An EIP becomes ready after NAT Gateway
605
        // pod successfully configures iptables rules. Before that, adding routes
606
        // would cause traffic to be blackholed.
NEW
607
        if !eip.Status.Ready {
×
NEW
608
                klog.V(3).Infof("iptables-eip %s not ready, skipping route", info.eipName)
×
NEW
609
                return nil
×
NEW
610
        }
×
611

612
        // If NAT GW pod is not on this node, delete routes (if any) and return
NEW
613
        if !c.hasNatGwPodOnLocalNode(eip.Spec.NatGwDp) {
×
NEW
614
                klog.V(3).Infof("NAT GW pod for iptables-eip %s not on local node, deleting routes if exist", info.eipName)
×
NEW
615
                if err := deleteEIPRoute(info.v4ip, info.macvlanName); err != nil {
×
NEW
616
                        klog.V(3).Infof("failed to delete route for EIP %s (may not exist): %v", info.eipName, err)
×
NEW
617
                }
×
NEW
618
                return nil
×
619
        }
620

621
        // Add IPv4 route via macvlan sub-interface
NEW
622
        if err := addEIPRoute(info.v4ip, info.macvlanName); err != nil {
×
NEW
623
                klog.Errorf("failed to add IPv4 route for iptables-eip %s (V4ip=%s, macvlan=%s): %v", info.eipName, info.v4ip, info.macvlanName, err)
×
NEW
624
                return err
×
NEW
625
        }
×
626

NEW
627
        return nil
×
628
}
629

630
// isVpcNatGwPod checks if a pod is a VPC NAT Gateway pod
631
func isVpcNatGwPod(pod *corev1.Pod) bool {
1✔
632
        return pod.Labels[util.VpcNatGatewayLabel] == "true"
1✔
633
}
1✔
634

635
// getNatGwNameFromPod extracts the NAT GW name from a NAT GW pod via label.
636
func getNatGwNameFromPod(pod *corev1.Pod) string {
1✔
637
        return pod.Labels[util.VpcNatGatewayNameLabel]
1✔
638
}
1✔
639

640
// enqueueEipsForNatGw enqueues all EIPs associated with the given NAT GW for route processing.
641
// This is called when a NAT GW pod node changes or phase changes.
642
// The EIP handler will decide whether to add or delete routes based on current state.
NEW
643
func (c *Controller) enqueueEipsForNatGw(natGwName string) {
×
NEW
644
        eips, err := c.iptablesEipsLister.List(labels.SelectorFromSet(labels.Set{
×
NEW
645
                util.VpcNatGatewayNameLabel: natGwName,
×
NEW
646
        }))
×
NEW
647
        if err != nil {
×
NEW
648
                klog.Errorf("failed to list EIPs for NAT GW %s: %v", natGwName, err)
×
NEW
649
                return
×
NEW
650
        }
×
651

NEW
652
        for _, eip := range eips {
×
NEW
653
                if !shouldEnqueueIptablesEip(eip) {
×
NEW
654
                        continue
×
655
                }
NEW
656
                info := c.buildEipRouteInfo(eip)
×
NEW
657
                if info == nil {
×
NEW
658
                        continue
×
659
                }
NEW
660
                klog.Infof("enqueue iptables-eip %s for NAT GW %s pod event", eip.Name, natGwName)
×
NEW
661
                c.iptablesEipQueue.Add(*info)
×
662
        }
663
}
664

665
// handleNatGwPodUpdate handles NAT GW pod update events.
666
// When a NAT GW pod moves to/from this node or phase changes, enqueue all its EIPs for route processing.
667
// The EIP handler will decide whether to add or delete routes based on current pod location.
NEW
668
func (c *Controller) handleNatGwPodUpdate(oldPod, newPod *corev1.Pod) {
×
NEW
669
        if !isVpcNatGwPod(newPod) {
×
NEW
670
                return
×
NEW
671
        }
×
672

NEW
673
        oldNodeName := oldPod.Spec.NodeName
×
NEW
674
        newNodeName := newPod.Spec.NodeName
×
NEW
675

×
NEW
676
        // Skip if pod is not related to this node (neither old nor new node is this node)
×
NEW
677
        if oldNodeName != c.config.NodeName && newNodeName != c.config.NodeName {
×
NEW
678
                return
×
NEW
679
        }
×
680

NEW
681
        natGwName := getNatGwNameFromPod(newPod)
×
NEW
682
        if natGwName == "" {
×
NEW
683
                return
×
NEW
684
        }
×
685

686
        // Case 1: Pod moved from this node to another node - enqueue to delete routes
NEW
687
        if oldNodeName == c.config.NodeName && newNodeName != c.config.NodeName {
×
NEW
688
                klog.Infof("NAT GW pod %s moved from this node to %s, enqueuing EIPs to delete routes", newPod.Name, newNodeName)
×
NEW
689
                c.enqueueEipsForNatGw(natGwName)
×
NEW
690
                return
×
NEW
691
        }
×
692

693
        // Case 2: Pod moved from another node to this node - enqueue to add routes
NEW
694
        if oldNodeName != c.config.NodeName && newNodeName == c.config.NodeName {
×
NEW
695
                if newPod.Status.Phase == corev1.PodRunning {
×
NEW
696
                        klog.Infof("NAT GW pod %s moved to this node, enqueuing EIPs to add routes", newPod.Name)
×
NEW
697
                        c.enqueueEipsForNatGw(natGwName)
×
NEW
698
                }
×
NEW
699
                return
×
700
        }
701

702
        // Case 3: Pod is on this node and phase changed to Running - enqueue to add routes
NEW
703
        if oldPod.Status.Phase != corev1.PodRunning && newPod.Status.Phase == corev1.PodRunning {
×
NEW
704
                klog.Infof("NAT GW pod %s became running on this node, enqueuing EIPs to add routes", newPod.Name)
×
NEW
705
                c.enqueueEipsForNatGw(natGwName)
×
NEW
706
                return
×
NEW
707
        }
×
708

709
        // Case 4: Pod is on this node and phase changed from Running to non-Running
710
        // (e.g., being deleted with DeletionTimestamp set, or terminated) - enqueue to delete routes
NEW
711
        if oldPod.Status.Phase == corev1.PodRunning && newPod.Status.Phase != corev1.PodRunning {
×
NEW
712
                klog.Infof("NAT GW pod %s no longer running on this node (phase: %s), enqueuing EIPs to delete routes", newPod.Name, newPod.Status.Phase)
×
NEW
713
                c.enqueueEipsForNatGw(natGwName)
×
NEW
714
        }
×
715
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc