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

kubeovn / kube-ovn / 24830126592

23 Apr 2026 10:25AM UTC coverage: 24.853% (+0.04%) from 24.817%
24830126592

push

github

web-flow
fix(controller): keep VM LSP port-group memberships when a sibling pod is alive (#6666)

* fix(controller): keep VM LSP port-group memberships when a sibling pod is alive

For VM pods with EnableKeepVMIP=true a single LSP is shared across every
virt-launcher pod of the VM (ExternalIDs["pod"] is keyed by VM name, not
by pod name). When kubernetes GCs a completed virt-launcher pod — most
commonly the source of a successful live migration — handleDeletePod
finds that LSP, and because isVMToDel is false it calls
RemovePortFromPortGroups(port.Name) with no port-group names, which
strips the LSP from every port group (node, subnet, security group,
network policy).

The LSP is still in use by the currently running destination pod, so the
active VM pod loses its memberships and connectivity until
kube-ovn-controller is restarted and all pods are re-synced.

Before stripping port-group memberships in the keepIPCR branch, check
whether another alive virt-launcher pod exists for the same VMI. If so,
the memberships belong to that sibling and must be preserved.

Fixes #6665

Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>

* fix(controller): rewrite vm lsp cleanup if-else chain as switch

Satisfies gocritic's ifElseChain rule; no behavior change.

Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com>

---------

Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com>
Co-authored-by: Mengxin Liu <liumengxinfly@gmail.com>

11 of 29 new or added lines in 1 file covered. (37.93%)

2 existing lines in 1 file now uncovered.

14067 of 56601 relevant lines covered (24.85%)

0.29 hits per line

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

15.78
/pkg/controller/pod.go
1
package controller
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "errors"
7
        "fmt"
8
        "maps"
9
        "net"
10
        "slices"
11
        "strconv"
12
        "strings"
13
        "sync"
14
        "time"
15

16
        nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
17
        nadutils "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils"
18
        "github.com/scylladb/go-set/strset"
19
        appsv1 "k8s.io/api/apps/v1"
20
        v1 "k8s.io/api/core/v1"
21
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
22
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23
        "k8s.io/apimachinery/pkg/labels"
24
        "k8s.io/apimachinery/pkg/types"
25
        utilruntime "k8s.io/apimachinery/pkg/util/runtime"
26
        "k8s.io/client-go/kubernetes"
27
        "k8s.io/client-go/tools/cache"
28
        "k8s.io/klog/v2"
29
        kubevirtv1 "kubevirt.io/api/core/v1"
30

31
        kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
32
        "github.com/kubeovn/kube-ovn/pkg/ipam"
33
        "github.com/kubeovn/kube-ovn/pkg/ovs"
34
        "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb"
35
        "github.com/kubeovn/kube-ovn/pkg/request"
36
        "github.com/kubeovn/kube-ovn/pkg/util"
37
)
38

39
type NamedPort struct {
40
        mutex sync.RWMutex
41
        // first key is namespace, second key is portName
42
        namedPortMap map[string]map[string]*util.NamedPortInfo
43
}
44

45
func NewNamedPort() *NamedPort {
1✔
46
        return &NamedPort{
1✔
47
                mutex:        sync.RWMutex{},
1✔
48
                namedPortMap: map[string]map[string]*util.NamedPortInfo{},
1✔
49
        }
1✔
50
}
1✔
51

52
func (n *NamedPort) AddNamedPortByPod(pod *v1.Pod) {
1✔
53
        n.mutex.Lock()
1✔
54
        defer n.mutex.Unlock()
1✔
55
        ns := pod.Namespace
1✔
56
        podName := pod.Name
1✔
57

1✔
58
        restartableInitContainers := make([]v1.Container, 0, len(pod.Spec.InitContainers))
1✔
59
        for i := range pod.Spec.InitContainers {
2✔
60
                if pod.Spec.InitContainers[i].RestartPolicy != nil &&
1✔
61
                        *pod.Spec.InitContainers[i].RestartPolicy == v1.ContainerRestartPolicyAlways {
2✔
62
                        restartableInitContainers = append(restartableInitContainers, pod.Spec.InitContainers[i])
1✔
63
                }
1✔
64
        }
65

66
        containers := slices.Concat(restartableInitContainers, pod.Spec.Containers)
1✔
67
        if len(containers) == 0 {
1✔
68
                return
×
69
        }
×
70

71
        for _, container := range containers {
2✔
72
                if len(container.Ports) == 0 {
1✔
73
                        continue
×
74
                }
75

76
                for _, port := range container.Ports {
2✔
77
                        if port.Name == "" || port.ContainerPort == 0 {
1✔
78
                                continue
×
79
                        }
80

81
                        if _, ok := n.namedPortMap[ns]; ok {
2✔
82
                                if _, ok := n.namedPortMap[ns][port.Name]; ok {
1✔
83
                                        if n.namedPortMap[ns][port.Name].PortID == port.ContainerPort {
×
84
                                                n.namedPortMap[ns][port.Name].Pods.Add(podName)
×
85
                                        } else {
×
86
                                                klog.Warningf("named port %s has already been defined with portID %d",
×
87
                                                        port.Name, n.namedPortMap[ns][port.Name].PortID)
×
88
                                        }
×
89
                                        continue
×
90
                                }
91
                        } else {
1✔
92
                                n.namedPortMap[ns] = make(map[string]*util.NamedPortInfo)
1✔
93
                        }
1✔
94
                        n.namedPortMap[ns][port.Name] = &util.NamedPortInfo{
1✔
95
                                PortID: port.ContainerPort,
1✔
96
                                Pods:   strset.New(podName),
1✔
97
                        }
1✔
98
                }
99
        }
100
}
101

102
func (n *NamedPort) DeleteNamedPortByPod(pod *v1.Pod) {
1✔
103
        n.mutex.Lock()
1✔
104
        defer n.mutex.Unlock()
1✔
105

1✔
106
        ns := pod.Namespace
1✔
107
        podName := pod.Name
1✔
108

1✔
109
        restartableInitContainers := make([]v1.Container, 0, len(pod.Spec.InitContainers))
1✔
110
        for i := range pod.Spec.InitContainers {
2✔
111
                if pod.Spec.InitContainers[i].RestartPolicy != nil &&
1✔
112
                        *pod.Spec.InitContainers[i].RestartPolicy == v1.ContainerRestartPolicyAlways {
2✔
113
                        restartableInitContainers = append(restartableInitContainers, pod.Spec.InitContainers[i])
1✔
114
                }
1✔
115
        }
116

117
        containers := slices.Concat(restartableInitContainers, pod.Spec.Containers)
1✔
118
        if len(containers) == 0 {
1✔
119
                return
×
120
        }
×
121

122
        for _, container := range containers {
2✔
123
                if len(container.Ports) == 0 {
1✔
124
                        continue
×
125
                }
126

127
                for _, port := range container.Ports {
2✔
128
                        if port.Name == "" {
1✔
129
                                continue
×
130
                        }
131

132
                        if _, ok := n.namedPortMap[ns]; !ok {
1✔
133
                                continue
×
134
                        }
135

136
                        if _, ok := n.namedPortMap[ns][port.Name]; !ok {
1✔
137
                                continue
×
138
                        }
139

140
                        if !n.namedPortMap[ns][port.Name].Pods.Has(podName) {
1✔
141
                                continue
×
142
                        }
143

144
                        n.namedPortMap[ns][port.Name].Pods.Remove(podName)
1✔
145
                        if n.namedPortMap[ns][port.Name].Pods.Size() == 0 {
2✔
146
                                delete(n.namedPortMap[ns], port.Name)
1✔
147
                                if len(n.namedPortMap[ns]) == 0 {
2✔
148
                                        delete(n.namedPortMap, ns)
1✔
149
                                }
1✔
150
                        }
151
                }
152
        }
153
}
154

155
func (n *NamedPort) GetNamedPortByNs(namespace string) map[string]*util.NamedPortInfo {
1✔
156
        n.mutex.RLock()
1✔
157
        defer n.mutex.RUnlock()
1✔
158

1✔
159
        if result, ok := n.namedPortMap[namespace]; ok {
2✔
160
                klog.V(3).Infof("namespace %s has %d named ports", namespace, len(result))
1✔
161
                return maps.Clone(result)
1✔
162
        }
1✔
163
        return nil
1✔
164
}
165

166
func isPodAlive(p *v1.Pod) bool {
1✔
167
        if !p.DeletionTimestamp.IsZero() && p.DeletionGracePeriodSeconds != nil {
1✔
168
                now := time.Now()
×
169
                deletionTime := p.DeletionTimestamp.Time
×
170
                gracePeriod := time.Duration(*p.DeletionGracePeriodSeconds) * time.Second
×
171
                if now.After(deletionTime.Add(gracePeriod)) {
×
172
                        return false
×
173
                }
×
174
        }
175
        return isPodStatusPhaseAlive(p)
1✔
176
}
177

178
func isPodStatusPhaseAlive(p *v1.Pod) bool {
1✔
179
        if p.Status.Phase == v1.PodSucceeded && p.Spec.RestartPolicy != v1.RestartPolicyAlways {
2✔
180
                return false
1✔
181
        }
1✔
182

183
        if p.Status.Phase == v1.PodFailed && p.Spec.RestartPolicy == v1.RestartPolicyNever {
1✔
184
                return false
×
185
        }
×
186

187
        if p.Status.Phase == v1.PodFailed && p.Status.Reason == "Evicted" {
1✔
188
                return false
×
189
        }
×
190
        return true
1✔
191
}
192

193
func (c *Controller) enqueueAddPod(obj any) {
×
194
        p := obj.(*v1.Pod)
×
195
        if p.Spec.HostNetwork {
×
196
                return
×
197
        }
×
198

199
        // Pod might be targeted by manual endpoints and we need to recompute its port mappings
200
        c.enqueueStaticEndpointUpdateInNamespace(p.Namespace)
×
201

×
202
        // TODO: we need to find a way to reduce duplicated np added to the queue
×
203
        if c.config.EnableNP {
×
204
                c.namedPort.AddNamedPortByPod(p)
×
205
                if p.Status.PodIP != "" {
×
206
                        for _, np := range c.podMatchNetworkPolicies(p) {
×
207
                                klog.V(3).Infof("enqueue update network policy %s", np)
×
208
                                c.updateNpQueue.Add(np)
×
209
                        }
×
210
                }
211
        }
212

213
        key := cache.MetaObjectToName(p).String()
×
214
        if !isPodAlive(p) {
×
215
                isStateful, statefulSetName, statefulSetUID := isStatefulSetPod(p)
×
216
                isVMPod, vmName := isVMPod(p)
×
217
                if isStateful || (isVMPod && c.config.EnableKeepVMIP) {
×
218
                        if isStateful && isStatefulSetPodToDel(c.config.KubeClient, p, statefulSetName, statefulSetUID) {
×
219
                                klog.V(3).Infof("enqueue delete pod %s", key)
×
220
                                c.deletingPodObjMap.Store(key, p)
×
221
                                c.deletePodQueue.Add(key)
×
222
                        }
×
223
                        if isVMPod && c.isVMToDel(p, vmName) {
×
224
                                klog.V(3).Infof("enqueue delete pod %s", key)
×
225
                                c.deletingPodObjMap.Store(key, p)
×
226
                                c.deletePodQueue.Add(key)
×
227
                        }
×
228
                } else {
×
229
                        klog.V(3).Infof("enqueue delete pod %s", key)
×
230
                        c.deletingPodObjMap.Store(key, p)
×
231
                        c.deletePodQueue.Add(key)
×
232
                }
×
233
                return
×
234
        }
235

236
        need, err := c.podNeedSync(p)
×
237
        if err != nil {
×
238
                klog.Errorf("invalid pod net: %v", err)
×
239
                return
×
240
        }
×
241
        if need {
×
242
                klog.Infof("enqueue add pod %s", key)
×
243
                c.addOrUpdatePodQueue.Add(key)
×
244
        }
×
245

246
        if err = c.handlePodEventForVpcEgressGateway(p); err != nil {
×
247
                klog.Errorf("failed to handle pod event for vpc egress gateway: %v", err)
×
248
        }
×
249
}
250

251
func (c *Controller) getNsLabels(nsName, podName string) map[string]string {
×
252
        podNs, err := c.namespacesLister.Get(nsName)
×
253
        if err != nil {
×
254
                if k8serrors.IsNotFound(err) {
×
255
                        klog.V(3).Infof("namespace %s not found for pod %s, use empty ns labels", nsName, podName)
×
256
                } else {
×
257
                        klog.Errorf("failed to get namespace %s: %v, use empty ns labels", nsName, err)
×
258
                }
×
259
                return nil
×
260
        }
261
        return podNs.Labels
×
262
}
263

264
func (c *Controller) enqueueDeletePod(obj any) {
×
265
        var p *v1.Pod
×
266
        switch t := obj.(type) {
×
267
        case *v1.Pod:
×
268
                p = t
×
269
        case cache.DeletedFinalStateUnknown:
×
270
                pod, ok := t.Obj.(*v1.Pod)
×
271
                if !ok {
×
272
                        klog.Warningf("unexpected object type: %T", t.Obj)
×
273
                        return
×
274
                }
×
275
                p = pod
×
276
        default:
×
277
                klog.Warningf("unexpected type: %T", obj)
×
278
                return
×
279
        }
280

281
        if p.Spec.HostNetwork {
×
282
                return
×
283
        }
×
284

285
        // Pod might be targeted by manual endpoints and we need to recompute its port mappings
286
        c.enqueueStaticEndpointUpdateInNamespace(p.Namespace)
×
287

×
288
        if c.config.EnableNP {
×
289
                c.namedPort.DeleteNamedPortByPod(p)
×
290
                for _, np := range c.podMatchNetworkPolicies(p) {
×
291
                        c.updateNpQueue.Add(np)
×
292
                }
×
293
        }
294

295
        if c.config.EnableANP {
×
296
                nsLabels := c.getNsLabels(p.Namespace, p.Name)
×
297
                c.updateAnpsByLabelsMatch(nsLabels, p.Labels)
×
298
                c.updateCnpsByLabelsMatch(nsLabels, p.Labels)
×
299
        }
×
300

301
        key := cache.MetaObjectToName(p).String()
×
302
        klog.Infof("enqueue delete pod %s", key)
×
303
        c.deletingPodObjMap.Store(key, p)
×
304
        c.deletePodQueue.Add(key)
×
305
}
306

307
func (c *Controller) enqueueUpdatePod(oldObj, newObj any) {
×
308
        oldPod := oldObj.(*v1.Pod)
×
309
        newPod := newObj.(*v1.Pod)
×
310

×
311
        // Pod might be targeted by manual endpoints and we need to recompute its port mappings
×
312
        c.enqueueStaticEndpointUpdateInNamespace(oldPod.Namespace)
×
313

×
314
        if oldPod.Annotations[util.AAPsAnnotation] != "" || newPod.Annotations[util.AAPsAnnotation] != "" {
×
315
                oldAAPs := strings.Split(oldPod.Annotations[util.AAPsAnnotation], ",")
×
316
                newAAPs := strings.Split(newPod.Annotations[util.AAPsAnnotation], ",")
×
317
                var vipNames []string
×
318
                for _, vipName := range oldAAPs {
×
319
                        vipNames = append(vipNames, strings.TrimSpace(vipName))
×
320
                }
×
321
                for _, vipName := range newAAPs {
×
322
                        vipName = strings.TrimSpace(vipName)
×
323
                        if !slices.Contains(vipNames, vipName) {
×
324
                                vipNames = append(vipNames, vipName)
×
325
                        }
×
326
                }
327
                for _, vipName := range vipNames {
×
328
                        if vip, err := c.virtualIpsLister.Get(vipName); err == nil {
×
329
                                if vip.Spec.Namespace != newPod.Namespace {
×
330
                                        continue
×
331
                                }
332
                                klog.Infof("enqueue update virtual parents for %s", vipName)
×
333
                                c.updateVirtualParentsQueue.Add(vipName)
×
334
                        }
335
                }
336
        }
337

338
        if newPod.Spec.HostNetwork || oldPod.ResourceVersion == newPod.ResourceVersion {
×
339
                return
×
340
        }
×
341

342
        podNets, err := c.getPodKubeovnNets(newPod)
×
343
        if err != nil {
×
344
                klog.Errorf("failed to get newPod nets %v", err)
×
345
                return
×
346
        }
×
347

348
        key := cache.MetaObjectToName(newPod).String()
×
349
        if c.config.EnableNP {
×
350
                c.namedPort.AddNamedPortByPod(newPod)
×
351
                newNp := c.podMatchNetworkPolicies(newPod)
×
352
                if !maps.Equal(oldPod.Labels, newPod.Labels) {
×
353
                        oldNp := c.podMatchNetworkPolicies(oldPod)
×
354
                        for _, np := range util.DiffStringSlice(oldNp, newNp) {
×
355
                                c.updateNpQueue.Add(np)
×
356
                        }
×
357
                }
358

359
                for _, podNet := range podNets {
×
360
                        oldAllocated := oldPod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)]
×
361
                        newAllocated := newPod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)]
×
362
                        if oldAllocated != newAllocated {
×
363
                                for _, np := range newNp {
×
364
                                        klog.V(3).Infof("enqueue update network policy %s for pod %s", np, key)
×
365
                                        c.updateNpQueue.Add(np)
×
366
                                }
×
367
                                break
×
368
                        }
369
                }
370
        }
371

372
        if c.config.EnableANP {
×
373
                nsLabels := c.getNsLabels(newPod.Namespace, newPod.Name)
×
374
                if !maps.Equal(oldPod.Labels, newPod.Labels) {
×
375
                        c.updateAnpsByLabelsMatch(nsLabels, newPod.Labels)
×
376
                        c.updateCnpsByLabelsMatch(nsLabels, newPod.Labels)
×
377
                }
×
378

379
                for _, podNet := range podNets {
×
380
                        oldAllocated := oldPod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)]
×
381
                        newAllocated := newPod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)]
×
382
                        if oldAllocated != newAllocated {
×
383
                                c.updateAnpsByLabelsMatch(nsLabels, newPod.Labels)
×
384
                                c.updateCnpsByLabelsMatch(nsLabels, newPod.Labels)
×
385
                                break
×
386
                        }
387
                }
388
        }
389

390
        isStateful, statefulSetName, statefulSetUID := isStatefulSetPod(newPod)
×
391
        isVMPod, vmName := isVMPod(newPod)
×
392
        if !isPodStatusPhaseAlive(newPod) && !isStateful && !isVMPod {
×
393
                klog.V(3).Infof("enqueue delete pod %s", key)
×
394
                c.deletingPodObjMap.Store(key, newPod)
×
395
                c.deletePodQueue.Add(key)
×
396
                return
×
397
        }
×
398

399
        // enqueue delay
400
        var delay time.Duration
×
401
        if newPod.Spec.TerminationGracePeriodSeconds != nil {
×
402
                if !newPod.DeletionTimestamp.IsZero() {
×
403
                        delay = time.Until(newPod.DeletionTimestamp.Add(time.Duration(*newPod.Spec.TerminationGracePeriodSeconds) * time.Second))
×
404
                } else {
×
405
                        delay = time.Duration(*newPod.Spec.TerminationGracePeriodSeconds) * time.Second
×
406
                }
×
407
        }
408

409
        if !newPod.DeletionTimestamp.IsZero() && !isStateful && !isVMPod {
×
410
                go func() {
×
411
                        // In case node get lost and pod can not be deleted,
×
412
                        // the ip address will not be recycled
×
413
                        klog.V(3).Infof("enqueue delete pod %s after %v", key, delay)
×
414
                        c.deletingPodObjMap.Store(key, newPod)
×
415
                        c.deletePodQueue.AddAfter(key, delay)
×
416
                }()
×
417
                return
×
418
        }
419

420
        if err = c.handlePodEventForVpcEgressGateway(newPod); err != nil {
×
421
                klog.Errorf("failed to handle pod event for vpc egress gateway: %v", err)
×
422
        }
×
423

424
        // do not delete statefulset/vm pod unless ownerReferences is deleted
425
        shouldDelete := (isStateful && isStatefulSetPodToDel(c.config.KubeClient, newPod, statefulSetName, statefulSetUID)) ||
×
426
                (isVMPod && c.isVMToDel(newPod, vmName))
×
427
        if shouldDelete {
×
428
                go func() {
×
429
                        klog.V(3).Infof("enqueue delete pod %s after %v", key, delay)
×
430
                        c.deletingPodObjMap.Store(key, newPod)
×
431
                        c.deletePodQueue.AddAfter(key, delay)
×
432
                }()
×
433
                return
×
434
        }
435
        klog.Infof("enqueue update pod %s", key)
×
436
        c.addOrUpdatePodQueue.Add(key)
×
437

×
438
        // security policy changed
×
439
        for _, podNet := range podNets {
×
440
                oldSecurity := oldPod.Annotations[fmt.Sprintf(util.PortSecurityAnnotationTemplate, podNet.ProviderName)]
×
441
                newSecurity := newPod.Annotations[fmt.Sprintf(util.PortSecurityAnnotationTemplate, podNet.ProviderName)]
×
442
                oldSg := oldPod.Annotations[fmt.Sprintf(util.SecurityGroupAnnotationTemplate, podNet.ProviderName)]
×
443
                newSg := newPod.Annotations[fmt.Sprintf(util.SecurityGroupAnnotationTemplate, podNet.ProviderName)]
×
444
                oldVips := oldPod.Annotations[fmt.Sprintf(util.PortVipAnnotationTemplate, podNet.ProviderName)]
×
445
                newVips := newPod.Annotations[fmt.Sprintf(util.PortVipAnnotationTemplate, podNet.ProviderName)]
×
446
                oldAAPs := oldPod.Annotations[util.AAPsAnnotation]
×
447
                newAAPs := newPod.Annotations[util.AAPsAnnotation]
×
448
                if oldSecurity != newSecurity || oldSg != newSg || oldVips != newVips || oldAAPs != newAAPs {
×
449
                        c.updatePodSecurityQueue.Add(key)
×
450
                        break
×
451
                }
452
        }
453
}
454

455
func (c *Controller) getPodKubeovnNets(pod *v1.Pod) ([]*kubeovnNet, error) {
1✔
456
        attachmentNets, err := c.getPodAttachmentNet(pod)
1✔
457
        if err != nil {
1✔
458
                klog.Error(err)
×
459
                return nil, err
×
460
        }
×
461

462
        podNets := attachmentNets
1✔
463
        // When Kube-OVN is run as non-primary CNI, we do not add default network configuration to pod.
1✔
464
        // We only add network attachment defined by the user to pod.
1✔
465
        if c.config.EnableNonPrimaryCNI {
2✔
466
                return podNets, nil
1✔
467
        }
1✔
468

469
        defaultSubnet, err := c.getPodDefaultSubnet(pod)
1✔
470
        if err != nil {
1✔
471
                klog.Error(err)
×
472
                return nil, err
×
473
        }
×
474

475
        // pod annotation default subnet not found
476
        if defaultSubnet == nil {
1✔
477
                klog.Errorf("pod %s/%s has no default subnet, skip adding default network", pod.Namespace, pod.Name)
×
478
                return attachmentNets, nil
×
479
        }
×
480

481
        if _, hasOtherDefaultNet := pod.Annotations[util.DefaultNetworkAnnotation]; !hasOtherDefaultNet {
2✔
482
                podNets = append(attachmentNets, &kubeovnNet{
1✔
483
                        Type:         providerTypeOriginal,
1✔
484
                        ProviderName: util.OvnProvider,
1✔
485
                        Subnet:       defaultSubnet,
1✔
486
                        IsDefault:    true,
1✔
487
                })
1✔
488
        }
1✔
489

490
        return podNets, nil
1✔
491
}
492

493
func (c *Controller) handleAddOrUpdatePod(key string) (err error) {
×
494
        now := time.Now()
×
495
        klog.Infof("handle add/update pod %s", key)
×
496

×
497
        namespace, name, err := cache.SplitMetaNamespaceKey(key)
×
498
        if err != nil {
×
499
                utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
×
500
                return nil
×
501
        }
×
502

503
        c.podKeyMutex.LockKey(key)
×
504
        defer func() {
×
505
                _ = c.podKeyMutex.UnlockKey(key)
×
506
                last := time.Since(now)
×
507
                klog.Infof("take %d ms to handle add or update pod %s", last.Milliseconds(), key)
×
508
        }()
×
509

510
        pod, err := c.podsLister.Pods(namespace).Get(name)
×
511
        if err != nil {
×
512
                if k8serrors.IsNotFound(err) {
×
513
                        return nil
×
514
                }
×
515
                klog.Error(err)
×
516
                return err
×
517
        }
518
        if err := util.ValidatePodNetwork(pod.Annotations); err != nil {
×
519
                klog.Errorf("validate pod %s/%s failed: %v", namespace, name, err)
×
520
                c.recorder.Eventf(pod, v1.EventTypeWarning, "ValidatePodNetworkFailed", err.Error())
×
521
                return err
×
522
        }
×
523

524
        podNets, err := c.getPodKubeovnNets(pod)
×
525
        if err != nil {
×
526
                klog.Errorf("failed to get pod nets %v", err)
×
527
                return err
×
528
        }
×
529

530
        // check and do hotnoplug nic
531
        if pod, err = c.syncKubeOvnNet(pod, podNets); err != nil {
×
532
                klog.Errorf("failed to sync pod nets %v", err)
×
533
                return err
×
534
        }
×
535
        if pod == nil {
×
536
                // pod has been deleted
×
537
                return nil
×
538
        }
×
539
        needAllocatePodNets := needAllocateSubnets(pod, podNets)
×
540
        if len(needAllocatePodNets) != 0 {
×
541
                if pod, err = c.reconcileAllocateSubnets(pod, needAllocatePodNets); err != nil {
×
542
                        klog.Error(err)
×
543
                        return err
×
544
                }
×
545
                if pod == nil {
×
546
                        // pod has been deleted
×
547
                        return nil
×
548
                }
×
549
        }
550

551
        isVpcNatGw, vpcGwName := c.checkIsPodVpcNatGw(pod)
×
552
        if isVpcNatGw {
×
553
                c.enqueueAddOrUpdateVpcNatGwByName(vpcGwName, "natgw-pod-update")
×
554
                if needRestartNatGatewayPod(pod) {
×
555
                        klog.Infof("restarting vpc nat gateway %s", vpcGwName)
×
556
                        c.addOrUpdateVpcNatGatewayQueue.Add(vpcGwName)
×
557
                }
×
558
        }
559

560
        // Reconcile per-port DHCP options for pods that carry DHCP annotations.
561
        // This handles annotation add/change on already-running pods without requiring a pod restart.
562
        if err = c.reconcilePodDHCPOptions(pod, podNets); err != nil {
×
563
                return err
×
564
        }
×
565

566
        // check if route subnet is need.
567
        return c.reconcileRouteSubnets(pod, needRouteSubnets(pod, podNets))
×
568
}
569

570
// subnetDHCPOptionsUUIDs returns the subnet-level DHCP option UUIDs from the subnet status.
571
func subnetDHCPOptionsUUIDs(subnet *kubeovnv1.Subnet) *ovs.DHCPOptionsUUIDs {
×
572
        return &ovs.DHCPOptionsUUIDs{
×
573
                DHCPv4OptionsUUID: subnet.Status.DHCPv4OptionsUUID,
×
574
                DHCPv6OptionsUUID: subnet.Status.DHCPv6OptionsUUID,
×
575
        }
×
576
}
×
577

578
// reconcilePodDHCPOptions reconciles per-port DHCP_Options for already-allocated pods.
579
// It delegates all DHCP logic (stale detection, create/update/cleanup, LSP pointer update)
580
// to ReconcilePortDHCPOptions in the OVS layer.
581
func (c *Controller) reconcilePodDHCPOptions(pod *v1.Pod, podNets []*kubeovnNet) error {
×
582
        podName := c.getNameByPod(pod)
×
583
        for _, podNet := range podNets {
×
584
                if podNet.Type == providerTypeIPAM {
×
585
                        continue
×
586
                }
587
                // Skip nets not yet allocated; they are handled by reconcileAllocateSubnets.
588
                if pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)] != "true" {
×
589
                        continue
×
590
                }
591

592
                subnet := podNet.Subnet
×
593
                portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
×
594
                dhcpV4 := pod.Annotations[fmt.Sprintf(util.DHCPv4OptionsAnnotationTemplate, podNet.ProviderName)]
×
595
                dhcpV6 := pod.Annotations[fmt.Sprintf(util.DHCPv6OptionsAnnotationTemplate, podNet.ProviderName)]
×
596

×
597
                var mtu int
×
598
                var gateway string
×
599
                if dhcpV4 != "" || dhcpV6 != "" {
×
600
                        var err error
×
601
                        if mtu, err = c.getSubnetMTU(subnet); err != nil {
×
602
                                return err
×
603
                        }
×
604
                        gateway = subnet.Spec.Gateway
×
605
                        if subnet.Status.U2OInterconnectionIP != "" && subnet.Spec.U2OInterconnection {
×
606
                                gateway = subnet.Status.U2OInterconnectionIP
×
607
                        }
×
608
                }
609

610
                if _, _, err := c.OVNNbClient.ReconcilePortDHCPOptions(
×
611
                        subnet.Name, portName, subnetDHCPOptionsUUIDs(subnet),
×
612
                        subnet.Spec.CIDRBlock, gateway, dhcpV4, dhcpV6, mtu,
×
613
                ); err != nil {
×
614
                        klog.Errorf("failed to reconcile DHCP options for port %s: %v", portName, err)
×
615
                        return err
×
616
                }
×
617
        }
618
        return nil
×
619
}
620

621
// do the same thing as add pod
622
func (c *Controller) reconcileAllocateSubnets(pod *v1.Pod, needAllocatePodNets []*kubeovnNet) (*v1.Pod, error) {
×
623
        namespace := pod.Namespace
×
624
        name := pod.Name
×
625
        klog.Infof("sync pod %s/%s allocated", namespace, name)
×
626

×
627
        vipsMap := c.getVirtualIPs(pod, needAllocatePodNets)
×
628
        isVMPod, vmName := isVMPod(pod)
×
629
        podType := getPodType(pod)
×
630
        podName := c.getNameByPod(pod)
×
631
        // todo: isVmPod, getPodType, getNameByPod has duplicated logic
×
632

×
633
        var err error
×
634
        var vmKey string
×
635
        if isVMPod && c.config.EnableKeepVMIP {
×
636
                vmKey = fmt.Sprintf("%s/%s", namespace, vmName)
×
637
        }
×
638
        // Avoid create lsp for already running pod in ovn-nb when controller restart
639
        patch := util.KVPatch{}
×
640
        for _, podNet := range needAllocatePodNets {
×
641
                // the subnet may changed when alloc static ip from the latter subnet after ns supports multi subnets
×
642
                v4IP, v6IP, mac, subnet, err := c.acquireAddress(pod, podNet)
×
643
                if err != nil {
×
644
                        c.recorder.Eventf(pod, v1.EventTypeWarning, "AcquireAddressFailed", err.Error())
×
645
                        klog.Error(err)
×
646
                        return nil, err
×
647
                }
×
648
                ipStr := util.GetStringIP(v4IP, v6IP)
×
649
                patch[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] = ipStr
×
650
                if mac == "" {
×
651
                        patch[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] = nil
×
652
                } else {
×
653
                        patch[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] = mac
×
654
                }
×
655
                patch[fmt.Sprintf(util.CidrAnnotationTemplate, podNet.ProviderName)] = subnet.Spec.CIDRBlock
×
656
                patch[fmt.Sprintf(util.GatewayAnnotationTemplate, podNet.ProviderName)] = subnet.Spec.Gateway
×
657
                if isOvnSubnet(podNet.Subnet) {
×
658
                        patch[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName)] = subnet.Name
×
659
                        if pod.Annotations[fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName)] == "" {
×
660
                                patch[fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName)] = c.config.PodNicType
×
661
                        }
×
662
                } else {
×
663
                        patch[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName)] = nil
×
664
                        patch[fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName)] = nil
×
665
                }
×
666
                patch[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)] = "true"
×
667
                if vmKey != "" {
×
668
                        patch[fmt.Sprintf(util.VMAnnotationTemplate, podNet.ProviderName)] = vmName
×
669
                }
×
670
                if err := util.ValidateNetworkBroadcast(podNet.Subnet.Spec.CIDRBlock, ipStr); err != nil {
×
671
                        klog.Errorf("validate pod %s/%s failed: %v", namespace, name, err)
×
672
                        c.recorder.Eventf(pod, v1.EventTypeWarning, "ValidatePodNetworkFailed", err.Error())
×
673
                        return nil, err
×
674
                }
×
675

676
                if podNet.Type != providerTypeIPAM {
×
677
                        if (subnet.Spec.Vlan == "" || subnet.Spec.LogicalGateway || subnet.Spec.U2OInterconnection) && subnet.Spec.Vpc != "" {
×
678
                                patch[fmt.Sprintf(util.LogicalRouterAnnotationTemplate, podNet.ProviderName)] = subnet.Spec.Vpc
×
679
                        }
×
680

681
                        if subnet.Spec.Vlan != "" {
×
682
                                vlan, err := c.vlansLister.Get(subnet.Spec.Vlan)
×
683
                                if err != nil {
×
684
                                        klog.Error(err)
×
685
                                        c.recorder.Eventf(pod, v1.EventTypeWarning, "GetVlanInfoFailed", err.Error())
×
686
                                        return nil, err
×
687
                                }
×
688
                                patch[fmt.Sprintf(util.VlanIDAnnotationTemplate, podNet.ProviderName)] = strconv.Itoa(vlan.Spec.ID)
×
689
                                patch[fmt.Sprintf(util.ProviderNetworkTemplate, podNet.ProviderName)] = vlan.Spec.Provider
×
690
                        }
691

692
                        portSecurity := false
×
693
                        if pod.Annotations[fmt.Sprintf(util.PortSecurityAnnotationTemplate, podNet.ProviderName)] == "true" {
×
694
                                portSecurity = true
×
695
                        }
×
696

697
                        vips := vipsMap[fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)]
×
698
                        for ip := range strings.SplitSeq(vips, ",") {
×
699
                                if ip != "" && net.ParseIP(ip) == nil {
×
700
                                        klog.Errorf("invalid vip address '%s' for pod %s", ip, name)
×
701
                                        vips = ""
×
702
                                        break
×
703
                                }
704
                        }
705

706
                        portName := ovs.PodNameToPortName(podName, namespace, podNet.ProviderName)
×
707

×
708
                        dhcpV4 := pod.Annotations[fmt.Sprintf(util.DHCPv4OptionsAnnotationTemplate, podNet.ProviderName)]
×
709
                        dhcpV6 := pod.Annotations[fmt.Sprintf(util.DHCPv6OptionsAnnotationTemplate, podNet.ProviderName)]
×
710

×
711
                        var mtu int
×
712
                        var gateway string
×
713
                        if dhcpV4 != "" || dhcpV6 != "" {
×
714
                                if mtu, err = c.getSubnetMTU(subnet); err != nil {
×
715
                                        return nil, err
×
716
                                }
×
717
                                gateway = subnet.Spec.Gateway
×
718
                                if subnet.Status.U2OInterconnectionIP != "" && subnet.Spec.U2OInterconnection {
×
719
                                        gateway = subnet.Status.U2OInterconnectionIP
×
720
                                }
×
721
                        }
722

723
                        dhcpOptions, hasPerPortDHCP, err := c.OVNNbClient.ReconcilePortDHCPOptions(
×
724
                                subnet.Name, portName, subnetDHCPOptionsUUIDs(subnet),
×
725
                                subnet.Spec.CIDRBlock, gateway, dhcpV4, dhcpV6, mtu,
×
726
                        )
×
727
                        if err != nil {
×
728
                                klog.Errorf("failed to reconcile DHCP options for port %s: %v", portName, err)
×
729
                                return nil, err
×
730
                        }
×
731

732
                        // When pod has per-port DHCP options, enable DHCP regardless of subnet setting.
733
                        enableDHCP := podNet.Subnet.Spec.EnableDHCP || hasPerPortDHCP
×
734

×
735
                        var oldSgList []string
×
736
                        if vmKey != "" {
×
737
                                existingLsp, err := c.OVNNbClient.GetLogicalSwitchPort(portName, true)
×
738
                                if err != nil {
×
739
                                        klog.Errorf("failed to get logical switch port %s: %v", portName, err)
×
740
                                        return nil, err
×
741
                                }
×
742
                                if existingLsp != nil {
×
743
                                        oldSgList, _ = c.getPortSg(existingLsp)
×
744
                                }
×
745
                        }
746

747
                        securityGroupAnnotation := pod.Annotations[fmt.Sprintf(util.SecurityGroupAnnotationTemplate, podNet.ProviderName)]
×
748
                        if err := c.OVNNbClient.CreateLogicalSwitchPort(subnet.Name, portName, ipStr, mac, podName, pod.Namespace,
×
749
                                portSecurity, securityGroupAnnotation, vips, enableDHCP, dhcpOptions, subnet.Spec.Vpc); err != nil {
×
750
                                c.recorder.Eventf(pod, v1.EventTypeWarning, "CreateOVNPortFailed", err.Error())
×
751
                                klog.Errorf("%v", err)
×
752
                                return nil, err
×
753
                        }
×
754

755
                        if pod.Annotations[fmt.Sprintf(util.Layer2ForwardAnnotationTemplate, podNet.ProviderName)] == "true" {
×
756
                                if err := c.OVNNbClient.EnablePortLayer2forward(portName); err != nil {
×
757
                                        c.recorder.Eventf(pod, v1.EventTypeWarning, "SetOVNPortL2ForwardFailed", err.Error())
×
758
                                        klog.Errorf("%v", err)
×
759
                                        return nil, err
×
760
                                }
×
761
                        }
762

763
                        if securityGroupAnnotation != "" || oldSgList != nil {
×
764
                                securityGroups := strings.ReplaceAll(securityGroupAnnotation, " ", "")
×
765
                                newSgList := strings.Split(securityGroups, ",")
×
766
                                sgNames := util.UnionStringSlice(oldSgList, newSgList)
×
767
                                for _, sgName := range sgNames {
×
768
                                        if sgName != "" {
×
769
                                                c.syncSgPortsQueue.Add(sgName)
×
770
                                        }
×
771
                                }
772
                        }
773

774
                        if vips != "" {
×
775
                                c.syncVirtualPortsQueue.Add(podNet.Subnet.Name)
×
776
                        }
×
777
                }
778
                // CreatePort may fail, so put ip CR creation after CreatePort
779
                ipCRName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
×
780
                if err := c.createOrUpdateIPCR(ipCRName, podName, ipStr, mac, subnet.Name, pod.Namespace, pod.Spec.NodeName, podType); err != nil {
×
781
                        err = fmt.Errorf("failed to create ips CR %s.%s: %w", podName, pod.Namespace, err)
×
782
                        klog.Error(err)
×
783
                        return nil, err
×
784
                }
×
785
        }
786
        if err = util.PatchAnnotations(c.config.KubeClient.CoreV1().Pods(namespace), name, patch); err != nil {
×
787
                if k8serrors.IsNotFound(err) {
×
788
                        // Sometimes pod is deleted between kube-ovn configure ovn-nb and patch pod.
×
789
                        // Then we need to recycle the resource again.
×
790
                        key := strings.Join([]string{namespace, name}, "/")
×
791
                        c.deletingPodObjMap.Store(key, pod)
×
792
                        c.deletePodQueue.AddRateLimited(key)
×
793
                        return nil, nil
×
794
                }
×
795
                klog.Errorf("failed to patch pod %s/%s: %v", namespace, name, err)
×
796
                return nil, err
×
797
        }
798

799
        oldPod := pod
×
800
        if pod, err = c.config.KubeClient.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{}); err != nil {
×
801
                if k8serrors.IsNotFound(err) {
×
802
                        key := strings.Join([]string{namespace, name}, "/")
×
803
                        c.deletingPodObjMap.Store(key, oldPod)
×
804
                        c.deletePodQueue.AddRateLimited(key)
×
805
                        return nil, nil
×
806
                }
×
807
                klog.Errorf("failed to get pod %s/%s: %v", namespace, name, err)
×
808
                return nil, err
×
809
        }
810

811
        // Clean stale attachment IPs/LSPs from previous NAD references when a new
812
        // VM pod starts. This handles the stop→patch NAD→start workflow where the
813
        // old pod deletion was processed before the NAD change.
814
        // Called after pod re-fetch so getPodKubeovnNets sees current annotations.
815
        if isVMPod && c.config.EnableKeepVMIP {
×
816
                c.cleanStaleVMAttachmentIPs(pod, podName)
×
817
        }
×
818

819
        // Check if pod is a vpc nat gateway. Annotation set will have subnet provider name as prefix
820
        isVpcNatGw, vpcGwName := c.checkIsPodVpcNatGw(pod)
×
821
        if isVpcNatGw {
×
822
                c.enqueueAddOrUpdateVpcNatGwByName(vpcGwName, "natgw-pod-update")
×
823
                klog.Infof("init vpc nat gateway pod %s/%s with name %s", namespace, name, vpcGwName)
×
824
                c.initVpcNatGatewayQueue.Add(vpcGwName)
×
825
        }
×
826

827
        return pod, nil
×
828
}
829

830
// do the same thing as update pod
831
func (c *Controller) reconcileRouteSubnets(pod *v1.Pod, needRoutePodNets []*kubeovnNet) error {
×
832
        // the lb-svc pod has dependencies on Running state, check it when pod state get updated
×
833
        if err := c.checkAndReInitLbSvcPod(pod); err != nil {
×
834
                klog.Errorf("failed to init iptable rules for load-balancer pod %s/%s: %v", pod.Namespace, pod.Name, err)
×
835
        }
×
836

837
        if len(needRoutePodNets) == 0 {
×
838
                return nil
×
839
        }
×
840

841
        namespace := pod.Namespace
×
842
        name := pod.Name
×
843
        podName := c.getNameByPod(pod)
×
844

×
845
        klog.Infof("sync pod %s/%s routed", namespace, name)
×
846

×
847
        node, err := c.nodesLister.Get(pod.Spec.NodeName)
×
848
        if err != nil {
×
849
                klog.Errorf("failed to get node %s: %v", pod.Spec.NodeName, err)
×
850
                return err
×
851
        }
×
852

853
        portGroups, err := c.OVNNbClient.ListPortGroups(map[string]string{"node": "", networkPolicyKey: ""})
×
854
        if err != nil {
×
855
                klog.Errorf("failed to list port groups: %v", err)
×
856
                return err
×
857
        }
×
858

859
        var nodePortGroups []string
×
860
        nodePortGroup := strings.ReplaceAll(node.Annotations[util.PortNameAnnotation], "-", ".")
×
861
        for _, pg := range portGroups {
×
862
                if pg.Name != nodePortGroup && pg.ExternalIDs["subnet"] == "" {
×
863
                        nodePortGroups = append(nodePortGroups, pg.Name)
×
864
                }
×
865
        }
866

867
        var podIP string
×
868
        var subnet *kubeovnv1.Subnet
×
869
        patch := util.KVPatch{}
×
870
        for _, podNet := range needRoutePodNets {
×
871
                // in case update handler overlap the annotation when cache is not in sync
×
872
                if pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)] == "" {
×
873
                        return fmt.Errorf("no address has been allocated to %s/%s", namespace, name)
×
874
                }
×
875

876
                podIP = pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]
×
877
                subnet = podNet.Subnet
×
878

×
879
                // Check if pod uses nodeSwitch subnet
×
880
                if subnet.Name == c.config.NodeSwitch {
×
881
                        return fmt.Errorf("NodeSwitch subnet %s is unavailable for pod", subnet.Name)
×
882
                }
×
883

884
                if portGroups, err = c.OVNNbClient.ListPortGroups(map[string]string{"subnet": subnet.Name, "node": "", networkPolicyKey: ""}); err != nil {
×
885
                        klog.Errorf("failed to list port groups: %v", err)
×
886
                        return err
×
887
                }
×
888

889
                pgName := getOverlaySubnetsPortGroupName(subnet.Name, pod.Spec.NodeName)
×
890
                portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
×
891
                subnetPortGroups := make([]string, 0, len(portGroups))
×
892
                for _, pg := range portGroups {
×
893
                        if pg.Name != pgName {
×
894
                                subnetPortGroups = append(subnetPortGroups, pg.Name)
×
895
                        }
×
896
                }
897

898
                if (!c.config.EnableLb || (subnet.Spec.EnableLb == nil || !*subnet.Spec.EnableLb)) &&
×
899
                        subnet.Spec.Vpc == c.config.ClusterRouter &&
×
900
                        subnet.Spec.U2OInterconnection &&
×
901
                        subnet.Spec.Vlan != "" &&
×
902
                        !subnet.Spec.LogicalGateway {
×
903
                        // remove lsp from other port groups
×
904
                        // we need to do this because the pod, e.g. a sts/vm, can be rescheduled to another node
×
905
                        if err = c.OVNNbClient.RemovePortFromPortGroups(portName, subnetPortGroups...); err != nil {
×
906
                                klog.Errorf("failed to remove port %s from port groups %v: %v", portName, subnetPortGroups, err)
×
907
                                return err
×
908
                        }
×
909
                        // add lsp to the port group
910
                        if err := c.OVNNbClient.PortGroupAddPorts(pgName, portName); err != nil {
×
911
                                klog.Errorf("failed to add port to u2o port group %s: %v", pgName, err)
×
912
                                return err
×
913
                        }
×
914
                }
915

916
                if podIP != "" && (subnet.Spec.Vlan == "" || subnet.Spec.LogicalGateway) && subnet.Spec.Vpc == c.config.ClusterRouter {
×
917
                        // remove lsp from other port groups
×
918
                        // we need to do this because the pod, e.g. a sts/vm, can be rescheduled to another node
×
919
                        if err = c.OVNNbClient.RemovePortFromPortGroups(portName, nodePortGroups...); err != nil {
×
920
                                klog.Errorf("failed to remove port %s from port groups %v: %v", portName, nodePortGroups, err)
×
921
                                return err
×
922
                        }
×
923
                        // add lsp to the port group
924
                        if err = c.OVNNbClient.PortGroupAddPorts(nodePortGroup, portName); err != nil {
×
925
                                klog.Errorf("failed to add port %s to port group %s: %v", portName, nodePortGroup, err)
×
926
                                return err
×
927
                        }
×
928

929
                        if c.config.EnableEipSnat && (pod.Annotations[util.EipAnnotation] != "" || pod.Annotations[util.SnatAnnotation] != "") {
×
930
                                cm, err := c.configMapsLister.ConfigMaps(c.config.ExternalGatewayConfigNS).Get(util.ExternalGatewayConfig)
×
931
                                if err != nil {
×
932
                                        klog.Errorf("failed to get ex-gateway config, %v", err)
×
933
                                        return err
×
934
                                }
×
935
                                nextHop := cm.Data["external-gw-addr"]
×
936
                                if nextHop == "" {
×
937
                                        externalSubnet, err := c.subnetsLister.Get(c.config.ExternalGatewaySwitch)
×
938
                                        if err != nil {
×
939
                                                klog.Errorf("failed to get subnet %s, %v", c.config.ExternalGatewaySwitch, err)
×
940
                                                return err
×
941
                                        }
×
942
                                        nextHop = externalSubnet.Spec.Gateway
×
943
                                        if nextHop == "" {
×
944
                                                klog.Errorf("no available gateway address")
×
945
                                                return errors.New("no available gateway address")
×
946
                                        }
×
947
                                }
948
                                if strings.Contains(nextHop, "/") {
×
949
                                        nextHop = strings.Split(nextHop, "/")[0]
×
950
                                }
×
951

952
                                if err := c.addPolicyRouteToVpc(
×
953
                                        subnet.Spec.Vpc,
×
954
                                        &kubeovnv1.PolicyRoute{
×
955
                                                Priority:  util.NorthGatewayRoutePolicyPriority,
×
956
                                                Match:     "ip4.src == " + podIP,
×
957
                                                Action:    kubeovnv1.PolicyRouteActionReroute,
×
958
                                                NextHopIP: nextHop,
×
959
                                        },
×
960
                                        map[string]string{
×
961
                                                "vendor": util.CniTypeName,
×
962
                                                "subnet": subnet.Name,
×
963
                                        },
×
964
                                ); err != nil {
×
965
                                        klog.Errorf("failed to add policy route, %v", err)
×
966
                                        return err
×
967
                                }
×
968

969
                                // remove lsp from port group to make EIP/SNAT work
970
                                if err = c.OVNNbClient.PortGroupRemovePorts(pgName, portName); err != nil {
×
971
                                        klog.Error(err)
×
972
                                        return err
×
973
                                }
×
974
                        } else {
×
975
                                if subnet.Spec.GatewayType == kubeovnv1.GWDistributedType && pod.Annotations[util.NorthGatewayAnnotation] == "" {
×
976
                                        nodeTunlIPAddr, err := getNodeTunlIP(node)
×
977
                                        if err != nil {
×
978
                                                klog.Error(err)
×
979
                                                return err
×
980
                                        }
×
981

982
                                        var added bool
×
983
                                        for _, nodeAddr := range nodeTunlIPAddr {
×
984
                                                for podAddr := range strings.SplitSeq(podIP, ",") {
×
985
                                                        if util.CheckProtocol(nodeAddr.String()) != util.CheckProtocol(podAddr) {
×
986
                                                                continue
×
987
                                                        }
988

989
                                                        // remove lsp from other port groups
990
                                                        // we need to do this because the pod, e.g. a sts/vm, can be rescheduled to another node
991
                                                        if err = c.OVNNbClient.RemovePortFromPortGroups(portName, subnetPortGroups...); err != nil {
×
992
                                                                klog.Errorf("failed to remove port %s from port groups %v: %v", portName, subnetPortGroups, err)
×
993
                                                                return err
×
994
                                                        }
×
995
                                                        if err := c.OVNNbClient.PortGroupAddPorts(pgName, portName); err != nil {
×
996
                                                                klog.Errorf("failed to add port %s to port group %s: %v", portName, pgName, err)
×
997
                                                                return err
×
998
                                                        }
×
999

1000
                                                        added = true
×
1001
                                                        break
×
1002
                                                }
1003
                                                if added {
×
1004
                                                        break
×
1005
                                                }
1006
                                        }
1007
                                }
1008

1009
                                if pod.Annotations[util.NorthGatewayAnnotation] != "" && pod.Annotations[util.IPAddressAnnotation] != "" {
×
1010
                                        for podAddr := range strings.SplitSeq(pod.Annotations[util.IPAddressAnnotation], ",") {
×
1011
                                                if util.CheckProtocol(podAddr) != util.CheckProtocol(pod.Annotations[util.NorthGatewayAnnotation]) {
×
1012
                                                        continue
×
1013
                                                }
1014
                                                ipSuffix := "ip4"
×
1015
                                                if util.CheckProtocol(podAddr) == kubeovnv1.ProtocolIPv6 {
×
1016
                                                        ipSuffix = "ip6"
×
1017
                                                }
×
1018

1019
                                                if err := c.addPolicyRouteToVpc(
×
1020
                                                        subnet.Spec.Vpc,
×
1021
                                                        &kubeovnv1.PolicyRoute{
×
1022
                                                                Priority:  util.NorthGatewayRoutePolicyPriority,
×
1023
                                                                Match:     fmt.Sprintf("%s.src == %s", ipSuffix, podAddr),
×
1024
                                                                Action:    kubeovnv1.PolicyRouteActionReroute,
×
1025
                                                                NextHopIP: pod.Annotations[util.NorthGatewayAnnotation],
×
1026
                                                        },
×
1027
                                                        map[string]string{
×
1028
                                                                "vendor": util.CniTypeName,
×
1029
                                                                "subnet": subnet.Name,
×
1030
                                                        },
×
1031
                                                ); err != nil {
×
1032
                                                        klog.Errorf("failed to add policy route, %v", err)
×
1033
                                                        return err
×
1034
                                                }
×
1035
                                        }
1036
                                } else if c.config.EnableEipSnat {
×
1037
                                        if err = c.deleteStaticRouteFromVpc(
×
1038
                                                c.config.ClusterRouter,
×
1039
                                                subnet.Spec.RouteTable,
×
1040
                                                podIP,
×
1041
                                                "",
×
1042
                                                kubeovnv1.PolicyDst,
×
1043
                                        ); err != nil {
×
1044
                                                klog.Error(err)
×
1045
                                                return err
×
1046
                                        }
×
1047
                                }
1048
                        }
1049

1050
                        if c.config.EnableEipSnat {
×
1051
                                for ipStr := range strings.SplitSeq(podIP, ",") {
×
1052
                                        if eip := pod.Annotations[util.EipAnnotation]; eip == "" {
×
1053
                                                if err = c.OVNNbClient.DeleteNats(c.config.ClusterRouter, ovnnb.NATTypeDNATAndSNAT, ipStr); err != nil {
×
1054
                                                        klog.Errorf("failed to delete nat rules: %v", err)
×
1055
                                                }
×
1056
                                        } else if util.CheckProtocol(eip) == util.CheckProtocol(ipStr) {
×
1057
                                                if err = c.OVNNbClient.UpdateDnatAndSnat(c.config.ClusterRouter, eip, ipStr, fmt.Sprintf("%s.%s", podName, pod.Namespace), pod.Annotations[util.MacAddressAnnotation], c.ExternalGatewayType); err != nil {
×
1058
                                                        klog.Errorf("failed to add nat rules, %v", err)
×
1059
                                                        return err
×
1060
                                                }
×
1061
                                        }
1062
                                        if eip := pod.Annotations[util.SnatAnnotation]; eip == "" {
×
1063
                                                if err = c.OVNNbClient.DeleteNats(c.config.ClusterRouter, ovnnb.NATTypeSNAT, ipStr); err != nil {
×
1064
                                                        klog.Errorf("failed to delete nat rules: %v", err)
×
1065
                                                }
×
1066
                                        } else if util.CheckProtocol(eip) == util.CheckProtocol(ipStr) {
×
1067
                                                if err = c.OVNNbClient.UpdateSnat(c.config.ClusterRouter, eip, ipStr); err != nil {
×
1068
                                                        klog.Errorf("failed to add nat rules, %v", err)
×
1069
                                                        return err
×
1070
                                                }
×
1071
                                        }
1072
                                }
1073
                        }
1074
                }
1075

1076
                if pod.Annotations[fmt.Sprintf(util.ActivationStrategyTemplate, podNet.ProviderName)] != "" {
×
1077
                        if err := c.OVNNbClient.SetLogicalSwitchPortActivationStrategy(portName, pod.Spec.NodeName); err != nil {
×
1078
                                klog.Errorf("failed to set activation strategy for lsp %s: %v", portName, err)
×
1079
                                return err
×
1080
                        }
×
1081
                }
1082

1083
                patch[fmt.Sprintf(util.RoutedAnnotationTemplate, podNet.ProviderName)] = "true"
×
1084
        }
1085
        if err := util.PatchAnnotations(c.config.KubeClient.CoreV1().Pods(namespace), name, patch); err != nil {
×
1086
                if k8serrors.IsNotFound(err) {
×
1087
                        // Sometimes pod is deleted between kube-ovn configure ovn-nb and patch pod.
×
1088
                        // Then we need to recycle the resource again.
×
1089
                        key := strings.Join([]string{namespace, name}, "/")
×
1090
                        c.deletingPodObjMap.Store(key, pod)
×
1091
                        c.deletePodQueue.AddRateLimited(key)
×
1092
                        return nil
×
1093
                }
×
1094
                klog.Errorf("failed to patch pod %s/%s: %v", namespace, name, err)
×
1095
                return err
×
1096
        }
1097
        return nil
×
1098
}
1099

1100
func (c *Controller) handleDeletePod(key string) (err error) {
×
1101
        pod, ok := c.deletingPodObjMap.Load(key)
×
1102
        if !ok {
×
1103
                return nil
×
1104
        }
×
1105
        now := time.Now()
×
1106
        klog.Infof("handle delete pod %s", key)
×
1107
        podName := c.getNameByPod(pod)
×
1108
        c.podKeyMutex.LockKey(key)
×
1109
        defer func() {
×
1110
                _ = c.podKeyMutex.UnlockKey(key)
×
1111
                if err == nil {
×
1112
                        c.deletingPodObjMap.Delete(key)
×
1113
                }
×
1114
                last := time.Since(now)
×
1115
                klog.Infof("take %d ms to handle delete pod %s", last.Milliseconds(), key)
×
1116
        }()
1117

1118
        p, _ := c.podsLister.Pods(pod.Namespace).Get(pod.Name)
×
1119
        if p != nil && p.UID != pod.UID {
×
1120
                // Pod with same name exists, just return here
×
1121
                return nil
×
1122
        }
×
1123

1124
        if aaps := pod.Annotations[util.AAPsAnnotation]; aaps != "" {
×
1125
                for vipName := range strings.SplitSeq(aaps, ",") {
×
1126
                        if vip, err := c.virtualIpsLister.Get(vipName); err == nil {
×
1127
                                if vip.Spec.Namespace != pod.Namespace {
×
1128
                                        continue
×
1129
                                }
1130
                                klog.Infof("enqueue update virtual parents for %s", vipName)
×
1131
                                c.updateVirtualParentsQueue.Add(vipName)
×
1132
                        }
1133
                }
1134
        }
1135

1136
        podKey := fmt.Sprintf("%s/%s", pod.Namespace, podName)
×
1137

×
1138
        var keepIPCR, isOwnerRefToDel, isOwnerRefDeleted bool
×
1139
        var ipcrToDelete []string
×
1140
        var vmOrphanedPorts map[string]bool
×
1141
        isStsPod, stsName, stsUID := isStatefulSetPod(pod)
×
1142
        if isStsPod {
×
1143
                if !pod.DeletionTimestamp.IsZero() {
×
1144
                        klog.Infof("handle deletion of sts pod %s", podKey)
×
1145
                        isOwnerRefToDel = isStatefulSetPodToDel(c.config.KubeClient, pod, stsName, stsUID)
×
1146
                        if !isOwnerRefToDel {
×
1147
                                klog.Infof("try keep ip for sts pod %s", podKey)
×
1148
                                keepIPCR = true
×
1149
                        }
×
1150
                }
1151
                if keepIPCR {
×
1152
                        isOwnerRefDeleted, ipcrToDelete, err = appendCheckPodNetToDel(c, pod, stsName, util.KindStatefulSet)
×
1153
                        if err != nil {
×
1154
                                klog.Error(err)
×
1155
                                return err
×
1156
                        }
×
1157
                        if isOwnerRefDeleted || len(ipcrToDelete) != 0 {
×
1158
                                klog.Infof("not keep ip for sts pod %s", podKey)
×
1159
                                keepIPCR = false
×
1160
                        }
×
1161
                }
1162
        }
1163
        ports, err := c.OVNNbClient.ListNormalLogicalSwitchPorts(true, map[string]string{"pod": podKey})
×
1164
        if err != nil {
×
1165
                klog.Errorf("failed to list lsps of pod %s: %v", podKey, err)
×
1166
                return err
×
1167
        }
×
1168

NEW
1169
        var hasAliveVMSibling bool
×
1170
        isVMPod, vmName := isVMPod(pod)
×
1171
        if isVMPod && c.config.EnableKeepVMIP {
×
1172
                for _, port := range ports {
×
1173
                        if err := c.OVNNbClient.CleanLogicalSwitchPortMigrateOptions(port.Name); err != nil {
×
1174
                                err = fmt.Errorf("failed to clean migrate options for vm lsp %s, %w", port.Name, err)
×
1175
                                klog.Error(err)
×
1176
                                return err
×
1177
                        }
×
1178
                }
1179
                if pod.DeletionTimestamp != nil {
×
1180
                        klog.Infof("handle deletion of vm pod %s", podKey)
×
1181
                        isOwnerRefToDel = c.isVMToDel(pod, vmName)
×
1182
                        if !isOwnerRefToDel {
×
1183
                                klog.Infof("try keep ip for vm pod %s", podKey)
×
1184
                                keepIPCR = true
×
NEW
1185
                                // The VM LSP is shared across every virt-launcher pod of the VM
×
NEW
1186
                                // (ExternalIDs["pod"] is keyed by VM name). Port-group memberships,
×
NEW
1187
                                // however, belong to whichever virt-launcher pod is currently
×
NEW
1188
                                // running the VM. If another live sibling exists (e.g. a
×
NEW
1189
                                // live-migration destination while the completed source is being
×
NEW
1190
                                // GC'd), its memberships must not be wiped out.
×
NEW
1191
                                siblings, listErr := c.podsLister.Pods(pod.Namespace).List(labels.Everything())
×
NEW
1192
                                if listErr != nil {
×
NEW
1193
                                        klog.Errorf("failed to list pods in namespace %s: %v", pod.Namespace, listErr)
×
NEW
1194
                                        return listErr
×
NEW
1195
                                }
×
NEW
1196
                                hasAliveVMSibling = hasAliveSiblingVMPod(siblings, vmName, pod.Name)
×
1197
                        }
1198
                }
1199
                if keepIPCR {
×
1200
                        isOwnerRefDeleted, ipcrToDelete, err = appendCheckPodNetToDel(c, pod, vmName, util.KindVirtualMachineInstance)
×
1201
                        if err != nil {
×
1202
                                klog.Error(err)
×
1203
                                return err
×
1204
                        }
×
1205
                        if isOwnerRefDeleted || len(ipcrToDelete) != 0 {
×
1206
                                klog.Infof("not keep ip for vm pod %s", podKey)
×
1207
                                keepIPCR = false
×
1208
                        }
×
1209
                }
1210
                // Detect orphaned attachment ports from NAD hotplug (KubeVirt VEP #140).
1211
                // Compare OVN's actual ports against VM spec's desired ports.
1212
                // These are cleaned selectively in the keepIPCR=true branch to avoid
1213
                // deleting shared primary network LSPs during live migration.
1214
                if keepIPCR {
×
1215
                        vmOrphanedPorts = c.getVMOrphanedAttachmentPorts(pod.Namespace, vmName, ports)
×
1216
                }
×
1217
        }
1218

1219
        podNets, err := c.getPodKubeovnNets(pod)
×
1220
        if err != nil {
×
1221
                klog.Errorf("failed to get kube-ovn nets of pod %s: %v", podKey, err)
×
1222
        }
×
1223
        if keepIPCR {
×
1224
                for _, port := range ports {
×
NEW
1225
                        switch {
×
NEW
1226
                        case vmOrphanedPorts[port.Name]:
×
1227
                                // Orphaned attachment LSP from NAD hotplug: delete and release its IP.
×
1228
                                klog.Infof("delete orphaned vm attachment lsp %s", port.Name)
×
1229
                                if err := c.OVNNbClient.DeleteLogicalSwitchPort(port.Name); err != nil {
×
1230
                                        klog.Errorf("failed to delete orphaned lsp %s: %v", port.Name, err)
×
1231
                                        return err
×
1232
                                }
×
1233
                                ipCR, err := c.ipsLister.Get(port.Name)
×
1234
                                if err != nil {
×
1235
                                        if !k8serrors.IsNotFound(err) {
×
1236
                                                klog.Errorf("failed to get ip %s: %v", port.Name, err)
×
1237
                                        }
×
1238
                                        continue
×
1239
                                }
1240
                                if ipCR.Labels[util.IPReservedLabel] != "true" {
×
1241
                                        klog.Infof("delete orphaned vm attachment ip CR %s", ipCR.Name)
×
1242
                                        if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), ipCR.Name, metav1.DeleteOptions{}); err != nil {
×
1243
                                                if !k8serrors.IsNotFound(err) {
×
1244
                                                        klog.Errorf("failed to delete ip %s: %v", ipCR.Name, err)
×
1245
                                                        return err
×
1246
                                                }
×
1247
                                        }
1248
                                        if subnetName := ipCR.Spec.Subnet; subnetName != "" {
×
1249
                                                c.ipam.ReleaseAddressByNic(podKey, port.Name, subnetName)
×
1250
                                                c.updateSubnetStatusQueue.Add(subnetName)
×
1251
                                        }
×
1252
                                }
NEW
1253
                        case hasAliveVMSibling:
×
NEW
1254
                                klog.Infof("skip removing lsp %s from port groups: another alive virt-launcher pod exists for vm %s/%s", port.Name, pod.Namespace, vmName)
×
NEW
1255
                        default:
×
1256
                                klog.Infof("remove lsp %s from all port groups", port.Name)
×
1257
                                if err = c.OVNNbClient.RemovePortFromPortGroups(port.Name); err != nil {
×
1258
                                        klog.Errorf("failed to remove lsp %s from all port groups: %v", port.Name, err)
×
1259
                                        return err
×
1260
                                }
×
1261
                        }
1262
                }
1263
        } else {
×
1264
                if len(ports) != 0 {
×
1265
                        addresses := c.ipam.GetPodAddress(podKey)
×
1266
                        for _, address := range addresses {
×
1267
                                if strings.TrimSpace(address.IP) == "" {
×
1268
                                        continue
×
1269
                                }
1270
                                subnet, err := c.subnetsLister.Get(address.Subnet.Name)
×
1271
                                if k8serrors.IsNotFound(err) {
×
1272
                                        continue
×
1273
                                } else if err != nil {
×
1274
                                        klog.Error(err)
×
1275
                                        return err
×
1276
                                }
×
1277
                                vpc, err := c.vpcsLister.Get(subnet.Spec.Vpc)
×
1278
                                if k8serrors.IsNotFound(err) {
×
1279
                                        continue
×
1280
                                } else if err != nil {
×
1281
                                        klog.Error(err)
×
1282
                                        return err
×
1283
                                }
×
1284

1285
                                ipSuffix := "ip4"
×
1286
                                if util.CheckProtocol(address.IP) == kubeovnv1.ProtocolIPv6 {
×
1287
                                        ipSuffix = "ip6"
×
1288
                                }
×
1289
                                if err = c.deletePolicyRouteFromVpc(
×
1290
                                        vpc.Name,
×
1291
                                        util.NorthGatewayRoutePolicyPriority,
×
1292
                                        fmt.Sprintf("%s.src == %s", ipSuffix, address.IP),
×
1293
                                ); err != nil {
×
1294
                                        klog.Errorf("failed to delete static route, %v", err)
×
1295
                                        return err
×
1296
                                }
×
1297

1298
                                if c.config.EnableEipSnat {
×
1299
                                        if pod.Annotations[util.EipAnnotation] != "" {
×
1300
                                                if err = c.OVNNbClient.DeleteNat(c.config.ClusterRouter, ovnnb.NATTypeDNATAndSNAT, pod.Annotations[util.EipAnnotation], address.IP); err != nil {
×
1301
                                                        klog.Errorf("failed to delete nat rules: %v", err)
×
1302
                                                }
×
1303
                                        }
1304
                                        if pod.Annotations[util.SnatAnnotation] != "" {
×
1305
                                                if err = c.OVNNbClient.DeleteNat(c.config.ClusterRouter, ovnnb.NATTypeSNAT, "", address.IP); err != nil {
×
1306
                                                        klog.Errorf("failed to delete nat rules: %v", err)
×
1307
                                                }
×
1308
                                        }
1309
                                }
1310
                        }
1311
                }
1312
                for _, port := range ports {
×
1313
                        // when lsp is deleted, the port of pod is deleted from any port-group automatically.
×
1314
                        klog.Infof("delete logical switch port %s", port.Name)
×
1315
                        if err := c.OVNNbClient.DeleteLogicalSwitchPort(port.Name); err != nil {
×
1316
                                klog.Errorf("failed to delete lsp %s, %v", port.Name, err)
×
1317
                                return err
×
1318
                        }
×
1319
                }
1320
                klog.Infof("try release all ip address for deleting pod %s", podKey)
×
1321
                for _, podNet := range podNets {
×
1322
                        portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
×
1323
                        // if the OwnerRef has been deleted or is in the process of being deleted, all associated IPCRs must be cleaned up
×
1324
                        if (isStsPod || isVMPod) && !isOwnerRefToDel && !isOwnerRefDeleted &&
×
1325
                                !slices.Contains(ipcrToDelete, portName) {
×
1326
                                klog.Infof("skip clean ip CR %s", portName)
×
1327
                                continue
×
1328
                        }
1329
                        ipCR, err := c.ipsLister.Get(portName)
×
1330
                        if err != nil {
×
1331
                                if k8serrors.IsNotFound(err) {
×
1332
                                        continue
×
1333
                                }
1334
                                klog.Errorf("failed to get ip %s, %v", portName, err)
×
1335
                                return err
×
1336
                        }
1337
                        if ipCR.Labels[util.IPReservedLabel] != "true" {
×
1338
                                klog.Infof("delete ip CR %s", ipCR.Name)
×
1339
                                if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), ipCR.Name, metav1.DeleteOptions{}); err != nil {
×
1340
                                        if !k8serrors.IsNotFound(err) {
×
1341
                                                klog.Errorf("failed to delete ip %s, %v", ipCR.Name, err)
×
1342
                                                return err
×
1343
                                        }
×
1344
                                }
1345
                                // release ipam address after delete ip CR
1346
                                c.ipam.ReleaseAddressByNic(podKey, portName, podNet.Subnet.Name)
×
1347
                                // Trigger subnet status update after IPAM release
×
1348
                                // This is needed when IP CR is deleted without finalizer (race condition)
×
1349
                                c.updateSubnetStatusQueue.Add(podNet.Subnet.Name)
×
1350
                        }
1351
                }
1352
                if pod.Annotations[util.VipAnnotation] != "" {
×
1353
                        if err = c.releaseVip(pod.Annotations[util.VipAnnotation]); err != nil {
×
1354
                                klog.Errorf("failed to clean label from vip %s, %v", pod.Annotations[util.VipAnnotation], err)
×
1355
                                return err
×
1356
                        }
×
1357
                }
1358
        }
1359
        for _, podNet := range podNets {
×
1360
                // Skip non-OVN subnets for security group synchronization
×
1361
                if !isOvnSubnet(podNet.Subnet) {
×
1362
                        continue
×
1363
                }
1364

1365
                c.syncVirtualPortsQueue.Add(podNet.Subnet.Name)
×
1366
                securityGroupAnnotation := pod.Annotations[fmt.Sprintf(util.SecurityGroupAnnotationTemplate, podNet.ProviderName)]
×
1367
                if securityGroupAnnotation != "" {
×
1368
                        securityGroups := strings.ReplaceAll(securityGroupAnnotation, " ", "")
×
1369
                        for sgName := range strings.SplitSeq(securityGroups, ",") {
×
1370
                                if sgName != "" {
×
1371
                                        c.syncSgPortsQueue.Add(sgName)
×
1372
                                }
×
1373
                        }
1374
                }
1375
        }
1376
        return nil
×
1377
}
1378

1379
func (c *Controller) handleUpdatePodSecurity(key string) error {
×
1380
        now := time.Now()
×
1381
        klog.Infof("handle add/update pod security group %s", key)
×
1382

×
1383
        namespace, name, err := cache.SplitMetaNamespaceKey(key)
×
1384
        if err != nil {
×
1385
                utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
×
1386
                return nil
×
1387
        }
×
1388

1389
        c.podKeyMutex.LockKey(key)
×
1390
        defer func() {
×
1391
                _ = c.podKeyMutex.UnlockKey(key)
×
1392
                last := time.Since(now)
×
1393
                klog.Infof("take %d ms to handle sg for pod %s", last.Milliseconds(), key)
×
1394
        }()
×
1395

1396
        pod, err := c.podsLister.Pods(namespace).Get(name)
×
1397
        if err != nil {
×
1398
                if k8serrors.IsNotFound(err) {
×
1399
                        return nil
×
1400
                }
×
1401
                klog.Error(err)
×
1402
                return err
×
1403
        }
1404
        podName := c.getNameByPod(pod)
×
1405

×
1406
        podNets, err := c.getPodKubeovnNets(pod)
×
1407
        if err != nil {
×
1408
                klog.Errorf("failed to pod nets %v", err)
×
1409
                return err
×
1410
        }
×
1411

1412
        vipsMap := c.getVirtualIPs(pod, podNets)
×
1413

×
1414
        // associated with security group
×
1415
        for _, podNet := range podNets {
×
1416
                // Skip non-OVN subnets (e.g., macvlan) that don't create OVN logical switch ports
×
1417
                if !isOvnSubnet(podNet.Subnet) {
×
1418
                        continue
×
1419
                }
1420

1421
                portSecurity := false
×
1422
                if pod.Annotations[fmt.Sprintf(util.PortSecurityAnnotationTemplate, podNet.ProviderName)] == "true" {
×
1423
                        portSecurity = true
×
1424
                }
×
1425

1426
                mac := pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)]
×
1427
                ipStr := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]
×
1428
                vips := vipsMap[fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)]
×
1429
                portName := ovs.PodNameToPortName(podName, namespace, podNet.ProviderName)
×
1430
                if err = c.OVNNbClient.SetLogicalSwitchPortSecurity(portSecurity, portName, mac, ipStr, vips); err != nil {
×
1431
                        klog.Errorf("failed to set security for logical switch port %s: %v", portName, err)
×
1432
                        return err
×
1433
                }
×
1434

1435
                c.syncVirtualPortsQueue.Add(podNet.Subnet.Name)
×
1436
                securityGroupAnnotation := pod.Annotations[fmt.Sprintf(util.SecurityGroupAnnotationTemplate, podNet.ProviderName)]
×
1437
                var securityGroups string
×
1438
                if securityGroupAnnotation != "" {
×
1439
                        securityGroups = strings.ReplaceAll(securityGroupAnnotation, " ", "")
×
1440
                        for sgName := range strings.SplitSeq(securityGroups, ",") {
×
1441
                                if sgName != "" {
×
1442
                                        c.syncSgPortsQueue.Add(sgName)
×
1443
                                }
×
1444
                        }
1445
                }
1446
                if err = c.reconcilePortSg(portName, securityGroups); err != nil {
×
1447
                        klog.Errorf("reconcilePortSg failed. %v", err)
×
1448
                        return err
×
1449
                }
×
1450
        }
1451
        return nil
×
1452
}
1453

1454
func (c *Controller) syncKubeOvnNet(pod *v1.Pod, podNets []*kubeovnNet) (*v1.Pod, error) {
×
1455
        podName := c.getNameByPod(pod)
×
1456
        key := cache.NewObjectName(pod.Namespace, podName).String()
×
1457
        targetPortNameList := strset.NewWithSize(len(podNets))
×
1458
        portsNeedToDel := []string{}
×
1459
        annotationsNeedToDel := []string{}
×
1460
        annotationsNeedToAdd := make(map[string]string)
×
1461
        subnetUsedByPort := make(map[string]string)
×
1462

×
1463
        for _, podNet := range podNets {
×
1464
                portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
×
1465
                targetPortNameList.Add(portName)
×
1466
                if podNet.IPRequest != "" {
×
1467
                        klog.Infof("pod %s/%s use custom IP %s for provider %s", pod.Namespace, pod.Name, podNet.IPRequest, podNet.ProviderName)
×
1468
                        annotationsNeedToAdd[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] = podNet.IPRequest
×
1469
                }
×
1470

1471
                if podNet.MacRequest != "" {
×
1472
                        klog.Infof("pod %s/%s use custom MAC %s for provider %s", pod.Namespace, pod.Name, podNet.MacRequest, podNet.ProviderName)
×
1473
                        annotationsNeedToAdd[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] = podNet.MacRequest
×
1474
                }
×
1475
        }
1476

1477
        ports, err := c.OVNNbClient.ListNormalLogicalSwitchPorts(true, map[string]string{"pod": key})
×
1478
        if err != nil {
×
1479
                klog.Errorf("failed to list lsps of pod '%s', %v", pod.Name, err)
×
1480
                return nil, err
×
1481
        }
×
1482

1483
        for _, port := range ports {
×
1484
                if !targetPortNameList.Has(port.Name) {
×
1485
                        portsNeedToDel = append(portsNeedToDel, port.Name)
×
1486
                        subnetUsedByPort[port.Name] = port.ExternalIDs["ls"]
×
1487
                        portNameSlice := strings.Split(port.Name, ".")
×
1488
                        providerName := strings.Join(portNameSlice[2:], ".")
×
1489
                        if providerName == util.OvnProvider {
×
1490
                                continue
×
1491
                        }
1492
                        annotationsNeedToDel = append(annotationsNeedToDel, providerName)
×
1493
                }
1494
        }
1495

1496
        if len(portsNeedToDel) == 0 && len(annotationsNeedToAdd) == 0 {
×
1497
                return pod, nil
×
1498
        }
×
1499

1500
        for _, portNeedDel := range portsNeedToDel {
×
1501
                klog.Infof("release port %s for pod %s", portNeedDel, podName)
×
1502
                c.ipam.ReleaseAddressByNic(key, portNeedDel, subnetUsedByPort[portNeedDel])
×
1503
                if err := c.OVNNbClient.DeleteLogicalSwitchPort(portNeedDel); err != nil {
×
1504
                        klog.Errorf("failed to delete lsp %s, %v", portNeedDel, err)
×
1505
                        return nil, err
×
1506
                }
×
1507
                if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), portNeedDel, metav1.DeleteOptions{}); err != nil {
×
1508
                        if !k8serrors.IsNotFound(err) {
×
1509
                                klog.Errorf("failed to delete ip %s, %v", portNeedDel, err)
×
1510
                                return nil, err
×
1511
                        }
×
1512
                }
1513
        }
1514

1515
        patch := util.KVPatch{}
×
1516
        for _, providerName := range annotationsNeedToDel {
×
1517
                for key := range pod.Annotations {
×
1518
                        if strings.HasPrefix(key, providerName) {
×
1519
                                patch[key] = nil
×
1520
                        }
×
1521
                }
1522
        }
1523

1524
        for key, value := range annotationsNeedToAdd {
×
1525
                patch[key] = value
×
1526
        }
×
1527

1528
        if len(patch) == 0 {
×
1529
                return pod, nil
×
1530
        }
×
1531

1532
        if err = util.PatchAnnotations(c.config.KubeClient.CoreV1().Pods(pod.Namespace), pod.Name, patch); err != nil {
×
1533
                if k8serrors.IsNotFound(err) {
×
1534
                        return nil, nil
×
1535
                }
×
1536
                klog.Errorf("failed to clean annotations for pod %s/%s: %v", pod.Namespace, pod.Name, err)
×
1537
                return nil, err
×
1538
        }
1539

1540
        if pod, err = c.config.KubeClient.CoreV1().Pods(pod.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}); err != nil {
×
1541
                if k8serrors.IsNotFound(err) {
×
1542
                        return nil, nil
×
1543
                }
×
1544
                klog.Errorf("failed to get pod %s/%s: %v", pod.Namespace, pod.Name, err)
×
1545
                return nil, err
×
1546
        }
1547

1548
        return pod, nil
×
1549
}
1550

1551
func isStatefulSetPod(pod *v1.Pod) (bool, string, types.UID) {
1✔
1552
        for _, owner := range pod.OwnerReferences {
2✔
1553
                if owner.Kind == util.KindStatefulSet && strings.HasPrefix(owner.APIVersion, appsv1.SchemeGroupVersion.Group+"/") {
2✔
1554
                        if strings.HasPrefix(pod.Name, owner.Name) {
2✔
1555
                                return true, owner.Name, owner.UID
1✔
1556
                        }
1✔
1557
                }
1558
        }
1559
        return false, "", ""
1✔
1560
}
1561

1562
func isStatefulSetPodToDel(c kubernetes.Interface, pod *v1.Pod, statefulSetName string, statefulSetUID types.UID) bool {
×
1563
        // only delete statefulset pod lsp when statefulset deleted or down scaled
×
1564
        sts, err := c.AppsV1().StatefulSets(pod.Namespace).Get(context.Background(), statefulSetName, metav1.GetOptions{})
×
1565
        if err != nil {
×
1566
                // statefulset is deleted
×
1567
                if k8serrors.IsNotFound(err) {
×
1568
                        klog.Infof("statefulset %s/%s has been deleted", pod.Namespace, statefulSetName)
×
1569
                        return true
×
1570
                }
×
1571
                klog.Errorf("failed to get statefulset %s/%s: %v", pod.Namespace, statefulSetName, err)
×
1572
                return false
×
1573
        }
1574

1575
        // statefulset is being deleted, or it's a newly created one
1576
        if !sts.DeletionTimestamp.IsZero() {
×
1577
                klog.Infof("statefulset %s/%s is being deleted", pod.Namespace, statefulSetName)
×
1578
                return true
×
1579
        }
×
1580
        if sts.UID != statefulSetUID {
×
1581
                klog.Infof("statefulset %s/%s is a newly created one", pod.Namespace, statefulSetName)
×
1582
                return true
×
1583
        }
×
1584

1585
        // down scale statefulset
1586
        tempStrs := strings.Split(pod.Name, "-")
×
1587
        numStr := tempStrs[len(tempStrs)-1]
×
1588
        index, err := strconv.ParseInt(numStr, 10, 0)
×
1589
        if err != nil {
×
1590
                klog.Errorf("failed to parse %s to int", numStr)
×
1591
                return false
×
1592
        }
×
1593
        // down scaled
1594
        var startOrdinal int64
×
1595
        if sts.Spec.Ordinals != nil {
×
1596
                startOrdinal = int64(sts.Spec.Ordinals.Start)
×
1597
        }
×
1598
        if index >= startOrdinal+int64(*sts.Spec.Replicas) {
×
1599
                klog.Infof("statefulset %s/%s is down scaled", pod.Namespace, statefulSetName)
×
1600
                return true
×
1601
        }
×
1602
        return false
×
1603
}
1604

1605
// only gc statefulset pod lsp when:
1606
// 1. the statefulset has been deleted or is being deleted
1607
// 2. the statefulset has been deleted and recreated
1608
// 3. the statefulset is down scaled and the pod is not alive
1609
func isStatefulSetPodToGC(c kubernetes.Interface, pod *v1.Pod, statefulSetName string, statefulSetUID types.UID) bool {
×
1610
        sts, err := c.AppsV1().StatefulSets(pod.Namespace).Get(context.Background(), statefulSetName, metav1.GetOptions{})
×
1611
        if err != nil {
×
1612
                // the statefulset has been deleted
×
1613
                if k8serrors.IsNotFound(err) {
×
1614
                        klog.Infof("statefulset %s/%s has been deleted", pod.Namespace, statefulSetName)
×
1615
                        return true
×
1616
                }
×
1617
                klog.Errorf("failed to get statefulset %s/%s: %v", pod.Namespace, statefulSetName, err)
×
1618
                return false
×
1619
        }
1620

1621
        // statefulset is being deleted
1622
        if !sts.DeletionTimestamp.IsZero() {
×
1623
                klog.Infof("statefulset %s/%s is being deleted", pod.Namespace, statefulSetName)
×
1624
                return true
×
1625
        }
×
1626
        // the statefulset has been deleted and recreated
1627
        if sts.UID != statefulSetUID {
×
1628
                klog.Infof("statefulset %s/%s is a newly created one", pod.Namespace, statefulSetName)
×
1629
                return true
×
1630
        }
×
1631

1632
        // the statefulset is down scaled and the pod is not alive
1633

1634
        tempStrs := strings.Split(pod.Name, "-")
×
1635
        numStr := tempStrs[len(tempStrs)-1]
×
1636
        index, err := strconv.ParseInt(numStr, 10, 0)
×
1637
        if err != nil {
×
1638
                klog.Errorf("failed to parse %s to int", numStr)
×
1639
                return false
×
1640
        }
×
1641
        // down scaled
1642
        var startOrdinal int64
×
1643
        if sts.Spec.Ordinals != nil {
×
1644
                startOrdinal = int64(sts.Spec.Ordinals.Start)
×
1645
        }
×
1646
        if index >= startOrdinal+int64(*sts.Spec.Replicas) {
×
1647
                klog.Infof("statefulset %s/%s is down scaled", pod.Namespace, statefulSetName)
×
1648
                if !isPodAlive(pod) {
×
1649
                        // we must check whether the pod is alive because we have to consider the following case:
×
1650
                        // 1. the statefulset is down scaled to zero
×
1651
                        // 2. the lsp gc is triggered
×
1652
                        // 3. gc interval, e.g. 90s, is passed and the second gc is triggered
×
1653
                        // 4. the sts is up scaled to the original replicas
×
1654
                        // 5. the pod is still running and it will not be recreated
×
1655
                        return true
×
1656
                }
×
1657
        }
1658

1659
        return false
×
1660
}
1661

1662
func getNodeTunlIP(node *v1.Node) ([]net.IP, error) {
×
1663
        var nodeTunlIPAddr []net.IP
×
1664
        nodeTunlIP := node.Annotations[util.IPAddressAnnotation]
×
1665
        if nodeTunlIP == "" {
×
1666
                return nil, errors.New("node has no tunnel ip annotation")
×
1667
        }
×
1668

1669
        for ip := range strings.SplitSeq(nodeTunlIP, ",") {
×
1670
                parsed := net.ParseIP(ip)
×
1671
                if parsed == nil {
×
1672
                        return nil, fmt.Errorf("failed to parse tunnel IP %q on node %s", ip, node.Name)
×
1673
                }
×
1674
                nodeTunlIPAddr = append(nodeTunlIPAddr, parsed)
×
1675
        }
1676
        return nodeTunlIPAddr, nil
×
1677
}
1678

1679
func getNextHopByTunnelIP(gw []net.IP) string {
×
1680
        // validation check by caller
×
1681
        nextHop := gw[0].String()
×
1682
        if len(gw) == 2 {
×
1683
                nextHop = gw[0].String() + "," + gw[1].String()
×
1684
        }
×
1685
        return nextHop
×
1686
}
1687

1688
func needAllocateSubnets(pod *v1.Pod, nets []*kubeovnNet) []*kubeovnNet {
×
1689
        // check if allocate from subnet is need.
×
1690
        // allocate subnet when change subnet to hotplug nic
×
1691
        // allocate subnet when migrate vm
×
1692
        if !isPodAlive(pod) {
×
1693
                return nil
×
1694
        }
×
1695

1696
        if pod.Annotations == nil {
×
1697
                return nets
×
1698
        }
×
1699

1700
        migrate := false
×
1701
        if job, ok := pod.Annotations[kubevirtv1.MigrationJobNameAnnotation]; ok {
×
1702
                klog.Infof("pod %s/%s is in the migration job %s", pod.Namespace, pod.Name, job)
×
1703
                migrate = true
×
1704
        }
×
1705

1706
        result := make([]*kubeovnNet, 0, len(nets))
×
1707
        for _, n := range nets {
×
1708
                if migrate || pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, n.ProviderName)] != "true" {
×
1709
                        result = append(result, n)
×
1710
                }
×
1711
        }
1712
        return result
×
1713
}
1714

1715
func needRestartNatGatewayPod(pod *v1.Pod) bool {
×
1716
        for _, psc := range pod.Status.ContainerStatuses {
×
1717
                if psc.Name != "vpc-nat-gw" {
×
1718
                        continue
×
1719
                }
1720
                if psc.RestartCount > 0 {
×
1721
                        return true
×
1722
                }
×
1723
        }
1724
        return false
×
1725
}
1726

1727
func (c *Controller) podNeedSync(pod *v1.Pod) (bool, error) {
×
1728
        // 1. check annotations
×
1729
        if pod.Annotations == nil {
×
1730
                return true, nil
×
1731
        }
×
1732
        // 2. check annotation ovn subnet
1733
        if pod.Annotations[util.RoutedAnnotation] != "true" {
×
1734
                return true, nil
×
1735
        }
×
1736
        // 3. check multus subnet
1737
        attachmentNets, err := c.getPodAttachmentNet(pod)
×
1738
        if err != nil {
×
1739
                klog.Error(err)
×
1740
                return false, err
×
1741
        }
×
1742

1743
        podName := c.getNameByPod(pod)
×
1744
        for _, n := range attachmentNets {
×
1745
                if pod.Annotations[fmt.Sprintf(util.RoutedAnnotationTemplate, n.ProviderName)] != "true" {
×
1746
                        return true, nil
×
1747
                }
×
1748
                ipName := ovs.PodNameToPortName(podName, pod.Namespace, n.ProviderName)
×
1749
                if _, err = c.ipsLister.Get(ipName); err != nil {
×
1750
                        if !k8serrors.IsNotFound(err) {
×
1751
                                err = fmt.Errorf("failed to get ip %s: %w", ipName, err)
×
1752
                                klog.Error(err)
×
1753
                                return false, err
×
1754
                        }
×
1755
                        klog.Infof("ip %s not found", ipName)
×
1756
                        // need to sync to create ip
×
1757
                        return true, nil
×
1758
                }
1759
        }
1760
        return false, nil
×
1761
}
1762

1763
func needRouteSubnets(pod *v1.Pod, nets []*kubeovnNet) []*kubeovnNet {
×
1764
        if !isPodAlive(pod) {
×
1765
                return nil
×
1766
        }
×
1767

1768
        if pod.Annotations == nil {
×
1769
                return nets
×
1770
        }
×
1771

1772
        result := make([]*kubeovnNet, 0, len(nets))
×
1773
        for _, n := range nets {
×
1774
                if !isOvnSubnet(n.Subnet) {
×
1775
                        continue
×
1776
                }
1777

1778
                if pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, n.ProviderName)] == "true" && pod.Spec.NodeName != "" {
×
1779
                        if pod.Annotations[fmt.Sprintf(util.RoutedAnnotationTemplate, n.ProviderName)] != "true" {
×
1780
                                result = append(result, n)
×
1781
                        }
×
1782
                }
1783
        }
1784
        return result
×
1785
}
1786

1787
func (c *Controller) getPodDefaultSubnet(pod *v1.Pod) (*kubeovnv1.Subnet, error) {
1✔
1788
        // ignore to clean its ip crd in existing subnets
1✔
1789
        ignoreSubnetNotExist := !pod.DeletionTimestamp.IsZero()
1✔
1790

1✔
1791
        // check pod annotations
1✔
1792
        if lsName := pod.Annotations[util.LogicalSwitchAnnotation]; lsName != "" {
2✔
1793
                // annotations only has one default subnet
1✔
1794
                subnet, err := c.subnetsLister.Get(lsName)
1✔
1795
                if err != nil {
1✔
1796
                        klog.Errorf("failed to get subnet %s: %v", lsName, err)
×
1797
                        if k8serrors.IsNotFound(err) {
×
1798
                                if ignoreSubnetNotExist {
×
1799
                                        klog.Errorf("deleting pod %s/%s default subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, lsName)
×
1800
                                        return nil, nil
×
1801
                                }
×
1802
                        }
1803
                        return nil, err
×
1804
                }
1805
                return subnet, nil
1✔
1806
        }
1807

1808
        ns, err := c.namespacesLister.Get(pod.Namespace)
1✔
1809
        if err != nil {
1✔
1810
                klog.Errorf("failed to get namespace %s: %v", pod.Namespace, err)
×
1811
                return nil, err
×
1812
        }
×
1813
        if len(ns.Annotations) == 0 {
1✔
1814
                err = fmt.Errorf("namespace %s network annotations is empty", ns.Name)
×
1815
                klog.Error(err)
×
1816
                return nil, err
×
1817
        }
×
1818

1819
        subnetNames := ns.Annotations[util.LogicalSwitchAnnotation]
1✔
1820
        for subnetName := range strings.SplitSeq(subnetNames, ",") {
2✔
1821
                if subnetName == "" {
1✔
1822
                        err = fmt.Errorf("namespace %s default logical switch is not found", ns.Name)
×
1823
                        klog.Error(err)
×
1824
                        return nil, err
×
1825
                }
×
1826
                subnet, err := c.subnetsLister.Get(subnetName)
1✔
1827
                if err != nil {
1✔
1828
                        klog.Errorf("failed to get subnet %s: %v", subnetName, err)
×
1829
                        if k8serrors.IsNotFound(err) {
×
1830
                                if ignoreSubnetNotExist {
×
1831
                                        klog.Errorf("deleting pod %s/%s namespace subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
1832
                                        // ip name is unique, it is ok if any subnet release it
×
1833
                                        // gc will handle their ip cr, if all subnets are not exist
×
1834
                                        continue
×
1835
                                }
1836
                        }
1837
                        return nil, err
×
1838
                }
1839

1840
                switch subnet.Spec.Protocol {
1✔
1841
                case kubeovnv1.ProtocolDual:
×
1842
                        if subnet.Status.V6AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) {
×
1843
                                klog.Infof("there's no available ipv6 address in subnet %s, try next one", subnet.Name)
×
1844
                                continue
×
1845
                        }
1846
                        fallthrough
×
1847
                case kubeovnv1.ProtocolIPv4:
1✔
1848
                        if subnet.Status.V4AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) {
1✔
1849
                                klog.Infof("there's no available ipv4 address in subnet %s, try next one", subnet.Name)
×
1850
                                continue
×
1851
                        }
1852
                case kubeovnv1.ProtocolIPv6:
×
1853
                        if subnet.Status.V6AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) {
×
1854
                                klog.Infof("there's no available ipv6 address in subnet %s, try next one", subnet.Name)
×
1855
                                continue
×
1856
                        }
1857
                }
1858
                return subnet, nil
1✔
1859
        }
1860
        return nil, ipam.ErrNoAvailable
×
1861
}
1862

1863
func (c *Controller) podCanUseExcludeIPs(pod *v1.Pod, subnet *kubeovnv1.Subnet) bool {
×
1864
        if ipAddr := pod.Annotations[util.IPAddressAnnotation]; ipAddr != "" {
×
1865
                return c.checkIPsInExcludeList(ipAddr, subnet.Spec.ExcludeIps, subnet.Spec.CIDRBlock)
×
1866
        }
×
1867
        if ipPool := pod.Annotations[util.IPPoolAnnotation]; ipPool != "" {
×
1868
                return c.checkIPsInExcludeList(ipPool, subnet.Spec.ExcludeIps, subnet.Spec.CIDRBlock)
×
1869
        }
×
1870

1871
        return false
×
1872
}
1873

1874
func (c *Controller) checkIPsInExcludeList(ips string, excludeIPs []string, cidr string) bool {
×
1875
        expandedExcludeIPs := util.ExpandExcludeIPs(excludeIPs, cidr)
×
1876

×
1877
        for ipAddr := range strings.SplitSeq(strings.TrimSpace(ips), ",") {
×
1878
                ipAddr = strings.TrimSpace(ipAddr)
×
1879
                if ipAddr == "" {
×
1880
                        continue
×
1881
                }
1882

1883
                for _, excludeIP := range expandedExcludeIPs {
×
1884
                        if util.ContainsIPs(excludeIP, ipAddr) {
×
1885
                                klog.V(3).Infof("IP %s is found in exclude IP %s, allowing allocation", ipAddr, excludeIP)
×
1886
                                return true
×
1887
                        }
×
1888
                }
1889
        }
1890
        return false
×
1891
}
1892

1893
type providerType int
1894

1895
const (
1896
        providerTypeIPAM providerType = iota
1897
        providerTypeOriginal
1898
)
1899

1900
type kubeovnNet struct {
1901
        Type               providerType
1902
        ProviderName       string
1903
        Subnet             *kubeovnv1.Subnet
1904
        IsDefault          bool
1905
        AllowLiveMigration bool
1906
        IPRequest          string
1907
        MacRequest         string
1908
        NadName            string
1909
        NadNamespace       string
1910
        InterfaceName      string
1911
}
1912

1913
func (c *Controller) getPodAttachmentNet(pod *v1.Pod) ([]*kubeovnNet, error) {
1✔
1914
        var multusNets []*nadv1.NetworkSelectionElement
1✔
1915
        defaultAttachNetworks := pod.Annotations[util.DefaultNetworkAnnotation]
1✔
1916
        if defaultAttachNetworks != "" {
1✔
1917
                attachments, err := nadutils.ParseNetworkAnnotation(defaultAttachNetworks, pod.Namespace)
×
1918
                if err != nil {
×
1919
                        klog.Errorf("failed to parse default attach net for pod '%s', %v", pod.Name, err)
×
1920
                        return nil, err
×
1921
                }
×
1922
                multusNets = attachments
×
1923
        }
1924

1925
        attachNetworks := pod.Annotations[nadv1.NetworkAttachmentAnnot]
1✔
1926
        if attachNetworks != "" {
2✔
1927
                attachments, err := nadutils.ParseNetworkAnnotation(attachNetworks, pod.Namespace)
1✔
1928
                if err != nil {
1✔
1929
                        klog.Errorf("failed to parse attach net for pod '%s', %v", pod.Name, err)
×
1930
                        return nil, err
×
1931
                }
×
1932
                multusNets = append(multusNets, attachments...)
1✔
1933
        }
1934
        subnets, err := c.subnetsLister.List(labels.Everything())
1✔
1935
        if err != nil {
1✔
1936
                klog.Errorf("failed to list subnets: %v", err)
×
1937
                return nil, err
×
1938
        }
×
1939

1940
        // ignore to return all existing subnets to clean its ip crd
1941
        ignoreSubnetNotExist := !pod.DeletionTimestamp.IsZero()
1✔
1942

1✔
1943
        nadCounts := make(map[string]int)
1✔
1944
        for _, attach := range multusNets {
2✔
1945
                nadCounts[fmt.Sprintf("%s/%s", attach.Namespace, attach.Name)]++
1✔
1946
        }
1✔
1947

1948
        result := make([]*kubeovnNet, 0, len(multusNets))
1✔
1949
        for _, attach := range multusNets {
2✔
1950
                nadKey := fmt.Sprintf("%s/%s", attach.Namespace, attach.Name)
1✔
1951
                network, err := c.netAttachLister.NetworkAttachmentDefinitions(attach.Namespace).Get(attach.Name)
1✔
1952
                if err != nil {
1✔
1953
                        klog.Errorf("failed to get net-attach-def %s, %v", attach.Name, err)
×
1954
                        if k8serrors.IsNotFound(err) && ignoreSubnetNotExist {
×
1955
                                // NAD deleted before pod, find subnet for cleanup
×
1956
                                providerName := fmt.Sprintf("%s.%s.%s", attach.Name, attach.Namespace, util.OvnProvider)
×
1957
                                if nadCounts[nadKey] > 1 && attach.InterfaceRequest != "" {
×
1958
                                        providerName = fmt.Sprintf("%s.%s", providerName, attach.InterfaceRequest)
×
1959
                                }
×
1960
                                subnetName := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, providerName)]
×
1961
                                if subnetName == "" {
×
1962
                                        for _, subnet := range subnets {
×
1963
                                                if subnet.Spec.Provider == providerName {
×
1964
                                                        subnetName = subnet.Name
×
1965
                                                        break
×
1966
                                                }
1967
                                        }
1968
                                }
1969

1970
                                if subnetName == "" {
×
1971
                                        klog.Errorf("deleting pod %s/%s net-attach-def %s not found and cannot determine subnet, gc will clean its ip cr", pod.Namespace, pod.Name, attach.Name)
×
1972
                                        continue
×
1973
                                }
1974

1975
                                subnet, err := c.subnetsLister.Get(subnetName)
×
1976
                                if err != nil {
×
1977
                                        klog.Errorf("failed to get subnet %s, %v", subnetName, err)
×
1978
                                        if k8serrors.IsNotFound(err) {
×
1979
                                                klog.Errorf("deleting pod %s/%s attach subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
1980
                                                continue
×
1981
                                        }
1982
                                        return nil, err
×
1983
                                }
1984

1985
                                klog.Infof("pod %s/%s net-attach-def %s not found, using subnet %s for cleanup", pod.Namespace, pod.Name, attach.Name, subnetName)
×
1986
                                result = append(result, &kubeovnNet{
×
1987
                                        Type:          providerTypeIPAM,
×
1988
                                        ProviderName:  providerName,
×
1989
                                        Subnet:        subnet,
×
1990
                                        IsDefault:     util.IsDefaultNet(pod.Annotations[util.DefaultNetworkAnnotation], attach),
×
1991
                                        NadName:       attach.Name,
×
1992
                                        NadNamespace:  attach.Namespace,
×
1993
                                        InterfaceName: attach.InterfaceRequest,
×
1994
                                })
×
1995
                                continue
×
1996
                        }
1997
                        return nil, err
×
1998
                }
1999

2000
                if network.Spec.Config == "" {
1✔
2001
                        continue
×
2002
                }
2003

2004
                netCfg, err := loadNetConf([]byte(network.Spec.Config))
1✔
2005
                if err != nil {
1✔
2006
                        klog.Errorf("failed to load config of net-attach-def %s, %v", attach.Name, err)
×
2007
                        return nil, err
×
2008
                }
×
2009

2010
                // allocate kubeovn network
2011
                var providerName string
1✔
2012
                if util.IsOvnNetwork(netCfg) {
2✔
2013
                        allowLiveMigration := false
1✔
2014
                        isDefault := util.IsDefaultNet(pod.Annotations[util.DefaultNetworkAnnotation], attach)
1✔
2015

1✔
2016
                        providerName = fmt.Sprintf("%s.%s.%s", attach.Name, attach.Namespace, util.OvnProvider)
1✔
2017
                        if nadCounts[nadKey] > 1 && attach.InterfaceRequest != "" {
1✔
2018
                                providerName = fmt.Sprintf("%s.%s", providerName, attach.InterfaceRequest)
×
2019
                        }
×
2020
                        if pod.Annotations[kubevirtv1.MigrationJobNameAnnotation] != "" {
1✔
2021
                                allowLiveMigration = true
×
2022
                        }
×
2023

2024
                        subnetName := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, providerName)]
1✔
2025
                        if subnetName == "" {
1✔
2026
                                for _, subnet := range subnets {
×
2027
                                        if subnet.Spec.Provider == providerName {
×
2028
                                                subnetName = subnet.Name
×
2029
                                                break
×
2030
                                        }
2031
                                }
2032
                        }
2033
                        var subnet *kubeovnv1.Subnet
1✔
2034
                        if subnetName == "" {
1✔
2035
                                // attachment network not specify subnet, use pod default subnet or namespace subnet
×
2036
                                subnet, err = c.getPodDefaultSubnet(pod)
×
2037
                                if err != nil {
×
2038
                                        klog.Errorf("failed to pod default subnet, %v", err)
×
2039
                                        if k8serrors.IsNotFound(err) {
×
2040
                                                if ignoreSubnetNotExist {
×
2041
                                                        klog.Errorf("deleting pod %s/%s attach subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
2042
                                                        continue
×
2043
                                                }
2044
                                        }
2045
                                        return nil, err
×
2046
                                }
2047
                                // default subnet may change after pod restart
2048
                                klog.Infof("pod %s/%s attachment network %s use default subnet %s", pod.Namespace, pod.Name, attach.Name, subnet.Name)
×
2049
                        } else {
1✔
2050
                                subnet, err = c.subnetsLister.Get(subnetName)
1✔
2051
                                if err != nil {
1✔
2052
                                        klog.Errorf("failed to get subnet %s, %v", subnetName, err)
×
2053
                                        if k8serrors.IsNotFound(err) {
×
2054
                                                if ignoreSubnetNotExist {
×
2055
                                                        klog.Errorf("deleting pod %s/%s attach subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
2056
                                                        // just continue to next attach subnet
×
2057
                                                        // ip name is unique, so it is ok if the other subnet release it
×
2058
                                                        continue
×
2059
                                                }
2060
                                        }
2061
                                        return nil, err
×
2062
                                }
2063
                        }
2064

2065
                        ret := &kubeovnNet{
1✔
2066
                                Type:               providerTypeOriginal,
1✔
2067
                                ProviderName:       providerName,
1✔
2068
                                Subnet:             subnet,
1✔
2069
                                IsDefault:          isDefault,
1✔
2070
                                AllowLiveMigration: allowLiveMigration,
1✔
2071
                                MacRequest:         attach.MacRequest,
1✔
2072
                                IPRequest:          strings.Join(attach.IPRequest, ","),
1✔
2073
                                NadName:            attach.Name,
1✔
2074
                                NadNamespace:       attach.Namespace,
1✔
2075
                                InterfaceName:      attach.InterfaceRequest,
1✔
2076
                        }
1✔
2077
                        result = append(result, ret)
1✔
2078
                } else {
×
2079
                        providerName = fmt.Sprintf("%s.%s", attach.Name, attach.Namespace)
×
2080
                        for _, subnet := range subnets {
×
2081
                                if subnet.Spec.Provider == providerName {
×
2082
                                        result = append(result, &kubeovnNet{
×
2083
                                                Type:          providerTypeIPAM,
×
2084
                                                ProviderName:  providerName,
×
2085
                                                Subnet:        subnet,
×
2086
                                                MacRequest:    attach.MacRequest,
×
2087
                                                IPRequest:     strings.Join(attach.IPRequest, ","),
×
2088
                                                NadName:       attach.Name,
×
2089
                                                NadNamespace:  attach.Namespace,
×
2090
                                                InterfaceName: attach.InterfaceRequest,
×
2091
                                        })
×
2092
                                        break
×
2093
                                }
2094
                        }
2095
                }
2096
        }
2097
        return result, nil
1✔
2098
}
2099

2100
func (c *Controller) validatePodIP(podName, subnetName, ipv4, ipv6 string) (bool, bool, error) {
×
2101
        subnet, err := c.subnetsLister.Get(subnetName)
×
2102
        if err != nil {
×
2103
                klog.Errorf("failed to get subnet %s: %v", subnetName, err)
×
2104
                return false, false, err
×
2105
        }
×
2106

2107
        if subnet.Spec.Vlan == "" && subnet.Spec.Vpc == c.config.ClusterRouter {
×
2108
                nodes, err := c.nodesLister.List(labels.Everything())
×
2109
                if err != nil {
×
2110
                        klog.Errorf("failed to list nodes: %v", err)
×
2111
                        return false, false, err
×
2112
                }
×
2113

2114
                for _, node := range nodes {
×
2115
                        nodeIPv4, nodeIPv6 := util.GetNodeInternalIP(*node)
×
2116
                        if ipv4 != "" && ipv4 == nodeIPv4 {
×
2117
                                klog.Errorf("IP address (%s) assigned to pod %s is the same with internal IP address of node %s, reallocating...", ipv4, podName, node.Name)
×
2118
                                return false, true, nil
×
2119
                        }
×
2120
                        if ipv6 != "" && ipv6 == nodeIPv6 {
×
2121
                                klog.Errorf("IP address (%s) assigned to pod %s is the same with internal IP address of node %s, reallocating...", ipv6, podName, node.Name)
×
2122
                                return true, false, nil
×
2123
                        }
×
2124
                }
2125
        }
2126

2127
        return true, true, nil
×
2128
}
2129

2130
func (c *Controller) acquireAddress(pod *v1.Pod, podNet *kubeovnNet) (string, string, string, *kubeovnv1.Subnet, error) {
1✔
2131
        podName := c.getNameByPod(pod)
1✔
2132
        key := cache.NewObjectName(pod.Namespace, podName).String()
1✔
2133
        portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
1✔
2134

1✔
2135
        var checkVMPod bool
1✔
2136
        isStsPod, _, _ := isStatefulSetPod(pod)
1✔
2137
        // if pod has static vip
1✔
2138
        vipName := pod.Annotations[util.VipAnnotation]
1✔
2139
        if vipName != "" {
1✔
2140
                vip, err := c.virtualIpsLister.Get(vipName)
×
2141
                if err != nil {
×
2142
                        klog.Errorf("failed to get static vip '%s', %v", vipName, err)
×
2143
                        return "", "", "", podNet.Subnet, err
×
2144
                }
×
2145
                if c.config.EnableKeepVMIP {
×
2146
                        checkVMPod, _ = isVMPod(pod)
×
2147
                }
×
2148
                if err = c.podReuseVip(vipName, portName, isStsPod || checkVMPod); err != nil {
×
2149
                        return "", "", "", podNet.Subnet, err
×
2150
                }
×
2151
                return vip.Status.V4ip, vip.Status.V6ip, vip.Status.Mac, podNet.Subnet, nil
×
2152
        }
2153

2154
        var macPointer *string
1✔
2155
        if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
1✔
2156
                key := perInterfaceMACAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
×
2157
                if macStr := pod.Annotations[key]; macStr != "" {
×
2158
                        if _, err := net.ParseMAC(macStr); err != nil {
×
2159
                                return "", "", "", podNet.Subnet, err
×
2160
                        }
×
2161
                        macPointer = &macStr
×
2162
                }
2163
        }
2164

2165
        if macPointer == nil && isOvnSubnet(podNet.Subnet) {
2✔
2166
                annoMAC := pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)]
1✔
2167
                if annoMAC != "" {
1✔
2168
                        if _, err := net.ParseMAC(annoMAC); err != nil {
×
2169
                                return "", "", "", podNet.Subnet, err
×
2170
                        }
×
2171
                        macPointer = &annoMAC
×
2172
                }
2173
        } else if macPointer == nil {
×
2174
                macPointer = new("")
×
2175
        }
×
2176

2177
        var nsNets []*kubeovnNet
1✔
2178
        ippoolStr := pod.Annotations[fmt.Sprintf(util.IPPoolAnnotationTemplate, podNet.ProviderName)]
1✔
2179
        subnetStr := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName)]
1✔
2180

1✔
2181
        // Prepare nsNets based on subnet annotation
1✔
2182
        var err error
1✔
2183
        if subnetStr != "" {
2✔
2184
                nsNets = []*kubeovnNet{podNet}
1✔
2185
        } else if nsNets, err = c.getNsAvailableSubnets(pod, podNet); err != nil {
2✔
2186
                klog.Errorf("failed to get available subnets for pod %s/%s, %v", pod.Namespace, pod.Name, err)
×
2187
                return "", "", "", podNet.Subnet, err
×
2188
        }
×
2189

2190
        if ippoolStr == "" && podNet.IsDefault {
2✔
2191
                // no ippool specified by pod annotation, use namespace ippools or ippools in the subnet specified by pod annotation
1✔
2192
                ns, err := c.namespacesLister.Get(pod.Namespace)
1✔
2193
                if err != nil {
1✔
2194
                        klog.Errorf("failed to get namespace %s: %v", pod.Namespace, err)
×
2195
                        return "", "", "", podNet.Subnet, err
×
2196
                }
×
2197
                subnetNames := make([]string, 0, len(nsNets))
1✔
2198
                for _, net := range nsNets {
2✔
2199
                        if net.Subnet.Name == subnetStr {
2✔
2200
                                // allocate from ippools in the subnet specified by pod annotation
1✔
2201
                                podNet.Subnet = net.Subnet
1✔
2202
                                subnetNames = []string{net.Subnet.Name}
1✔
2203
                                break
1✔
2204
                        }
2205
                        subnetNames = append(subnetNames, net.Subnet.Name)
1✔
2206
                }
2207

2208
                if subnetStr == "" || slices.Contains(subnetNames, subnetStr) {
2✔
2209
                        // no subnet specified by pod annotation or specified subnet is in namespace subnets
1✔
2210
                        if ipPoolList, ok := ns.Annotations[util.IPPoolAnnotation]; ok {
1✔
2211
                                for ipPoolName := range strings.SplitSeq(ipPoolList, ",") {
×
2212
                                        ippool, err := c.ippoolLister.Get(ipPoolName)
×
2213
                                        if err != nil {
×
2214
                                                klog.Errorf("failed to get ippool %s: %v", ipPoolName, err)
×
2215
                                                return "", "", "", podNet.Subnet, err
×
2216
                                        }
×
2217

2218
                                        switch podNet.Subnet.Spec.Protocol {
×
2219
                                        case kubeovnv1.ProtocolDual:
×
2220
                                                if ippool.Status.V4AvailableIPs.Int64() == 0 || ippool.Status.V6AvailableIPs.Int64() == 0 {
×
2221
                                                        continue
×
2222
                                                }
2223
                                        case kubeovnv1.ProtocolIPv4:
×
2224
                                                if ippool.Status.V4AvailableIPs.Int64() == 0 {
×
2225
                                                        continue
×
2226
                                                }
2227

2228
                                        default:
×
2229
                                                if ippool.Status.V6AvailableIPs.Int64() == 0 {
×
2230
                                                        continue
×
2231
                                                }
2232
                                        }
2233

2234
                                        for _, net := range nsNets {
×
2235
                                                if net.Subnet.Name == ippool.Spec.Subnet && slices.Contains(subnetNames, net.Subnet.Name) {
×
2236
                                                        ippoolStr = ippool.Name
×
2237
                                                        podNet.Subnet = net.Subnet
×
2238
                                                        break
×
2239
                                                }
2240
                                        }
2241
                                        if ippoolStr != "" {
×
2242
                                                break
×
2243
                                        }
2244
                                }
2245
                                if ippoolStr == "" {
×
2246
                                        klog.Infof("no available ippool in subnet(s) %s for pod %s/%s", strings.Join(subnetNames, ","), pod.Namespace, pod.Name)
×
2247
                                        return "", "", "", podNet.Subnet, ipam.ErrNoAvailable
×
2248
                                }
×
2249
                        }
2250
                }
2251
        }
2252

2253
        // Random allocate
2254
        if pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] == "" &&
1✔
2255
                ippoolStr == "" {
1✔
2256
                // check new IP annotation
×
2257
                if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
×
2258
                        annoKey := perInterfaceIPAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
×
2259
                        if ipStr := pod.Annotations[annoKey]; ipStr != "" {
×
2260
                                return c.acquireStaticAddressHelper(pod, podNet, portName, macPointer, ippoolStr, nsNets, isStsPod, key)
×
2261
                        }
×
2262
                }
2263

2264
                var skippedAddrs []string
×
2265
                for {
×
2266
                        ipv4, ipv6, mac, err := c.ipam.GetRandomAddress(key, portName, macPointer, podNet.Subnet.Name, "", skippedAddrs, !podNet.AllowLiveMigration)
×
2267
                        if err != nil {
×
2268
                                klog.Error(err)
×
2269
                                return "", "", "", podNet.Subnet, err
×
2270
                        }
×
2271
                        ipv4OK, ipv6OK, err := c.validatePodIP(pod.Name, podNet.Subnet.Name, ipv4, ipv6)
×
2272
                        if err != nil {
×
2273
                                klog.Error(err)
×
2274
                                return "", "", "", podNet.Subnet, err
×
2275
                        }
×
2276
                        if ipv4OK && ipv6OK {
×
2277
                                return ipv4, ipv6, mac, podNet.Subnet, nil
×
2278
                        }
×
2279

2280
                        if !ipv4OK {
×
2281
                                skippedAddrs = append(skippedAddrs, ipv4)
×
2282
                        }
×
2283
                        if !ipv6OK {
×
2284
                                skippedAddrs = append(skippedAddrs, ipv6)
×
2285
                        }
×
2286
                }
2287
        }
2288

2289
        return c.acquireStaticAddressHelper(pod, podNet, portName, macPointer, ippoolStr, nsNets, isStsPod, key)
1✔
2290
}
2291

2292
func (c *Controller) acquireStaticAddressHelper(pod *v1.Pod, podNet *kubeovnNet, portName string, macPointer *string, ippoolStr string, nsNets []*kubeovnNet, isStsPod bool, key string) (string, string, string, *kubeovnv1.Subnet, error) {
1✔
2293
        var v4IP, v6IP, mac string
1✔
2294
        var err error
1✔
2295

1✔
2296
        // Static allocate
1✔
2297
        if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
2✔
2298
                annotationKey := perInterfaceIPAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
1✔
2299
                if ipStr := pod.Annotations[annotationKey]; ipStr != "" {
2✔
2300
                        for _, net := range nsNets {
2✔
2301
                                v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipStr, macPointer, net.Subnet.Name, net.AllowLiveMigration)
1✔
2302
                                if err == nil {
2✔
2303
                                        return v4IP, v6IP, mac, net.Subnet, nil
1✔
2304
                                }
1✔
2305
                        }
2306
                        return v4IP, v6IP, mac, podNet.Subnet, err
×
2307
                }
2308
        }
2309

2310
        if ipStr := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]; ipStr != "" {
2✔
2311
                for _, net := range nsNets {
2✔
2312
                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipStr, macPointer, net.Subnet.Name, net.AllowLiveMigration)
1✔
2313
                        if err == nil {
2✔
2314
                                return v4IP, v6IP, mac, net.Subnet, nil
1✔
2315
                        }
1✔
2316
                }
2317
                return v4IP, v6IP, mac, podNet.Subnet, err
1✔
2318
        }
2319

2320
        // IPPool allocate
2321
        if ippoolStr != "" {
×
2322
                var ipPool []string
×
2323
                if strings.ContainsRune(ippoolStr, ';') {
×
2324
                        ipPool = strings.Split(ippoolStr, ";")
×
2325
                } else {
×
2326
                        ipPool = strings.Split(ippoolStr, ",")
×
2327
                        if len(ipPool) == 2 && util.CheckProtocol(ipPool[0]) != util.CheckProtocol(ipPool[1]) {
×
2328
                                ipPool = []string{ippoolStr}
×
2329
                        }
×
2330
                }
2331
                for i, ip := range ipPool {
×
2332
                        ipPool[i] = strings.TrimSpace(ip)
×
2333
                }
×
2334

2335
                if len(ipPool) == 1 && (!strings.ContainsRune(ipPool[0], ',') && net.ParseIP(ipPool[0]) == nil) {
×
2336
                        var skippedAddrs []string
×
2337
                        pool, err := c.ippoolLister.Get(ipPool[0])
×
2338
                        if err != nil {
×
2339
                                klog.Errorf("failed to get ippool %s: %v", ipPool[0], err)
×
2340
                                return "", "", "", podNet.Subnet, err
×
2341
                        }
×
2342
                        for {
×
2343
                                ipv4, ipv6, mac, err := c.ipam.GetRandomAddress(key, portName, macPointer, pool.Spec.Subnet, ipPool[0], skippedAddrs, !podNet.AllowLiveMigration)
×
2344
                                if err != nil {
×
2345
                                        klog.Error(err)
×
2346
                                        return "", "", "", podNet.Subnet, err
×
2347
                                }
×
2348
                                ipv4OK, ipv6OK, err := c.validatePodIP(pod.Name, podNet.Subnet.Name, ipv4, ipv6)
×
2349
                                if err != nil {
×
2350
                                        klog.Error(err)
×
2351
                                        return "", "", "", podNet.Subnet, err
×
2352
                                }
×
2353
                                if ipv4OK && ipv6OK {
×
2354
                                        return ipv4, ipv6, mac, podNet.Subnet, nil
×
2355
                                }
×
2356

2357
                                if !ipv4OK {
×
2358
                                        skippedAddrs = append(skippedAddrs, ipv4)
×
2359
                                }
×
2360
                                if !ipv6OK {
×
2361
                                        skippedAddrs = append(skippedAddrs, ipv6)
×
2362
                                }
×
2363
                        }
2364
                }
2365

2366
                if !isStsPod {
×
2367
                        for _, net := range nsNets {
×
2368
                                for _, staticIP := range ipPool {
×
2369
                                        var checkIP string
×
2370
                                        ipProtocol := util.CheckProtocol(staticIP)
×
2371
                                        if ipProtocol == kubeovnv1.ProtocolDual {
×
2372
                                                checkIP = strings.Split(staticIP, ",")[0]
×
2373
                                        } else {
×
2374
                                                checkIP = staticIP
×
2375
                                        }
×
2376

2377
                                        if assignedPod, ok := c.ipam.IsIPAssignedToOtherPod(checkIP, net.Subnet.Name, key); ok {
×
2378
                                                klog.Errorf("static address %s for %s has been assigned to %s", staticIP, key, assignedPod)
×
2379
                                                continue
×
2380
                                        }
2381

2382
                                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, staticIP, macPointer, net.Subnet.Name, net.AllowLiveMigration)
×
2383
                                        if err == nil {
×
2384
                                                return v4IP, v6IP, mac, net.Subnet, nil
×
2385
                                        }
×
2386
                                }
2387
                        }
2388
                        klog.Errorf("acquire address from ippool %s for %s failed, %v", ippoolStr, key, err)
×
2389
                } else {
×
2390
                        tempStrs := strings.Split(pod.Name, "-")
×
2391
                        numStr := tempStrs[len(tempStrs)-1]
×
2392
                        index, _ := strconv.Atoi(numStr)
×
2393

×
2394
                        if index < len(ipPool) {
×
2395
                                for _, net := range nsNets {
×
2396
                                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipPool[index], macPointer, net.Subnet.Name, net.AllowLiveMigration)
×
2397
                                        if err == nil {
×
2398
                                                return v4IP, v6IP, mac, net.Subnet, nil
×
2399
                                        }
×
2400
                                }
2401
                                klog.Errorf("acquire address %s for %s failed, %v", ipPool[index], key, err)
×
2402
                        }
2403
                }
2404
        }
2405
        klog.Errorf("allocate address for %s failed, return NoAvailableAddress", key)
×
2406
        return "", "", "", podNet.Subnet, ipam.ErrNoAvailable
×
2407
}
2408

2409
func (c *Controller) acquireStaticAddress(key, nicName, ip string, mac *string, subnet string, liveMigration bool) (string, string, string, error) {
1✔
2410
        var v4IP, v6IP, macStr string
1✔
2411
        var err error
1✔
2412
        for ipStr := range strings.SplitSeq(ip, ",") {
2✔
2413
                if net.ParseIP(ipStr) == nil {
1✔
2414
                        return "", "", "", fmt.Errorf("failed to parse IP %s", ipStr)
×
2415
                }
×
2416
        }
2417

2418
        if v4IP, v6IP, macStr, err = c.ipam.GetStaticAddress(key, nicName, ip, mac, subnet, !liveMigration); err != nil {
2✔
2419
                klog.Errorf("failed to get static ip %v, mac %v, subnet %v, err %v", ip, mac, subnet, err)
1✔
2420
                return "", "", "", err
1✔
2421
        }
1✔
2422
        return v4IP, v6IP, macStr, nil
1✔
2423
}
2424

2425
func appendCheckPodNetToDel(c *Controller, pod *v1.Pod, ownerRefName, ownerRefKind string) (bool, []string, error) {
×
2426
        // subnet for ns has been changed, and statefulset/vm pod's ip is not in the range of subnet's cidr anymore
×
2427
        podNs, err := c.namespacesLister.Get(pod.Namespace)
×
2428
        if err != nil {
×
2429
                klog.Errorf("failed to get namespace %s, %v", pod.Namespace, err)
×
2430
                return false, nil, err
×
2431
        }
×
2432

2433
        var ownerRefAnnotations map[string]string
×
2434
        switch ownerRefKind {
×
2435
        case util.KindStatefulSet:
×
2436
                ss, err := c.config.KubeClient.AppsV1().StatefulSets(pod.Namespace).Get(context.Background(), ownerRefName, metav1.GetOptions{})
×
2437
                if err != nil {
×
2438
                        if k8serrors.IsNotFound(err) {
×
2439
                                klog.Infof("Statefulset %s is not found", ownerRefName)
×
2440
                                return true, nil, nil
×
2441
                        }
×
2442
                        klog.Errorf("failed to get StatefulSet %s, %v", ownerRefName, err)
×
2443
                        return false, nil, err
×
2444
                }
2445
                if ss.Spec.Template.Annotations != nil {
×
2446
                        ownerRefAnnotations = ss.Spec.Template.Annotations
×
2447
                }
×
2448

2449
        case util.KindVirtualMachineInstance:
×
2450
                vm, err := c.config.KubevirtClient.VirtualMachine(pod.Namespace).Get(context.Background(), ownerRefName, metav1.GetOptions{})
×
2451
                if err != nil {
×
2452
                        if k8serrors.IsNotFound(err) {
×
2453
                                klog.Infof("VirtualMachine %s is not found", ownerRefName)
×
2454
                                return true, nil, nil
×
2455
                        }
×
2456
                        klog.Errorf("failed to get VirtualMachine %s, %v", ownerRefName, err)
×
2457
                        return false, nil, err
×
2458
                }
2459
                if vm.Spec.Template != nil &&
×
2460
                        vm.Spec.Template.ObjectMeta.Annotations != nil {
×
2461
                        ownerRefAnnotations = vm.Spec.Template.ObjectMeta.Annotations
×
2462
                }
×
2463
        }
2464

2465
        var ipcrToDelete []string
×
2466
        if defaultIPCRName := appendCheckPodNonMultusNetToDel(c, pod, ownerRefName, ownerRefAnnotations, podNs); defaultIPCRName != "" {
×
2467
                ipcrToDelete = append(ipcrToDelete, defaultIPCRName)
×
2468
        }
×
2469

2470
        if multusIPCRNames := appendCheckPodMultusNetToDel(c, pod, ownerRefName, ownerRefAnnotations); len(multusIPCRNames) != 0 {
×
2471
                ipcrToDelete = append(ipcrToDelete, multusIPCRNames...)
×
2472
        }
×
2473

2474
        return false, ipcrToDelete, nil
×
2475
}
2476

2477
func appendCheckPodNonMultusNetToDel(c *Controller, pod *v1.Pod, ownerRefName string, ownerRefAnnotations map[string]string, podNs *v1.Namespace) string {
×
2478
        podDefaultSwitch := strings.TrimSpace(pod.Annotations[util.LogicalSwitchAnnotation])
×
2479
        if podDefaultSwitch != "" {
×
2480
                ownerRefSubnet := ownerRefAnnotations[util.LogicalSwitchAnnotation]
×
2481
                defaultIPCRName := ovs.PodNameToPortName(ownerRefName, pod.Namespace, util.OvnProvider)
×
2482
                if ownerRefSubnet == "" {
×
2483
                        nsSubnetNames := podNs.Annotations[util.LogicalSwitchAnnotation]
×
2484
                        // check if pod use the subnet of its ns
×
2485
                        if nsSubnetNames != "" && !slices.Contains(strings.Split(nsSubnetNames, ","), podDefaultSwitch) {
×
2486
                                klog.Infof("ns %s annotation subnet is %s, which is inconstant with subnet for pod %s, delete pod", pod.Namespace, nsSubnetNames, pod.Name)
×
2487
                                return defaultIPCRName
×
2488
                        }
×
2489
                } else {
×
2490
                        podIP := pod.Annotations[util.IPAddressAnnotation]
×
2491
                        if shouldCleanPodNet(c, pod, ownerRefName, ownerRefSubnet, podDefaultSwitch, podIP) {
×
2492
                                return defaultIPCRName
×
2493
                        }
×
2494
                }
2495
        }
2496
        return ""
×
2497
}
2498

2499
func appendCheckPodMultusNetToDel(c *Controller, pod *v1.Pod, ownerRefName string, ownerRefAnnotations map[string]string) []string {
×
2500
        var multusIPCRNames []string
×
2501
        attachmentNets, _ := c.getPodAttachmentNet(pod)
×
2502
        for _, attachmentNet := range attachmentNets {
×
2503
                ipCRName := ovs.PodNameToPortName(ownerRefName, pod.Namespace, attachmentNet.ProviderName)
×
2504
                podSwitch := strings.TrimSpace(pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, attachmentNet.ProviderName)])
×
2505
                ownerRefSubnet := ownerRefAnnotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, attachmentNet.ProviderName)]
×
2506
                podIP := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, attachmentNet.ProviderName)]
×
2507
                if shouldCleanPodNet(c, pod, ownerRefName, ownerRefSubnet, podSwitch, podIP) {
×
2508
                        multusIPCRNames = append(multusIPCRNames, ipCRName)
×
2509
                }
×
2510
        }
2511
        return multusIPCRNames
×
2512
}
2513

2514
func shouldCleanPodNet(c *Controller, pod *v1.Pod, ownerRefName, ownerRefSubnet, podSwitch, podIP string) bool {
×
2515
        // subnet cidr has been changed, and statefulset pod's ip is not in the range of subnet's cidr anymore
×
2516
        podSubnet, err := c.subnetsLister.Get(podSwitch)
×
2517
        if err != nil {
×
2518
                if k8serrors.IsNotFound(err) {
×
2519
                        klog.Infof("subnet %s not found for pod %s/%s, not auto clean ip", podSwitch, pod.Namespace, pod.Name)
×
2520
                        return false
×
2521
                }
×
2522
                klog.Errorf("failed to get subnet %s, %v, not auto clean ip", podSwitch, err)
×
2523
                return false
×
2524
        }
2525
        if podSubnet == nil {
×
2526
                // TODO: remove: CRD get interface will retrun a nil subnet ?
×
2527
                klog.Errorf("pod %s/%s subnet %s is nil, not auto clean ip", pod.Namespace, pod.Name, podSwitch)
×
2528
                return false
×
2529
        }
×
2530
        if podIP == "" {
×
2531
                // delete pod just after it created < 1ms
×
2532
                klog.Infof("pod %s/%s annotaions has no ip address, not auto clean ip", pod.Namespace, pod.Name)
×
2533
                return false
×
2534
        }
×
2535
        podSubnetCidr := podSubnet.Spec.CIDRBlock
×
2536
        if podSubnetCidr == "" {
×
2537
                // subnet spec cidr changed by user
×
2538
                klog.Errorf("invalid pod subnet %s empty cidr %s, not auto clean ip", podSwitch, podSubnetCidr)
×
2539
                return false
×
2540
        }
×
2541
        if !util.CIDRContainIP(podSubnetCidr, podIP) {
×
2542
                klog.Infof("pod's ip %s is not in the range of subnet %s, delete pod", podIP, podSubnet.Name)
×
2543
                return true
×
2544
        }
×
2545
        // subnet of ownerReference(sts/vm) has been changed, it needs to handle delete pod and create port on the new logical switch
2546
        if ownerRefSubnet != "" && podSubnet.Name != ownerRefSubnet {
×
2547
                klog.Infof("Subnet of owner %s has been changed from %s to %s, delete pod %s/%s", ownerRefName, podSubnet.Name, ownerRefSubnet, pod.Namespace, pod.Name)
×
2548
                return true
×
2549
        }
×
2550

2551
        return false
×
2552
}
2553

2554
// getVMOrphanedAttachmentPorts finds OVN ports that belong to the VM but are
2555
// no longer desired by the VM's current spec (e.g., KubeVirt NAD hotplug).
2556
// It compares OVN's actual ports (existingPorts) against the VM spec's desired
2557
// ports, rather than relying on pod annotations which may be stale or incomplete.
2558
func (c *Controller) getVMOrphanedAttachmentPorts(namespace, vmName string, existingPorts []ovnnb.LogicalSwitchPort) map[string]bool {
×
2559
        vm, err := c.config.KubevirtClient.VirtualMachine(namespace).Get(context.Background(), vmName, metav1.GetOptions{})
×
2560
        if err != nil {
×
2561
                klog.Errorf("failed to get vm %s/%s for orphaned port detection: %v", namespace, vmName, err)
×
2562
                return nil
×
2563
        }
×
2564

2565
        if vm.Spec.Template == nil {
×
2566
                return nil
×
2567
        }
×
2568

2569
        // Build expected port names from VM spec in a single pass (pattern from gc.go:1108-1145)
2570
        expectedPorts := make(map[string]bool)
×
2571
        defaultMultus := false
×
2572
        hasMultusNetwork := false
×
2573
        for _, network := range vm.Spec.Template.Spec.Networks {
×
2574
                if network.Multus == nil {
×
2575
                        continue
×
2576
                }
2577
                if network.Multus.Default {
×
2578
                        defaultMultus = true
×
2579
                }
×
2580
                if network.Multus.NetworkName != "" {
×
2581
                        hasMultusNetwork = true
×
2582
                        items := strings.Split(network.Multus.NetworkName, "/")
×
2583
                        if len(items) != 2 {
×
2584
                                items = []string{namespace, items[0]}
×
2585
                        }
×
2586
                        provider := fmt.Sprintf("%s.%s.%s", items[1], items[0], util.OvnProvider)
×
2587
                        expectedPorts[ovs.PodNameToPortName(vmName, namespace, provider)] = true
×
2588
                }
2589
        }
2590
        if !defaultMultus {
×
2591
                expectedPorts[ovs.PodNameToPortName(vmName, namespace, util.OvnProvider)] = true
×
2592
        }
×
2593

2594
        // If VM spec has no multus networks, skip detection to avoid
2595
        // false positives for VMs that only use template annotations.
2596
        if !hasMultusNetwork {
×
2597
                return nil
×
2598
        }
×
2599

2600
        // Find orphaned: ports in OVN but not in expected
2601
        orphanedPorts := make(map[string]bool)
×
2602
        for _, port := range existingPorts {
×
2603
                if !expectedPorts[port.Name] {
×
2604
                        klog.Infof("OVN port %s for vm %s/%s is not in VM spec, marking as orphaned",
×
2605
                                port.Name, namespace, vmName)
×
2606
                        orphanedPorts[port.Name] = true
×
2607
                }
×
2608
        }
2609

2610
        if len(orphanedPorts) == 0 {
×
2611
                return nil
×
2612
        }
×
2613
        return orphanedPorts
×
2614
}
2615

2616
// cleanStaleVMAttachmentIPs removes IP CRs and OVN LSPs that belong to
2617
// this VM but are not part of the current pod's networks. This handles
2618
// the stop→patch NAD→start workflow where the old pod deletion was
2619
// processed before the NAD change, leaving stale attachment resources.
2620
func (c *Controller) cleanStaleVMAttachmentIPs(pod *v1.Pod, podName string) {
×
2621
        podKey := fmt.Sprintf("%s/%s", pod.Namespace, podName)
×
2622

×
2623
        // List existing LSPs first (cheap OVN query) to bail out early if none exist
×
2624
        ports, err := c.OVNNbClient.ListNormalLogicalSwitchPorts(true, map[string]string{"pod": podKey})
×
2625
        if err != nil {
×
2626
                klog.Errorf("failed to list lsps of vm %s for stale cleanup: %v", podKey, err)
×
2627
                return
×
2628
        }
×
2629
        if len(ports) == 0 {
×
2630
                return
×
2631
        }
×
2632

2633
        // Build current port names from the pod's full network list
2634
        podNets, err := c.getPodKubeovnNets(pod)
×
2635
        if err != nil {
×
2636
                klog.Errorf("failed to get kube-ovn nets of pod %s for stale cleanup: %v", podKey, err)
×
2637
                return
×
2638
        }
×
2639
        currentPorts := make(map[string]bool, len(podNets)+1)
×
2640
        for _, podNet := range podNets {
×
2641
                currentPorts[ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)] = true
×
2642
        }
×
2643
        currentPorts[ovs.PodNameToPortName(podName, pod.Namespace, util.OvnProvider)] = true
×
2644

×
2645
        for _, port := range ports {
×
2646
                if currentPorts[port.Name] {
×
2647
                        continue
×
2648
                }
2649
                klog.Infof("cleaning stale vm attachment lsp %s (not in current pod networks)", port.Name)
×
2650
                if err := c.OVNNbClient.DeleteLogicalSwitchPort(port.Name); err != nil {
×
2651
                        klog.Errorf("failed to delete stale lsp %s, skipping IP cleanup to avoid inconsistency: %v", port.Name, err)
×
2652
                        continue
×
2653
                }
2654

2655
                ipCR, err := c.ipsLister.Get(port.Name)
×
2656
                if err != nil {
×
2657
                        if !k8serrors.IsNotFound(err) {
×
2658
                                klog.Errorf("failed to get ip %s: %v", port.Name, err)
×
2659
                        }
×
2660
                        continue
×
2661
                }
2662
                if ipCR.Labels[util.IPReservedLabel] != "true" {
×
2663
                        klog.Infof("deleting stale vm attachment ip CR %s", ipCR.Name)
×
2664
                        if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), ipCR.Name, metav1.DeleteOptions{}); err != nil {
×
2665
                                if !k8serrors.IsNotFound(err) {
×
2666
                                        klog.Errorf("failed to delete ip %s: %v", ipCR.Name, err)
×
2667
                                }
×
2668
                        }
2669
                        if subnetName := ipCR.Spec.Subnet; subnetName != "" {
×
2670
                                c.ipam.ReleaseAddressByNic(podKey, port.Name, subnetName)
×
2671
                                c.updateSubnetStatusQueue.Add(subnetName)
×
2672
                        }
×
2673
                }
2674
        }
2675
}
2676

2677
func isVMPod(pod *v1.Pod) (bool, string) {
1✔
2678
        for _, owner := range pod.OwnerReferences {
2✔
2679
                // The name of vmi is consistent with vm's name.
1✔
2680
                if owner.Kind == util.KindVirtualMachineInstance &&
1✔
2681
                        strings.HasPrefix(owner.APIVersion, kubevirtv1.SchemeGroupVersion.Group+"/") {
2✔
2682
                        return true, owner.Name
1✔
2683
                }
1✔
2684
        }
2685
        return false, ""
1✔
2686
}
2687

2688
// hasAliveSiblingVMPod reports whether pods contains any alive virt-launcher
2689
// pod owned by the VMI vmName, excluding the pod with name excludePodName.
2690
// It is used to decide whether a VM LSP's port-group memberships are still
2691
// owned by another running sibling (e.g. a live-migration destination) when a
2692
// completed/GC'd source pod is being processed.
2693
func hasAliveSiblingVMPod(pods []*v1.Pod, vmName, excludePodName string) bool {
1✔
2694
        for _, p := range pods {
2✔
2695
                if p == nil || p.Name == excludePodName {
2✔
2696
                        continue
1✔
2697
                }
2698
                isVM, name := isVMPod(p)
1✔
2699
                if !isVM || name != vmName {
2✔
2700
                        continue
1✔
2701
                }
2702
                if isPodAlive(p) {
2✔
2703
                        return true
1✔
2704
                }
1✔
2705
        }
2706
        return false
1✔
2707
}
2708

2709
func isOwnsByTheVM(vmi metav1.Object) (bool, string) {
×
2710
        for _, owner := range vmi.GetOwnerReferences() {
×
2711
                if owner.Kind == util.KindVirtualMachine &&
×
2712
                        strings.HasPrefix(owner.APIVersion, kubevirtv1.SchemeGroupVersion.Group+"/") {
×
2713
                        return true, owner.Name
×
2714
                }
×
2715
        }
2716
        return false, ""
×
2717
}
2718

2719
func (c *Controller) isVMToDel(pod *v1.Pod, vmiName string) bool {
×
2720
        var (
×
2721
                vmiAlive bool
×
2722
                vmName   string
×
2723
        )
×
2724
        // The vmi is also deleted when pod is deleted, only left vm exists.
×
2725
        vmi, err := c.config.KubevirtClient.VirtualMachineInstance(pod.Namespace).Get(context.Background(), vmiName, metav1.GetOptions{})
×
2726
        if err != nil {
×
2727
                if k8serrors.IsNotFound(err) {
×
2728
                        vmiAlive = false
×
2729
                        // The name of vmi is consistent with vm's name.
×
2730
                        vmName = vmiName
×
2731
                        klog.ErrorS(err, "failed to get vmi, will try to get the vm directly", "name", vmiName)
×
2732
                } else {
×
2733
                        klog.ErrorS(err, "failed to get vmi", "name", vmiName)
×
2734
                        return false
×
2735
                }
×
2736
        } else {
×
2737
                var ownsByVM bool
×
2738
                ownsByVM, vmName = isOwnsByTheVM(vmi)
×
2739
                if !ownsByVM && !vmi.DeletionTimestamp.IsZero() {
×
2740
                        klog.Infof("ephemeral vmi %s is deleting", vmiName)
×
2741
                        return true
×
2742
                }
×
2743
                vmiAlive = vmi.DeletionTimestamp.IsZero()
×
2744
        }
2745

2746
        if vmiAlive {
×
2747
                return false
×
2748
        }
×
2749

2750
        vm, err := c.config.KubevirtClient.VirtualMachine(pod.Namespace).Get(context.Background(), vmName, metav1.GetOptions{})
×
2751
        if err != nil {
×
2752
                // the vm has gone
×
2753
                if k8serrors.IsNotFound(err) {
×
2754
                        klog.ErrorS(err, "failed to get vm", "name", vmName)
×
2755
                        return true
×
2756
                }
×
2757
                klog.ErrorS(err, "failed to get vm", "name", vmName)
×
2758
                return false
×
2759
        }
2760

2761
        if !vm.DeletionTimestamp.IsZero() {
×
2762
                klog.Infof("vm %s is deleting", vmName)
×
2763
                return true
×
2764
        }
×
2765
        return false
×
2766
}
2767

2768
func (c *Controller) getNameByPod(pod *v1.Pod) string {
1✔
2769
        if c.config.EnableKeepVMIP {
1✔
2770
                if isVMPod, vmName := isVMPod(pod); isVMPod {
×
2771
                        return vmName
×
2772
                }
×
2773
        }
2774
        return pod.Name
1✔
2775
}
2776

2777
// When subnet's v4availableIPs is 0 but still there's available ip in exclude-ips, the static ip in exclude-ips can be allocated normal.
2778
func (c *Controller) getNsAvailableSubnets(pod *v1.Pod, podNet *kubeovnNet) ([]*kubeovnNet, error) {
1✔
2779
        // keep the annotation subnet of the pod in first position
1✔
2780
        result := []*kubeovnNet{podNet}
1✔
2781

1✔
2782
        ns, err := c.namespacesLister.Get(pod.Namespace)
1✔
2783
        if err != nil {
1✔
2784
                klog.Errorf("failed to get namespace %s, %v", pod.Namespace, err)
×
2785
                return nil, err
×
2786
        }
×
2787
        if ns.Annotations == nil {
1✔
2788
                return []*kubeovnNet{}, nil
×
2789
        }
×
2790

2791
        subnetNames := ns.Annotations[util.LogicalSwitchAnnotation]
1✔
2792
        for subnetName := range strings.SplitSeq(subnetNames, ",") {
2✔
2793
                if subnetName == "" || subnetName == podNet.Subnet.Name {
2✔
2794
                        continue
1✔
2795
                }
2796
                subnet, err := c.subnetsLister.Get(subnetName)
1✔
2797
                if err != nil {
1✔
2798
                        klog.Errorf("failed to get subnet %v", err)
×
2799
                        return nil, err
×
2800
                }
×
2801

2802
                result = append(result, &kubeovnNet{
1✔
2803
                        Type:         providerTypeOriginal,
1✔
2804
                        ProviderName: subnet.Spec.Provider,
1✔
2805
                        Subnet:       subnet,
1✔
2806
                })
1✔
2807
        }
2808

2809
        return result, nil
1✔
2810
}
2811

2812
func getPodType(pod *v1.Pod) string {
×
2813
        if ok, _, _ := isStatefulSetPod(pod); ok {
×
2814
                return util.KindStatefulSet
×
2815
        }
×
2816

2817
        if isVMPod, _ := isVMPod(pod); isVMPod {
×
2818
                return util.KindVirtualMachine
×
2819
        }
×
2820
        return ""
×
2821
}
2822

2823
func (c *Controller) getVirtualIPs(pod *v1.Pod, podNets []*kubeovnNet) map[string]string {
×
2824
        vipsListMap := make(map[string][]string)
×
2825
        var vipNamesList []string
×
2826
        for vipName := range strings.SplitSeq(strings.TrimSpace(pod.Annotations[util.AAPsAnnotation]), ",") {
×
2827
                if vipName = strings.TrimSpace(vipName); vipName == "" {
×
2828
                        continue
×
2829
                }
2830
                if !slices.Contains(vipNamesList, vipName) {
×
2831
                        vipNamesList = append(vipNamesList, vipName)
×
2832
                } else {
×
2833
                        continue
×
2834
                }
2835
                vip, err := c.virtualIpsLister.Get(vipName)
×
2836
                if err != nil {
×
2837
                        klog.Errorf("failed to get vip %s, %v", vipName, err)
×
2838
                        continue
×
2839
                }
2840
                if vip.Spec.Namespace != pod.Namespace || (vip.Status.V4ip == "" && vip.Status.V6ip == "") {
×
2841
                        continue
×
2842
                }
2843
                for _, podNet := range podNets {
×
2844
                        if podNet.Subnet.Name == vip.Spec.Subnet {
×
2845
                                key := fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)
×
2846
                                vipsList := vipsListMap[key]
×
2847
                                if vipsList == nil {
×
2848
                                        vipsList = []string{}
×
2849
                                }
×
2850
                                // ipam will ensure the uniqueness of VIP
2851
                                if util.IsValidIP(vip.Status.V4ip) {
×
2852
                                        vipsList = append(vipsList, vip.Status.V4ip)
×
2853
                                }
×
2854
                                if util.IsValidIP(vip.Status.V6ip) {
×
2855
                                        vipsList = append(vipsList, vip.Status.V6ip)
×
2856
                                }
×
2857

2858
                                vipsListMap[key] = vipsList
×
2859
                        }
2860
                }
2861
        }
2862

2863
        for _, podNet := range podNets {
×
2864
                vipStr := pod.Annotations[fmt.Sprintf(util.PortVipAnnotationTemplate, podNet.ProviderName)]
×
2865
                if vipStr == "" {
×
2866
                        continue
×
2867
                }
2868
                key := fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)
×
2869
                vipsList := vipsListMap[key]
×
2870
                if vipsList == nil {
×
2871
                        vipsList = []string{}
×
2872
                }
×
2873

2874
                for vip := range strings.SplitSeq(vipStr, ",") {
×
2875
                        if util.IsValidIP(vip) && !slices.Contains(vipsList, vip) {
×
2876
                                vipsList = append(vipsList, vip)
×
2877
                        }
×
2878
                }
2879

2880
                vipsListMap[key] = vipsList
×
2881
        }
2882

2883
        vipsMap := make(map[string]string)
×
2884
        for key, vipsList := range vipsListMap {
×
2885
                vipsMap[key] = strings.Join(vipsList, ",")
×
2886
        }
×
2887
        return vipsMap
×
2888
}
2889

2890
func setPodRoutesAnnotation(annotations map[string]string, provider string, routes []request.Route) error {
×
2891
        key := fmt.Sprintf(util.RoutesAnnotationTemplate, provider)
×
2892
        if len(routes) == 0 {
×
2893
                delete(annotations, key)
×
2894
                return nil
×
2895
        }
×
2896

2897
        buf, err := json.Marshal(routes)
×
2898
        if err != nil {
×
2899
                err = fmt.Errorf("failed to marshal routes %+v: %w", routes, err)
×
2900
                klog.Error(err)
×
2901
                return err
×
2902
        }
×
2903
        annotations[key] = string(buf)
×
2904

×
2905
        return nil
×
2906
}
2907

2908
// Check if pod is a VPC NAT gateway using pod annotations
2909
func (c *Controller) checkIsPodVpcNatGw(pod *v1.Pod) (bool, string) {
1✔
2910
        if pod == nil {
2✔
2911
                return false, ""
1✔
2912
        }
1✔
2913
        if pod.Annotations == nil {
2✔
2914
                return false, ""
1✔
2915
        }
1✔
2916
        vpcGwName, isVpcNatGw := pod.Annotations[util.VpcNatGatewayAnnotation]
1✔
2917
        if isVpcNatGw {
2✔
2918
                if vpcGwName == "" {
2✔
2919
                        klog.Errorf("pod %s is vpc nat gateway but name is empty", pod.Name)
1✔
2920
                        return false, ""
1✔
2921
                }
1✔
2922
                klog.Infof("pod %s is vpc nat gateway %s", pod.Name, vpcGwName)
1✔
2923
        }
2924
        return isVpcNatGw, vpcGwName
1✔
2925
}
2926

2927
func natGwNameFromStatefulSetOwner(pod *v1.Pod) string {
1✔
2928
        isStsPod, stsName, _ := isStatefulSetPod(pod)
1✔
2929
        if !isStsPod {
1✔
2930
                return ""
×
2931
        }
×
2932

2933
        prefix := util.VpcNatGwNamePrefix + "-"
1✔
2934
        if !strings.HasPrefix(stsName, prefix) {
1✔
2935
                return ""
×
2936
        }
×
2937
        return strings.TrimPrefix(stsName, prefix)
1✔
2938
}
2939

2940
func (c *Controller) backfillVpcNatGwLanIPFromPod(pod *v1.Pod, gwName string) error {
1✔
2941
        if pod == nil {
1✔
2942
                return nil
×
2943
        }
×
2944

2945
        ownerGwName := natGwNameFromStatefulSetOwner(pod)
1✔
2946
        if ownerGwName == "" {
1✔
2947
                return nil
×
2948
        }
×
2949
        if gwName == "" {
2✔
2950
                gwName = ownerGwName
1✔
2951
        }
1✔
2952
        // Use owner reference as a guard to avoid patching unrelated pods carrying a stale annotation.
2953
        if ownerGwName != gwName {
1✔
2954
                klog.Warningf("skip backfill for pod %s/%s: gw annotation %q does not match owner statefulset %q",
×
2955
                        pod.Namespace, pod.Name, gwName, ownerGwName)
×
2956
                return nil
×
2957
        }
×
2958

2959
        var (
1✔
2960
                gw  *kubeovnv1.VpcNatGateway
1✔
2961
                err error
1✔
2962
        )
1✔
2963
        if c.vpcNatGatewayLister != nil {
1✔
2964
                gw, err = c.vpcNatGatewayLister.Get(gwName)
×
2965
        } else {
1✔
2966
                gw, err = c.config.KubeOvnClient.KubeovnV1().VpcNatGateways().Get(context.Background(), gwName, metav1.GetOptions{})
1✔
2967
        }
1✔
2968
        if err != nil {
1✔
2969
                if k8serrors.IsNotFound(err) {
×
2970
                        return nil
×
2971
                }
×
2972
                return err
×
2973
        }
2974
        if gw.Spec.LanIP != "" {
2✔
2975
                return nil
1✔
2976
        }
1✔
2977

2978
        subnet, err := c.subnetsLister.Get(gw.Spec.Subnet)
1✔
2979
        if err != nil {
1✔
2980
                return fmt.Errorf("failed to get subnet %s: %w", gw.Spec.Subnet, err)
×
2981
        }
×
2982
        if !isOvnSubnet(subnet) {
1✔
2983
                return fmt.Errorf("subnet %s is not an OVN subnet", gw.Spec.Subnet)
×
2984
        }
×
2985
        provider := subnet.Spec.Provider
1✔
2986

1✔
2987
        lanIP := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, provider)]
1✔
2988
        v4IP, v6IP := util.SplitStringIP(lanIP)
1✔
2989
        switch subnet.Spec.Protocol {
1✔
2990
        case kubeovnv1.ProtocolIPv6:
1✔
2991
                lanIP = v6IP
1✔
2992
        case kubeovnv1.ProtocolIPv4:
1✔
2993
                lanIP = v4IP
1✔
2994
        case kubeovnv1.ProtocolDual:
×
2995
                if v4IP != "" {
×
2996
                        lanIP = v4IP
×
2997
                } else {
×
2998
                        lanIP = v6IP
×
2999
                }
×
3000
        default:
×
3001
                lanIP = v4IP
×
3002
        }
3003
        if lanIP == "" || net.ParseIP(lanIP) == nil {
2✔
3004
                return nil
1✔
3005
        }
1✔
3006

3007
        patchPayload := map[string]any{
1✔
3008
                "spec": map[string]string{
1✔
3009
                        "lanIp": lanIP,
1✔
3010
                },
1✔
3011
        }
1✔
3012
        raw, err := json.Marshal(patchPayload)
1✔
3013
        if err != nil {
1✔
3014
                return err
×
3015
        }
×
3016

3017
        _, err = c.config.KubeOvnClient.KubeovnV1().VpcNatGateways().Patch(context.Background(),
1✔
3018
                gw.Name, types.MergePatchType, raw, metav1.PatchOptions{})
1✔
3019
        if err != nil {
1✔
3020
                return err
×
3021
        }
×
3022
        klog.Infof("backfilled vpc nat gateway %s spec.lanIP with pod %s/%s ip %s", gw.Name, pod.Namespace, pod.Name, lanIP)
1✔
3023
        return nil
1✔
3024
}
3025

3026
func perInterfaceIPAnnotationKey(nadName, nadNamespace, ifaceName string) string {
1✔
3027
        return fmt.Sprintf("%s.%s.kubernetes.io/ip_address.%s", nadName, nadNamespace, ifaceName)
1✔
3028
}
1✔
3029

3030
func perInterfaceMACAnnotationKey(nadName, nadNamespace, ifaceName string) string {
×
3031
        return fmt.Sprintf("%s.%s.kubernetes.io/mac_address.%s", nadName, nadNamespace, ifaceName)
×
3032
}
×
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