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

kubeovn / kube-ovn / 21198867681

21 Jan 2026 05:50AM UTC coverage: 22.721% (-0.2%) from 22.889%
21198867681

Pull #6153

github

zbb88888
fix: cni eip cache mem leak

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

37 of 564 new or added lines in 6 files covered. (6.56%)

66 existing lines in 1 file now uncovered.

12336 of 54293 relevant lines covered (22.72%)

0.26 hits per line

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

5.11
/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
// Key design: One macvlan sub-interface per master interface (not per subnet).
15
// Multiple subnets may share the same NAD (Network Attachment Definition) and
16
// thus the same master interface. Creating one interface per master avoids
17
// ARP conflicts that would occur if the same IP were configured on multiple
18
// interfaces.
19
//
20
// Control flow:
21
//  1. Subnet event (subnet with NadMacvlanMasterAnnotation):
22
//     enqueueAddSubnet/enqueueUpdateSubnet/enqueueDeleteSubnet
23
//     → macvlanSubnetQueue → runMacvlanSubnetWorker()
24
//     → reconcileMacvlanSubnet() → createMacvlanSubInterface() / deleteMacvlanSubInterface()
25
//
26
//  2. IptablesEIP event:
27
//     - Add/Update: enqueueAddIptablesEip / enqueueUpdateIptablesEip → iptablesEipQueue
28
//       → runIptablesEipWorker() → syncIptablesEipRoute() → addEIPRoute() / deleteEIPRoute()
29
//     - Delete: enqueueDeleteIptablesEip → iptablesEipDeleteQueue
30
//       → runIptablesEipDeleteWorker() → handleDeleteIptablesEipRoute() → deleteEIPRoute()
31
//
32
//  3. NAT GW Pod event (pod update with VpcNatGatewayLabel):
33
//     enqueueUpdatePod() → handleNatGwPodUpdate() → enqueueEipsForNatGw()
34
//     → syncIptablesEipRoute() → addEIPRoute() / deleteEIPRoute()
35
//
36
// Prerequisites:
37
//   - Subnet controller must set NadMacvlanMasterAnnotation when provider NAD is macvlan type
38
//   - EnableNodeLocalAccessVpcNatGwEIP config flag must be true (default)
39

40
import (
41
        "fmt"
42
        "net"
43
        "syscall"
44

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

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

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

66
// parseEIPDestination parses an EIP address into a host route destination (/32 for IPv4, /128 for IPv6)
67
func parseEIPDestination(eip string) (*net.IPNet, error) {
1✔
68
        ip := net.ParseIP(eip)
1✔
69
        if ip == nil {
2✔
70
                err := fmt.Errorf("invalid EIP address: %s", eip)
1✔
71
                klog.Error(err)
1✔
72
                return nil, err
1✔
73
        }
1✔
74

75
        mask := net.CIDRMask(32, 32)
1✔
76
        if ip.To4() == nil {
2✔
77
                mask = net.CIDRMask(128, 128)
1✔
78
        }
1✔
79
        return &net.IPNet{IP: ip, Mask: mask}, nil
1✔
80
}
81

82
// ensureMacvlanSubInterfaceUp ensures the macvlan sub-interface is up
NEW
83
func ensureMacvlanSubInterfaceUp(link netlink.Link) error {
×
NEW
84
        if link.Attrs().OperState != netlink.OperUp {
×
NEW
85
                if err := netlink.LinkSetUp(link); err != nil {
×
NEW
86
                        err = fmt.Errorf("failed to set link %s up: %w", link.Attrs().Name, err)
×
NEW
87
                        klog.Error(err)
×
NEW
88
                        return err
×
NEW
89
                }
×
90
        }
NEW
91
        return nil
×
92
}
93

94
// createMacvlanSubInterface creates a macvlan sub-interface for node local EIP access.
95
// The macvlan name is derived from the master interface name, ensuring only one
96
// sub-interface is created per master interface even if multiple subnets use it.
NEW
97
func (c *Controller) createMacvlanSubInterface(masterIface string) error {
×
NEW
98
        macvlanName, err := util.GenMacvlanIfaceName(masterIface)
×
NEW
99
        if err != nil {
×
NEW
100
                return fmt.Errorf("createMacvlanSubInterface: %w", err)
×
NEW
101
        }
×
102

103
        // Check if sub-interface already exists
NEW
104
        if link, err := netlink.LinkByName(macvlanName); err == nil {
×
NEW
105
                klog.V(3).Infof("macvlan sub-interface %s already exists", macvlanName)
×
NEW
106
                return ensureMacvlanSubInterfaceUp(link)
×
NEW
107
        }
×
108

NEW
109
        master, err := netlink.LinkByName(masterIface)
×
NEW
110
        if err != nil {
×
NEW
111
                err = fmt.Errorf("failed to get master interface %s: %w", masterIface, err)
×
NEW
112
                klog.Error(err)
×
NEW
113
                return err
×
NEW
114
        }
×
115

NEW
116
        macvlan := &netlink.Macvlan{
×
NEW
117
                LinkAttrs: netlink.LinkAttrs{
×
NEW
118
                        Name:        macvlanName,
×
NEW
119
                        ParentIndex: master.Attrs().Index,
×
NEW
120
                },
×
NEW
121
                Mode: netlink.MACVLAN_MODE_BRIDGE,
×
NEW
122
        }
×
NEW
123

×
NEW
124
        if err := netlink.LinkAdd(macvlan); err != nil {
×
NEW
125
                err = fmt.Errorf("failed to create macvlan sub-interface %s: %w", macvlanName, err)
×
NEW
126
                klog.Error(err)
×
NEW
127
                return err
×
NEW
128
        }
×
129

NEW
130
        if err := netlink.LinkSetUp(macvlan); err != nil {
×
NEW
131
                if delErr := netlink.LinkDel(macvlan); delErr != nil {
×
NEW
132
                        klog.Warningf("failed to cleanup macvlan interface %s after setup failed: %v", macvlanName, delErr)
×
NEW
133
                }
×
NEW
134
                err = fmt.Errorf("failed to set macvlan sub-interface %s up: %w", macvlanName, err)
×
NEW
135
                klog.Error(err)
×
NEW
136
                return err
×
137
        }
138

NEW
139
        klog.Infof("created macvlan sub-interface %s with master %s", macvlanName, masterIface)
×
NEW
140
        return nil
×
141
}
142

143
// deleteMacvlanSubInterface deletes the macvlan sub-interface for the given master interface.
NEW
144
func deleteMacvlanSubInterface(masterIface string) error {
×
NEW
145
        macvlanName, err := util.GenMacvlanIfaceName(masterIface)
×
NEW
146
        if err != nil {
×
NEW
147
                return fmt.Errorf("deleteMacvlanSubInterface: %w", err)
×
NEW
148
        }
×
149

NEW
150
        link, err := netlink.LinkByName(macvlanName)
×
NEW
151
        if err != nil {
×
NEW
152
                if _, ok := err.(netlink.LinkNotFoundError); ok {
×
NEW
153
                        return nil
×
NEW
154
                }
×
NEW
155
                err = fmt.Errorf("failed to get macvlan sub-interface %s: %w", macvlanName, err)
×
NEW
156
                klog.Error(err)
×
NEW
157
                return err
×
158
        }
159

NEW
160
        if err := netlink.LinkDel(link); err != nil {
×
NEW
161
                err = fmt.Errorf("failed to delete macvlan sub-interface %s: %w", macvlanName, err)
×
NEW
162
                klog.Error(err)
×
NEW
163
                return err
×
NEW
164
        }
×
165

NEW
166
        klog.Infof("deleted macvlan sub-interface %s", macvlanName)
×
NEW
167
        return nil
×
168
}
169

170
// addEIPRoute adds a route for EIP via the specified macvlan sub-interface.
NEW
171
func addEIPRoute(eip, macvlanSubIfName string) error {
×
NEW
172
        link, err := netlink.LinkByName(macvlanSubIfName)
×
NEW
173
        if err != nil {
×
NEW
174
                err = fmt.Errorf("failed to get macvlan sub-interface %s for EIP %s: %w", macvlanSubIfName, eip, err)
×
NEW
175
                klog.Error(err)
×
NEW
176
                return err
×
NEW
177
        }
×
178

NEW
179
        dst, err := parseEIPDestination(eip)
×
NEW
180
        if err != nil {
×
NEW
181
                klog.Error(err)
×
NEW
182
                return err
×
NEW
183
        }
×
184

NEW
185
        route := &netlink.Route{
×
NEW
186
                LinkIndex: link.Attrs().Index,
×
NEW
187
                Dst:       dst,
×
NEW
188
                Scope:     netlink.SCOPE_LINK,
×
NEW
189
        }
×
NEW
190

×
NEW
191
        if err := netlink.RouteReplace(route); err != nil {
×
NEW
192
                err = fmt.Errorf("failed to add route for EIP %s via %s: %w", eip, macvlanSubIfName, err)
×
NEW
193
                klog.Error(err)
×
NEW
194
                return err
×
NEW
195
        }
×
196

NEW
197
        klog.Infof("added route for EIP %s via macvlan sub-interface %s", eip, macvlanSubIfName)
×
NEW
198
        return nil
×
199
}
200

201
// deleteEIPRoute deletes the route for EIP from the specified macvlan sub-interface.
NEW
202
func deleteEIPRoute(eip, macvlanSubIfName string) error {
×
NEW
203
        link, err := netlink.LinkByName(macvlanSubIfName)
×
NEW
204
        if err != nil {
×
NEW
205
                if _, ok := err.(netlink.LinkNotFoundError); ok {
×
NEW
206
                        // Interface gone means route is already gone
×
NEW
207
                        return nil
×
NEW
208
                }
×
NEW
209
                err = fmt.Errorf("failed to get macvlan interface %s for deleting EIP %s route: %w", macvlanSubIfName, eip, err)
×
NEW
210
                klog.Error(err)
×
NEW
211
                return err
×
212
        }
213

NEW
214
        dst, err := parseEIPDestination(eip)
×
NEW
215
        if err != nil {
×
NEW
216
                klog.Error(err)
×
NEW
217
                return err
×
NEW
218
        }
×
219

NEW
220
        route := &netlink.Route{
×
NEW
221
                LinkIndex: link.Attrs().Index,
×
NEW
222
                Dst:       dst,
×
NEW
223
                Scope:     netlink.SCOPE_LINK,
×
NEW
224
        }
×
NEW
225
        if err := netlink.RouteDel(route); err != nil {
×
NEW
226
                if errors.Is(err, syscall.ESRCH) {
×
NEW
227
                        klog.V(3).Infof("route for EIP %s not found on %s", eip, macvlanSubIfName)
×
NEW
228
                        return nil
×
NEW
229
                }
×
NEW
230
                err = fmt.Errorf("failed to delete route for EIP %s from %s: %w", eip, macvlanSubIfName, err)
×
NEW
231
                klog.Error(err)
×
NEW
232
                return err
×
233
        }
NEW
234
        klog.Infof("deleted route for EIP %s from %s", eip, macvlanSubIfName)
×
NEW
235
        return nil
×
236
}
237

238
// reconcileMacvlanSubnet is the main handler for macvlan subnet events.
239
// It processes subnet add/update/delete events and manages macvlan sub-interfaces accordingly.
240
// This function is called by the macvlan subnet worker with independent retry mechanism.
241
//
242
// Key design: One macvlan sub-interface per master interface (not per subnet).
243
// Multiple subnets may share the same master interface via the same NAD.
244
// The macvlan interface is created when the first subnet with that master appears,
245
// and deleted only when no more subnets use that master.
246
//
247
// Handles all transitions of NadMacvlanMasterAnnotation:
248
//   - Annotation addition: create macvlan sub-interface (if not exists)
249
//   - Annotation removal: delete macvlan sub-interface (if no other subnet uses it)
250
//   - Annotation value change: handle as removal + addition
NEW
251
func (c *Controller) reconcileMacvlanSubnet(event *subnetEvent) error {
×
NEW
252
        var oldSubnet, newSubnet *kubeovnv1.Subnet
×
NEW
253
        if event.oldObj != nil {
×
NEW
254
                oldSubnet, _ = event.oldObj.(*kubeovnv1.Subnet)
×
NEW
255
        }
×
NEW
256
        if event.newObj != nil {
×
NEW
257
                newSubnet, _ = event.newObj.(*kubeovnv1.Subnet)
×
NEW
258
        }
×
259

NEW
260
        oldMaster := ""
×
NEW
261
        if oldSubnet != nil {
×
NEW
262
                oldMaster = oldSubnet.Annotations[util.NadMacvlanMasterAnnotation]
×
NEW
263
        }
×
NEW
264
        newMaster := ""
×
NEW
265
        if newSubnet != nil {
×
NEW
266
                newMaster = newSubnet.Annotations[util.NadMacvlanMasterAnnotation]
×
NEW
267
        }
×
268

269
        // No change in master annotation, nothing to do
NEW
270
        if oldMaster == newMaster {
×
NEW
271
                return nil
×
NEW
272
        }
×
273

274
        // Master annotation removed or changed: check if we should delete old macvlan
NEW
275
        if oldMaster != "" {
×
NEW
276
                if c.shouldDeleteMacvlanForMaster(oldMaster, oldSubnet.Name) {
×
NEW
277
                        if err := deleteMacvlanSubInterface(oldMaster); err != nil {
×
NEW
278
                                klog.Errorf("failed to cleanup macvlan for master %s (subnet %s): %v", oldMaster, oldSubnet.Name, err)
×
NEW
279
                                return err
×
NEW
280
                        }
×
NEW
281
                } else {
×
NEW
282
                        klog.V(3).Infof("macvlan for master %s still in use by other subnets, skipping delete", oldMaster)
×
NEW
283
                }
×
284
        }
285

286
        // Master annotation added or changed: create new macvlan interface
NEW
287
        if newMaster != "" {
×
NEW
288
                klog.V(3).Infof("creating macvlan sub-interface for subnet %s (master=%s)", newSubnet.Name, newMaster)
×
NEW
289
                if err := c.createMacvlanSubInterface(newMaster); err != nil {
×
NEW
290
                        klog.Errorf("failed to create macvlan for subnet %s (master=%s): %v", newSubnet.Name, newMaster, err)
×
NEW
291
                        return err
×
NEW
292
                }
×
293
        }
294

NEW
295
        return nil
×
296
}
297

298
// shouldDeleteMacvlanForMaster checks if the macvlan interface for the given master
299
// should be deleted. Returns true only if no other subnet uses the same master.
300
// excludeSubnet is the subnet being deleted/changed, which should be excluded from the check.
NEW
301
func (c *Controller) shouldDeleteMacvlanForMaster(master, excludeSubnet string) bool {
×
NEW
302
        subnets, err := c.subnetsLister.List(labels.Everything())
×
NEW
303
        if err != nil {
×
NEW
304
                klog.Errorf("failed to list subnets: %v", err)
×
NEW
305
                return false // Don't delete if we can't check
×
NEW
306
        }
×
307

NEW
308
        for _, subnet := range subnets {
×
NEW
309
                if subnet.Name == excludeSubnet {
×
NEW
310
                        continue
×
311
                }
NEW
312
                if subnet.Annotations[util.NadMacvlanMasterAnnotation] == master {
×
NEW
313
                        return false // Another subnet uses this master
×
NEW
314
                }
×
315
        }
NEW
316
        return true
×
317
}
318

319
// hasNatGwPodOnLocalNode checks if the NAT GW pod for the given NatGwDp is scheduled on the local node.
320
// NAT GW pod name is generated from NatGwDp field using the pattern: vpc-nat-gw-{natGwDp}-0
321
// Note: This only checks NodeName, not pod phase. The caller should check EIP Ready status
322
// to ensure the NAT GW pod has successfully configured iptables rules.
NEW
323
func (c *Controller) hasNatGwPodOnLocalNode(natGwDp string) bool {
×
NEW
324
        if natGwDp == "" {
×
NEW
325
                return false
×
NEW
326
        }
×
327

328
        // In the current vpc-nat-gw CRD implementation, the StatefulSet has replicas=1,
329
        // so there is only one NAT GW pod running at any time. The pod name is deterministic
330
        // with suffix "-0" (e.g., vpc-nat-gw-mygateway-0).
NEW
331
        podName := util.GenNatGwPodName(natGwDp)
×
NEW
332
        pod, err := c.podsLister.Pods(c.config.PodNamespace).Get(podName)
×
NEW
333
        if err != nil {
×
NEW
334
                klog.V(3).Infof("failed to get NAT GW pod %s: %v", podName, err)
×
NEW
335
                return false
×
NEW
336
        }
×
337

NEW
338
        return pod.Spec.NodeName == c.config.NodeName
×
339
}
340

341
// shouldEnqueueIptablesEip checks if an EIP should be enqueued for route processing.
342
// Returns true if EIP is ready and has ExternalSubnet configured.
343
func shouldEnqueueIptablesEip(eip *kubeovnv1.IptablesEIP) bool {
1✔
344
        return eip.Spec.ExternalSubnet != "" && eip.Status.Ready
1✔
345
}
1✔
346

347
// buildEipRouteInfo builds eipRouteInfo from an EIP object.
348
// Returns nil if the EIP cannot be processed (e.g., subnet not found or no master annotation).
NEW
349
func (c *Controller) buildEipRouteInfo(eip *kubeovnv1.IptablesEIP) *eipRouteInfo {
×
NEW
350
        if eip.Spec.V4ip == "" {
×
NEW
351
                return nil
×
NEW
352
        }
×
353

NEW
354
        subnet, err := c.subnetsLister.Get(eip.Spec.ExternalSubnet)
×
NEW
355
        if err != nil {
×
NEW
356
                klog.Errorf("failed to get subnet %s for EIP %s: %v", eip.Spec.ExternalSubnet, eip.Name, err)
×
NEW
357
                return nil
×
NEW
358
        }
×
359

NEW
360
        master := subnet.Annotations[util.NadMacvlanMasterAnnotation]
×
NEW
361
        if master == "" {
×
NEW
362
                klog.V(3).Infof("subnet %s has no macvlan master annotation for EIP %s", subnet.Name, eip.Name)
×
NEW
363
                return nil
×
NEW
364
        }
×
365

NEW
366
        macvlanName, err := util.GenMacvlanIfaceName(master)
×
NEW
367
        if err != nil {
×
NEW
368
                klog.Errorf("failed to generate macvlan name for EIP %s (master=%s): %v", eip.Name, master, err)
×
NEW
369
                return nil
×
NEW
370
        }
×
371

NEW
372
        return &eipRouteInfo{
×
NEW
373
                eipName:     eip.Name,
×
NEW
374
                v4ip:        eip.Spec.V4ip,
×
NEW
375
                macvlanName: macvlanName,
×
NEW
376
        }
×
377
}
378

379
// enqueueAddIptablesEip handles add events for IptablesEIP.
380
// This is primarily for daemon restart recovery: when the daemon restarts, the informer
381
// triggers Add events for all existing resources. Ready EIPs need to be re-processed
382
// to restore their routes on the node.
383
// Note: For newly created EIPs, they start with Ready=false and become Ready via an
384
// Update event, which is handled by enqueueUpdateIptablesEip.
NEW
385
func (c *Controller) enqueueAddIptablesEip(obj any) {
×
NEW
386
        eip := obj.(*kubeovnv1.IptablesEIP)
×
NEW
387

×
NEW
388
        // Clear deleted mark if EIP exists (handles edge case of stale mark)
×
NEW
389
        c.deletedEIPs.Delete(eip.Name)
×
NEW
390

×
NEW
391
        if !shouldEnqueueIptablesEip(eip) {
×
NEW
392
                return
×
NEW
393
        }
×
394

NEW
395
        info := c.buildEipRouteInfo(eip)
×
NEW
396
        if info == nil {
×
NEW
397
                return
×
NEW
398
        }
×
399

NEW
400
        klog.V(3).Infof("enqueue add iptables-eip %s for route recovery", eip.Name)
×
NEW
401
        c.iptablesEipQueue.Add(*info)
×
402
}
403

404
// enqueueUpdateIptablesEip handles update events for IptablesEIP.
405
// This is for normal runtime: when an EIP transitions to Ready state (after NAT Gateway
406
// configures iptables rules), add its route to the node.
NEW
407
func (c *Controller) enqueueUpdateIptablesEip(_, newObj any) {
×
NEW
408
        eip := newObj.(*kubeovnv1.IptablesEIP)
×
NEW
409

×
NEW
410
        // Skip EIPs that are being deleted
×
NEW
411
        if eip.DeletionTimestamp != nil {
×
NEW
412
                return
×
NEW
413
        }
×
414

415
        // Clear deleted mark if EIP was recreated with the same name
NEW
416
        c.deletedEIPs.Delete(eip.Name)
×
NEW
417

×
NEW
418
        if !shouldEnqueueIptablesEip(eip) {
×
NEW
419
                return
×
NEW
420
        }
×
421

NEW
422
        info := c.buildEipRouteInfo(eip)
×
NEW
423
        if info == nil {
×
NEW
424
                return
×
NEW
425
        }
×
426

NEW
427
        klog.V(3).Infof("enqueue update iptables-eip %s", eip.Name)
×
NEW
428
        c.iptablesEipQueue.Add(*info)
×
429
}
430

431
// enqueueDeleteIptablesEip handles delete events for IptablesEIP.
432
// It marks the EIP as deleted to prevent race conditions with queued add events,
433
// then enqueues the V4ip for route deletion with retry support.
NEW
434
func (c *Controller) enqueueDeleteIptablesEip(obj any) {
×
NEW
435
        var eip *kubeovnv1.IptablesEIP
×
NEW
436
        switch t := obj.(type) {
×
NEW
437
        case *kubeovnv1.IptablesEIP:
×
NEW
438
                eip = t
×
NEW
439
        case cache.DeletedFinalStateUnknown:
×
NEW
440
                e, ok := t.Obj.(*kubeovnv1.IptablesEIP)
×
NEW
441
                if !ok {
×
NEW
442
                        klog.Warningf("unexpected object type: %T", t.Obj)
×
NEW
443
                        return
×
NEW
444
                }
×
NEW
445
                eip = e
×
NEW
446
        default:
×
NEW
447
                klog.Warningf("unexpected type: %T", obj)
×
NEW
448
                return
×
449
        }
450

451
        // Mark EIP as deleted to prevent race conditions with queued add events
NEW
452
        c.deletedEIPs.Store(eip.Name, true)
×
NEW
453

×
NEW
454
        // Build route info - subnet must exist (protected by finalizer)
×
NEW
455
        info := c.buildEipRouteInfo(eip)
×
NEW
456
        if info == nil {
×
NEW
457
                return
×
NEW
458
        }
×
459

NEW
460
        klog.V(3).Infof("enqueue delete iptables-eip %s", eip.Name)
×
NEW
461
        c.iptablesEipDeleteQueue.Add(*info)
×
462
}
463

464
// runIptablesEipDeleteWorker runs the worker for deleting IptablesEIP routes.
NEW
465
func (c *Controller) runIptablesEipDeleteWorker() {
×
NEW
466
        for c.processNextIptablesEipDeleteItem() {
×
NEW
467
        }
×
468
}
469

470
// processNextIptablesEipDeleteItem processes the next delete work item.
NEW
471
func (c *Controller) processNextIptablesEipDeleteItem() bool {
×
NEW
472
        item, shutdown := c.iptablesEipDeleteQueue.Get()
×
NEW
473
        if shutdown {
×
NEW
474
                return false
×
NEW
475
        }
×
476

NEW
477
        err := func(info eipRouteInfo) error {
×
NEW
478
                defer c.iptablesEipDeleteQueue.Done(info)
×
NEW
479
                if err := c.handleDeleteIptablesEipRoute(info); err != nil {
×
NEW
480
                        c.iptablesEipDeleteQueue.AddRateLimited(info)
×
NEW
481
                        return fmt.Errorf("error deleting EIP route for %q: %w, requeuing", info.v4ip, err)
×
NEW
482
                }
×
NEW
483
                c.iptablesEipDeleteQueue.Forget(info)
×
NEW
484
                // Clean up the deleted mark after successful route deletion to prevent memory leak.
×
NEW
485
                // This is safe because:
×
NEW
486
                // 1. The route has been deleted successfully
×
NEW
487
                // 2. Any new EIP with the same name will trigger Add/Update events which clear the mark
×
NEW
488
                c.deletedEIPs.Delete(info.eipName)
×
NEW
489
                return nil
×
490
        }(item)
NEW
491
        if err != nil {
×
NEW
492
                klog.Error(err)
×
NEW
493
                return true
×
NEW
494
        }
×
NEW
495
        return true
×
496
}
497

498
// handleDeleteIptablesEipRoute deletes the route for an IptablesEIP.
NEW
499
func (c *Controller) handleDeleteIptablesEipRoute(info eipRouteInfo) error {
×
NEW
500
        klog.Infof("deleting iptables-eip route for %s (v4ip=%s, macvlan=%s)", info.eipName, info.v4ip, info.macvlanName)
×
NEW
501
        return deleteEIPRoute(info.v4ip, info.macvlanName)
×
NEW
502
}
×
503

504
// runIptablesEipWorker runs the worker for syncing IptablesEIP routes.
NEW
505
func (c *Controller) runIptablesEipWorker() {
×
NEW
506
        for c.processNextIptablesEipItem() {
×
NEW
507
        }
×
508
}
509

510
// processNextIptablesEipItem processes the next work item.
NEW
511
func (c *Controller) processNextIptablesEipItem() bool {
×
NEW
512
        item, shutdown := c.iptablesEipQueue.Get()
×
NEW
513
        if shutdown {
×
NEW
514
                return false
×
NEW
515
        }
×
516

NEW
517
        err := func(info eipRouteInfo) error {
×
NEW
518
                defer c.iptablesEipQueue.Done(info)
×
NEW
519
                if err := c.syncIptablesEipRoute(info); err != nil {
×
NEW
520
                        c.iptablesEipQueue.AddRateLimited(info)
×
NEW
521
                        return fmt.Errorf("error syncing EIP route for %q: %w, requeuing", info.eipName, err)
×
NEW
522
                }
×
NEW
523
                c.iptablesEipQueue.Forget(info)
×
NEW
524
                return nil
×
525
        }(item)
NEW
526
        if err != nil {
×
NEW
527
                klog.Error(err)
×
NEW
528
                return true
×
NEW
529
        }
×
NEW
530
        return true
×
531
}
532

533
// syncIptablesEipRoute syncs the route for an IptablesEIP.
534
// It adds the route if NAT GW pod is on this node, or deletes the route if not.
NEW
535
func (c *Controller) syncIptablesEipRoute(info eipRouteInfo) error {
×
NEW
536
        klog.Infof("syncing iptables-eip route for %s", info.eipName)
×
NEW
537

×
NEW
538
        // Check if EIP was deleted - skip if it was to prevent race conditions
×
NEW
539
        if _, deleted := c.deletedEIPs.Load(info.eipName); deleted {
×
NEW
540
                klog.V(3).Infof("iptables-eip %s was deleted, skipping add", info.eipName)
×
NEW
541
                return nil
×
NEW
542
        }
×
543

NEW
544
        eip, err := c.iptablesEipsLister.Get(info.eipName)
×
NEW
545
        if err != nil {
×
NEW
546
                klog.V(3).Infof("iptables-eip %s not found: %v", info.eipName, err)
×
NEW
547
                return nil
×
NEW
548
        }
×
549

550
        // Only add routes for ready EIPs. An EIP becomes ready after NAT Gateway
551
        // pod successfully configures iptables rules. Before that, adding routes
552
        // would cause traffic to be blackholed.
NEW
553
        if !eip.Status.Ready {
×
NEW
554
                klog.V(3).Infof("iptables-eip %s not ready, skipping route", info.eipName)
×
NEW
555
                return nil
×
NEW
556
        }
×
557

558
        // If NAT GW pod is not on this node, delete routes (if any) and return
NEW
559
        if !c.hasNatGwPodOnLocalNode(eip.Spec.NatGwDp) {
×
NEW
560
                klog.V(3).Infof("NAT GW pod for iptables-eip %s not on local node, deleting routes if exist", info.eipName)
×
NEW
561
                if err := deleteEIPRoute(info.v4ip, info.macvlanName); err != nil {
×
NEW
562
                        klog.V(3).Infof("failed to delete route for EIP %s (may not exist): %v", info.eipName, err)
×
NEW
563
                }
×
NEW
564
                return nil
×
565
        }
566

567
        // Add IPv4 route via macvlan sub-interface
NEW
568
        if err := addEIPRoute(info.v4ip, info.macvlanName); err != nil {
×
NEW
569
                klog.Errorf("failed to add IPv4 route for iptables-eip %s (V4ip=%s, macvlan=%s): %v", info.eipName, info.v4ip, info.macvlanName, err)
×
NEW
570
                return err
×
NEW
571
        }
×
572

NEW
573
        return nil
×
574
}
575

576
// isVpcNatGwPod checks if a pod is a VPC NAT Gateway pod
577
func isVpcNatGwPod(pod *corev1.Pod) bool {
1✔
578
        return pod.Labels[util.VpcNatGatewayLabel] == "true"
1✔
579
}
1✔
580

581
// getNatGwNameFromPod extracts the NAT GW name from a NAT GW pod via label.
582
func getNatGwNameFromPod(pod *corev1.Pod) string {
1✔
583
        return pod.Labels[util.VpcNatGatewayNameLabel]
1✔
584
}
1✔
585

586
// enqueueEipsForNatGw enqueues all EIPs associated with the given NAT GW for route processing.
587
// This is called when a NAT GW pod node changes or phase changes.
588
// The EIP handler will decide whether to add or delete routes based on current state.
NEW
589
func (c *Controller) enqueueEipsForNatGw(natGwName string) {
×
NEW
590
        eips, err := c.iptablesEipsLister.List(labels.SelectorFromSet(labels.Set{
×
NEW
591
                util.VpcNatGatewayNameLabel: natGwName,
×
NEW
592
        }))
×
NEW
593
        if err != nil {
×
NEW
594
                klog.Errorf("failed to list EIPs for NAT GW %s: %v", natGwName, err)
×
NEW
595
                return
×
NEW
596
        }
×
597

NEW
598
        for _, eip := range eips {
×
NEW
599
                if !shouldEnqueueIptablesEip(eip) {
×
NEW
600
                        continue
×
601
                }
NEW
602
                info := c.buildEipRouteInfo(eip)
×
NEW
603
                if info == nil {
×
NEW
604
                        continue
×
605
                }
NEW
606
                klog.Infof("enqueue iptables-eip %s for NAT GW %s pod event", eip.Name, natGwName)
×
NEW
607
                c.iptablesEipQueue.Add(*info)
×
608
        }
609
}
610

611
// handleNatGwPodUpdate handles NAT GW pod update events.
612
// When a NAT GW pod moves to/from this node or phase changes, enqueue all its EIPs for route processing.
613
// The EIP handler will decide whether to add or delete routes based on current pod location.
NEW
614
func (c *Controller) handleNatGwPodUpdate(oldPod, newPod *corev1.Pod) {
×
NEW
615
        if !isVpcNatGwPod(newPod) {
×
NEW
616
                return
×
NEW
617
        }
×
618

NEW
619
        oldNodeName := oldPod.Spec.NodeName
×
NEW
620
        newNodeName := newPod.Spec.NodeName
×
NEW
621

×
NEW
622
        // Skip if pod is not related to this node (neither old nor new node is this node)
×
NEW
623
        if oldNodeName != c.config.NodeName && newNodeName != c.config.NodeName {
×
NEW
624
                return
×
NEW
625
        }
×
626

NEW
627
        natGwName := getNatGwNameFromPod(newPod)
×
NEW
628
        if natGwName == "" {
×
NEW
629
                return
×
NEW
630
        }
×
631

632
        // Case 1: Pod moved from this node to another node - enqueue to delete routes
NEW
633
        if oldNodeName == c.config.NodeName && newNodeName != c.config.NodeName {
×
NEW
634
                klog.Infof("NAT GW pod %s moved from this node to %s, enqueuing EIPs to delete routes", newPod.Name, newNodeName)
×
NEW
635
                c.enqueueEipsForNatGw(natGwName)
×
NEW
636
                return
×
NEW
637
        }
×
638

639
        // Case 2: Pod moved from another node to this node - enqueue to add routes
NEW
640
        if oldNodeName != c.config.NodeName && newNodeName == c.config.NodeName {
×
NEW
641
                if newPod.Status.Phase == corev1.PodRunning {
×
NEW
642
                        klog.Infof("NAT GW pod %s moved to this node, enqueuing EIPs to add routes", newPod.Name)
×
NEW
643
                        c.enqueueEipsForNatGw(natGwName)
×
NEW
644
                }
×
NEW
645
                return
×
646
        }
647

648
        // Case 3: Pod is on this node and phase changed to Running - enqueue to add routes
NEW
649
        if oldPod.Status.Phase != corev1.PodRunning && newPod.Status.Phase == corev1.PodRunning {
×
NEW
650
                klog.Infof("NAT GW pod %s became running on this node, enqueuing EIPs to add routes", newPod.Name)
×
NEW
651
                c.enqueueEipsForNatGw(natGwName)
×
NEW
652
                return
×
NEW
653
        }
×
654

655
        // Case 4: Pod is on this node and phase changed from Running to non-Running
656
        // (e.g., being deleted with DeletionTimestamp set, or terminated) - enqueue to delete routes
NEW
657
        if oldPod.Status.Phase == corev1.PodRunning && newPod.Status.Phase != corev1.PodRunning {
×
NEW
658
                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
659
                c.enqueueEipsForNatGw(natGwName)
×
NEW
660
        }
×
661
}
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