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

kubeovn / kube-ovn / 30253489111

27 Jul 2026 09:19AM UTC coverage: 30.144% (+0.006%) from 30.138%
30253489111

push

github

web-flow
feat: infer pod subnet from named IPPool (#7059)

* feat: infer pod subnet from named IPPool

Signed-off-by: clyi <clyi@alauda.io>

* test: lower named IPPool e2e version gate

---------

Signed-off-by: clyi <clyi@alauda.io>

7 of 11 new or added lines in 1 file covered. (63.64%)

18480 of 61305 relevant lines covered (30.14%)

0.35 hits per line

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

50.06
/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
        multustypes "gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/types"
20
        appsv1 "k8s.io/api/apps/v1"
21
        v1 "k8s.io/api/core/v1"
22
        k8serrors "k8s.io/apimachinery/pkg/api/errors"
23
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24
        "k8s.io/apimachinery/pkg/labels"
25
        "k8s.io/apimachinery/pkg/types"
26
        utilruntime "k8s.io/apimachinery/pkg/util/runtime"
27
        "k8s.io/client-go/kubernetes"
28
        "k8s.io/client-go/tools/cache"
29
        "k8s.io/klog/v2"
30
        kubevirtv1 "kubevirt.io/api/core/v1"
31

32
        kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1"
33
        "github.com/kubeovn/kube-ovn/pkg/ipam"
34
        "github.com/kubeovn/kube-ovn/pkg/ovs"
35
        "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb"
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) {
1✔
308
        oldPod := oldObj.(*v1.Pod)
1✔
309
        newPod := newObj.(*v1.Pod)
1✔
310

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

1✔
314
        if oldPod.Annotations[util.AAPsAnnotation] != "" || newPod.Annotations[util.AAPsAnnotation] != "" {
1✔
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 {
1✔
339
                return
×
340
        }
×
341

342
        podNets, err := c.getPodKubeovnNets(newPod)
1✔
343
        if err != nil {
2✔
344
                klog.Errorf("failed to get newPod nets %v", err)
1✔
345
                c.recorder.Eventf(newPod, v1.EventTypeWarning, "PodNetworkUpdateFailed", "stage=getPodKubeovnNets error=%v", err)
1✔
346
                return
1✔
347
        }
1✔
348

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

491
        return podNets, nil
1✔
492
}
493

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

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

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

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

525
        podNets, err := c.getPodKubeovnNets(pod)
1✔
526
        if err != nil {
2✔
527
                klog.Errorf("failed to get pod nets %v", err)
1✔
528
                c.recorder.Eventf(pod, v1.EventTypeWarning, "PodNetworkUpdateFailed", "stage=getPodKubeovnNets error=%v", err)
1✔
529
                return err
1✔
530
        }
1✔
531

532
        // check and do hotnoplug nic
533
        var hotplugDetails string
1✔
534
        if pod, hotplugDetails, err = c.syncKubeOvnNet(pod, podNets); err != nil {
1✔
535
                klog.Errorf("failed to sync pod nets %v", err)
×
536
                c.recorder.Eventf(pod, v1.EventTypeWarning, "PodNetworkUpdateFailed", "stage=syncKubeOvnNet error=%v", err)
×
537
                return err
×
538
        }
×
539
        if pod == nil {
1✔
540
                // pod has been deleted
×
541
                return nil
×
542
        }
×
543
        needAllocatePodNets := needAllocateSubnets(pod, podNets)
1✔
544
        if len(needAllocatePodNets) != 0 {
2✔
545
                if pod, err = c.reconcileAllocateSubnets(pod, needAllocatePodNets); err != nil {
2✔
546
                        klog.Error(err)
1✔
547
                        return err
1✔
548
                }
1✔
549
                if pod == nil {
1✔
550
                        // pod has been deleted
×
551
                        return nil
×
552
                }
×
553
        }
554

555
        isVpcNatGw, vpcGwName := c.checkIsPodVpcNatGw(pod)
1✔
556
        if isVpcNatGw {
1✔
557
                c.enqueueAddOrUpdateVpcNatGwByName(vpcGwName, "natgw-pod-update")
×
558
                if needRestartNatGatewayPod(pod) {
×
559
                        klog.Infof("restarting vpc nat gateway %s", vpcGwName)
×
560
                        c.addOrUpdateVpcNatGatewayQueue.Add(vpcGwName)
×
561
                }
×
562
        }
563

564
        // Reconcile per-port DHCP options for pods that carry DHCP annotations.
565
        // This handles annotation add/change on already-running pods without requiring a pod restart.
566
        if err = c.reconcilePodDHCPOptions(pod, podNets); err != nil {
2✔
567
                c.recorder.Eventf(pod, v1.EventTypeWarning, "PodNetworkUpdateFailed", "stage=reconcilePodDHCPOptions error=%v", err)
1✔
568
                return err
1✔
569
        }
1✔
570

571
        // check if route subnet is need.
572
        needRoutePodNets := needRouteSubnets(pod, podNets)
1✔
573
        if err = c.reconcileRouteSubnets(pod, needRoutePodNets); err != nil {
2✔
574
                c.recorder.Eventf(pod, v1.EventTypeWarning, "PodNetworkUpdateFailed", "stage=reconcileRouteSubnets error=%v", err)
1✔
575
                return err
1✔
576
        }
1✔
577

578
        if len(needAllocatePodNets) != 0 {
2✔
579
                details := c.podNetworkEventDetails(pod, needAllocatePodNets)
1✔
580
                if hotplugDetails != "" {
2✔
581
                        details += "; " + hotplugDetails
1✔
582
                }
1✔
583
                c.recorder.Eventf(pod, v1.EventTypeNormal, "PodNetworkAllocated", "%s", details)
1✔
584
        } else if hotplugDetails != "" || len(needRoutePodNets) != 0 {
2✔
585
                details := []string{hotplugDetails}
1✔
586
                if routeDetails := c.podNetworkEventDetails(pod, needRoutePodNets); routeDetails != "" {
2✔
587
                        details = append(details, routeDetails)
1✔
588
                }
1✔
589
                c.recorder.Eventf(pod, v1.EventTypeNormal, "PodNetworkUpdated", "%s", strings.TrimPrefix(strings.Join(details, "; "), "; "))
1✔
590
        }
591
        return nil
1✔
592
}
593

594
// subnetDHCPOptionsUUIDs returns the subnet-level DHCP option UUIDs from the subnet status.
595
func subnetDHCPOptionsUUIDs(subnet *kubeovnv1.Subnet) *ovs.DHCPOptionsUUIDs {
1✔
596
        return &ovs.DHCPOptionsUUIDs{
1✔
597
                DHCPv4OptionsUUID: subnet.Status.DHCPv4OptionsUUID,
1✔
598
                DHCPv6OptionsUUID: subnet.Status.DHCPv6OptionsUUID,
1✔
599
        }
1✔
600
}
1✔
601

602
// dhcpOptionsForPodIPFamily returns DHCP options that match the IP families
603
// actually allocated to the pod. A pod can request one family from a dual-stack
604
// subnet, so using the subnet's full DHCP option set would attach DHCP for an
605
// address family that is not configured on the port.
606
func dhcpOptionsForPodIPFamily(subnetDHCP *ovs.DHCPOptionsUUIDs, podIP, dhcpV4, dhcpV6 string) (*ovs.DHCPOptionsUUIDs, string, string) {
1✔
607
        filtered := &ovs.DHCPOptionsUUIDs{}
1✔
608
        if subnetDHCP != nil {
2✔
609
                *filtered = *subnetDHCP
1✔
610
        }
1✔
611
        switch util.CheckProtocol(podIP) {
1✔
612
        case kubeovnv1.ProtocolIPv4:
1✔
613
                filtered.DHCPv6OptionsUUID = ""
1✔
614
                return filtered, dhcpV4, ""
1✔
615
        case kubeovnv1.ProtocolIPv6:
1✔
616
                filtered.DHCPv4OptionsUUID = ""
1✔
617
                return filtered, "", dhcpV6
1✔
618
        }
619
        return filtered, dhcpV4, dhcpV6
1✔
620
}
621

622
// reconcilePodDHCPOptions reconciles per-port DHCP_Options for already-allocated pods.
623
// It delegates all DHCP logic (stale detection, create/update/cleanup, LSP pointer update)
624
// to ReconcilePortDHCPOptions in the OVS layer.
625
func (c *Controller) reconcilePodDHCPOptions(pod *v1.Pod, podNets []*kubeovnNet) error {
1✔
626
        podName := c.getNameByPod(pod)
1✔
627
        for _, podNet := range podNets {
2✔
628
                if podNet.Type == providerTypeIPAM {
1✔
629
                        continue
×
630
                }
631
                // Skip nets not yet allocated; they are handled by reconcileAllocateSubnets.
632
                if pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)] != "true" {
1✔
633
                        continue
×
634
                }
635

636
                subnet := podNet.Subnet
1✔
637
                portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
1✔
638
                podIP := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]
1✔
639
                dhcpV4 := pod.Annotations[fmt.Sprintf(util.DHCPv4OptionsAnnotationTemplate, podNet.ProviderName)]
1✔
640
                dhcpV6 := pod.Annotations[fmt.Sprintf(util.DHCPv6OptionsAnnotationTemplate, podNet.ProviderName)]
1✔
641
                dhcpOptions, dhcpV4, dhcpV6 := dhcpOptionsForPodIPFamily(subnetDHCPOptionsUUIDs(subnet), podIP, dhcpV4, dhcpV6)
1✔
642

1✔
643
                var mtu int
1✔
644
                var gateway string
1✔
645
                if dhcpV4 != "" || dhcpV6 != "" {
1✔
646
                        var err error
×
647
                        if mtu, err = c.getSubnetMTU(subnet); err != nil {
×
648
                                return err
×
649
                        }
×
650
                        gateway = subnet.Spec.Gateway
×
651
                        if subnet.Status.U2OInterconnectionIP != "" && subnet.Spec.U2OInterconnection {
×
652
                                gateway = subnet.Status.U2OInterconnectionIP
×
653
                        }
×
654
                }
655

656
                if _, _, err := c.OVNNbClient.ReconcilePortDHCPOptions(
1✔
657
                        subnet.Name, portName, dhcpOptions,
1✔
658
                        subnet.Spec.CIDRBlock, gateway, dhcpV4, dhcpV6, mtu,
1✔
659
                ); err != nil {
2✔
660
                        klog.Errorf("failed to reconcile DHCP options for port %s: %v", portName, err)
1✔
661
                        return err
1✔
662
                }
1✔
663
        }
664
        return nil
1✔
665
}
666

667
// do the same thing as add pod
668
func (c *Controller) reconcileAllocateSubnets(pod *v1.Pod, needAllocatePodNets []*kubeovnNet) (*v1.Pod, error) {
1✔
669
        namespace := pod.Namespace
1✔
670
        name := pod.Name
1✔
671
        klog.Infof("sync pod %s/%s allocated", namespace, name)
1✔
672

1✔
673
        isVMPod, vmName := isVMPod(pod)
1✔
674
        podType := getPodType(pod)
1✔
675
        podName := c.getNameByPod(pod)
1✔
676
        // todo: isVmPod, getPodType, getNameByPod has duplicated logic
1✔
677

1✔
678
        var err error
1✔
679
        recordFailure := func(stage string, err error) {
2✔
680
                c.recorder.Eventf(pod, v1.EventTypeWarning, "PodNetworkAllocationFailed", "stage=%s error=%v", stage, err)
1✔
681
        }
1✔
682
        var vmKey string
1✔
683
        if isVMPod && c.config.EnableKeepVMIP {
1✔
684
                vmKey = fmt.Sprintf("%s/%s", namespace, vmName)
×
685
        }
×
686
        // Avoid create lsp for already running pod in ovn-nb when controller restart
687
        patch := util.KVPatch{}
1✔
688
        for _, podNet := range needAllocatePodNets {
2✔
689
                // the subnet may changed when alloc static ip from the latter subnet after ns supports multi subnets
1✔
690
                v4IP, v6IP, mac, subnet, err := c.acquireAddress(pod, podNet)
1✔
691
                if err != nil {
2✔
692
                        c.recorder.Eventf(pod, v1.EventTypeWarning, "AcquireAddressFailed", "stage=acquireAddress error=%v", err)
1✔
693
                        klog.Error(err)
1✔
694
                        return nil, err
1✔
695
                }
1✔
696
                podNet.Subnet = subnet
1✔
697
                ipStr := util.GetStringIP(v4IP, v6IP)
1✔
698
                patch[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] = ipStr
1✔
699
                if mac == "" {
1✔
700
                        patch[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] = nil
×
701
                } else {
1✔
702
                        patch[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] = mac
1✔
703
                }
1✔
704
                patch[fmt.Sprintf(util.CidrAnnotationTemplate, podNet.ProviderName)] = subnet.Spec.CIDRBlock
1✔
705
                patch[fmt.Sprintf(util.GatewayAnnotationTemplate, podNet.ProviderName)] = subnet.Spec.Gateway
1✔
706
                if isOvnSubnet(podNet.Subnet) {
2✔
707
                        patch[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName)] = subnet.Name
1✔
708
                        if pod.Annotations[fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName)] == "" {
2✔
709
                                patch[fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName)] = c.config.PodNicType
1✔
710
                        }
1✔
711
                } else {
×
712
                        patch[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName)] = nil
×
713
                        patch[fmt.Sprintf(util.PodNicAnnotationTemplate, podNet.ProviderName)] = nil
×
714
                }
×
715
                patch[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)] = "true"
1✔
716
                if vmKey != "" {
1✔
717
                        patch[fmt.Sprintf(util.VMAnnotationTemplate, podNet.ProviderName)] = vmName
×
718
                }
×
719
                if err := util.ValidateNetworkBroadcast(podNet.Subnet.Spec.CIDRBlock, ipStr); err != nil {
2✔
720
                        klog.Errorf("validate pod %s/%s failed: %v", namespace, name, err)
1✔
721
                        c.recorder.Eventf(pod, v1.EventTypeWarning, "ValidatePodNetworkFailed", "stage=validateNetworkBroadcast error=%v", err)
1✔
722
                        return nil, err
1✔
723
                }
1✔
724

725
                if podNet.Type != providerTypeIPAM {
2✔
726
                        if (subnet.Spec.Vlan == "" || subnet.Spec.LogicalGateway || subnet.Spec.U2OInterconnection) && subnet.Spec.Vpc != "" {
2✔
727
                                patch[fmt.Sprintf(util.LogicalRouterAnnotationTemplate, podNet.ProviderName)] = subnet.Spec.Vpc
1✔
728
                        }
1✔
729

730
                        if subnet.Spec.Vlan != "" {
2✔
731
                                vlan, err := c.vlansLister.Get(subnet.Spec.Vlan)
1✔
732
                                if err != nil {
2✔
733
                                        klog.Error(err)
1✔
734
                                        c.recorder.Eventf(pod, v1.EventTypeWarning, "GetVlanInfoFailed", "stage=getVlanInfo error=%v", err)
1✔
735
                                        return nil, err
1✔
736
                                }
1✔
737
                                patch[fmt.Sprintf(util.VlanIDAnnotationTemplate, podNet.ProviderName)] = strconv.Itoa(vlan.Spec.ID)
×
738
                                patch[fmt.Sprintf(util.ProviderNetworkTemplate, podNet.ProviderName)] = vlan.Spec.Provider
×
739
                        }
740

741
                        portSecurity := false
1✔
742
                        if pod.Annotations[fmt.Sprintf(util.PortSecurityAnnotationTemplate, podNet.ProviderName)] == "true" {
1✔
743
                                portSecurity = true
×
744
                        }
×
745

746
                        vips := c.getVirtualIPs(pod, []*kubeovnNet{podNet})[fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)]
1✔
747
                        for ip := range strings.SplitSeq(vips, ",") {
2✔
748
                                if ip != "" && net.ParseIP(ip) == nil {
1✔
749
                                        klog.Errorf("invalid vip address '%s' for pod %s", ip, name)
×
750
                                        vips = ""
×
751
                                        break
×
752
                                }
753
                        }
754

755
                        portName := ovs.PodNameToPortName(podName, namespace, podNet.ProviderName)
1✔
756

1✔
757
                        dhcpV4 := pod.Annotations[fmt.Sprintf(util.DHCPv4OptionsAnnotationTemplate, podNet.ProviderName)]
1✔
758
                        dhcpV6 := pod.Annotations[fmt.Sprintf(util.DHCPv6OptionsAnnotationTemplate, podNet.ProviderName)]
1✔
759
                        subnetDHCP, dhcpV4, dhcpV6 := dhcpOptionsForPodIPFamily(subnetDHCPOptionsUUIDs(subnet), ipStr, dhcpV4, dhcpV6)
1✔
760

1✔
761
                        var mtu int
1✔
762
                        var gateway string
1✔
763
                        if dhcpV4 != "" || dhcpV6 != "" {
1✔
764
                                if mtu, err = c.getSubnetMTU(subnet); err != nil {
×
765
                                        recordFailure("getSubnetMTU", err)
×
766
                                        return nil, err
×
767
                                }
×
768
                                gateway = subnet.Spec.Gateway
×
769
                                if subnet.Status.U2OInterconnectionIP != "" && subnet.Spec.U2OInterconnection {
×
770
                                        gateway = subnet.Status.U2OInterconnectionIP
×
771
                                }
×
772
                        }
773

774
                        dhcpOptions, hasPerPortDHCP, err := c.OVNNbClient.ReconcilePortDHCPOptions(
1✔
775
                                subnet.Name, portName, subnetDHCP,
1✔
776
                                subnet.Spec.CIDRBlock, gateway, dhcpV4, dhcpV6, mtu,
1✔
777
                        )
1✔
778
                        if err != nil {
2✔
779
                                klog.Errorf("failed to reconcile DHCP options for port %s: %v", portName, err)
1✔
780
                                recordFailure("reconcilePortDHCPOptions", err)
1✔
781
                                return nil, err
1✔
782
                        }
1✔
783

784
                        // When pod has per-port DHCP options, enable DHCP regardless of subnet setting.
785
                        enableDHCP := podNet.Subnet.Spec.EnableDHCP || hasPerPortDHCP
1✔
786

1✔
787
                        var oldSgList []string
1✔
788
                        if vmKey != "" {
1✔
789
                                existingLsp, err := c.OVNNbClient.GetLogicalSwitchPort(portName, true)
×
790
                                if err != nil {
×
791
                                        klog.Errorf("failed to get logical switch port %s: %v", portName, err)
×
792
                                        recordFailure("getLogicalSwitchPort", err)
×
793
                                        return nil, err
×
794
                                }
×
795
                                if existingLsp != nil {
×
796
                                        oldSgList, _ = c.getPortSg(existingLsp)
×
797
                                }
×
798
                        }
799

800
                        securityGroupAnnotation := pod.Annotations[fmt.Sprintf(util.SecurityGroupAnnotationTemplate, podNet.ProviderName)]
1✔
801
                        if err := c.OVNNbClient.CreateLogicalSwitchPort(subnet.Name, portName, ipStr, mac, podName, pod.Namespace,
1✔
802
                                portSecurity, securityGroupAnnotation, vips, enableDHCP, dhcpOptions, subnet.Spec.Vpc); err != nil {
2✔
803
                                c.recorder.Eventf(pod, v1.EventTypeWarning, "CreateOVNPortFailed", "stage=createLogicalSwitchPort error=%v", err)
1✔
804
                                klog.Errorf("%v", err)
1✔
805
                                return nil, err
1✔
806
                        }
1✔
807

808
                        if pod.Annotations[fmt.Sprintf(util.Layer2ForwardAnnotationTemplate, podNet.ProviderName)] == "true" {
2✔
809
                                if err := c.OVNNbClient.EnablePortLayer2forward(portName); err != nil {
2✔
810
                                        c.recorder.Eventf(pod, v1.EventTypeWarning, "SetOVNPortL2ForwardFailed", "stage=setLogicalSwitchPortLayer2Forward error=%v", err)
1✔
811
                                        klog.Errorf("%v", err)
1✔
812
                                        return nil, err
1✔
813
                                }
1✔
814
                        }
815

816
                        if securityGroupAnnotation != "" || oldSgList != nil {
1✔
817
                                securityGroups := strings.ReplaceAll(securityGroupAnnotation, " ", "")
×
818
                                newSgList := strings.Split(securityGroups, ",")
×
819
                                sgNames := util.UnionStringSlice(oldSgList, newSgList)
×
820
                                for _, sgName := range sgNames {
×
821
                                        if sgName != "" {
×
822
                                                c.syncSgPortsQueue.Add(sgName)
×
823
                                        }
×
824
                                }
825
                        }
826

827
                        if vips != "" {
2✔
828
                                c.syncVirtualPortsQueue.Add(podNet.Subnet.Name)
1✔
829
                        }
1✔
830
                }
831
                // CreatePort may fail, so put ip CR creation after CreatePort
832
                ipCRName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
1✔
833
                if err := c.createOrUpdateIPCR(ipCRName, podName, ipStr, mac, subnet.Name, pod.Namespace, pod.Spec.NodeName, podType); err != nil {
1✔
834
                        err = fmt.Errorf("failed to create ips CR %s.%s: %w", podName, pod.Namespace, err)
×
835
                        klog.Error(err)
×
836
                        recordFailure("createOrUpdateIPCR", err)
×
837
                        return nil, err
×
838
                }
×
839
        }
840
        if err = util.PatchAnnotations(c.config.KubeClient.CoreV1().Pods(namespace), name, patch); err != nil {
1✔
841
                if k8serrors.IsNotFound(err) {
×
842
                        // Sometimes pod is deleted between kube-ovn configure ovn-nb and patch pod.
×
843
                        // Then we need to recycle the resource again.
×
844
                        key := strings.Join([]string{namespace, name}, "/")
×
845
                        c.deletingPodObjMap.Store(key, pod)
×
846
                        c.deletePodQueue.AddRateLimited(key)
×
847
                        return nil, nil
×
848
                }
×
849
                klog.Errorf("failed to patch pod %s/%s: %v", namespace, name, err)
×
850
                recordFailure("patchPodAnnotations", err)
×
851
                return nil, err
×
852
        }
853

854
        oldPod := pod
1✔
855
        if pod, err = c.config.KubeClient.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{}); err != nil {
1✔
856
                if k8serrors.IsNotFound(err) {
×
857
                        key := strings.Join([]string{namespace, name}, "/")
×
858
                        c.deletingPodObjMap.Store(key, oldPod)
×
859
                        c.deletePodQueue.AddRateLimited(key)
×
860
                        return nil, nil
×
861
                }
×
862
                klog.Errorf("failed to get pod %s/%s: %v", namespace, name, err)
×
863
                recordFailure("getPatchedPod", err)
×
864
                return nil, err
×
865
        }
866

867
        // Clean stale attachment IPs/LSPs from previous NAD references when a new
868
        // VM pod starts. This handles the stop→patch NAD→start workflow where the
869
        // old pod deletion was processed before the NAD change.
870
        // Called after pod re-fetch so getPodKubeovnNets sees current annotations.
871
        if isVMPod && c.config.EnableKeepVMIP {
1✔
872
                c.cleanStaleVMAttachmentIPs(pod, podName)
×
873
        }
×
874

875
        // Check if pod is a vpc nat gateway. Annotation set will have subnet provider name as prefix
876
        isVpcNatGw, vpcGwName := c.checkIsPodVpcNatGw(pod)
1✔
877
        if isVpcNatGw {
1✔
878
                c.enqueueAddOrUpdateVpcNatGwByName(vpcGwName, "natgw-pod-update")
×
879
                klog.Infof("init vpc nat gateway pod %s/%s with name %s", namespace, name, vpcGwName)
×
880
                c.initVpcNatGatewayQueue.Add(vpcGwName)
×
881
        }
×
882

883
        return pod, nil
1✔
884
}
885

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

893
        if len(needRoutePodNets) == 0 {
2✔
894
                return nil
1✔
895
        }
1✔
896

897
        namespace := pod.Namespace
1✔
898
        name := pod.Name
1✔
899
        podName := c.getNameByPod(pod)
1✔
900

1✔
901
        klog.Infof("sync pod %s/%s routed", namespace, name)
1✔
902

1✔
903
        node, err := c.nodesLister.Get(pod.Spec.NodeName)
1✔
904
        if err != nil {
2✔
905
                klog.Errorf("failed to get node %s: %v", pod.Spec.NodeName, err)
1✔
906
                return err
1✔
907
        }
1✔
908

909
        portGroups, err := c.OVNNbClient.ListPortGroups(map[string]string{"node": "", networkPolicyKey: ""})
1✔
910
        if err != nil {
1✔
911
                klog.Errorf("failed to list port groups: %v", err)
×
912
                return err
×
913
        }
×
914

915
        var nodePortGroups []string
1✔
916
        nodePortGroup := strings.ReplaceAll(node.Annotations[util.PortNameAnnotation], "-", ".")
1✔
917
        for _, pg := range portGroups {
1✔
918
                if pg.Name != nodePortGroup && pg.ExternalIDs["subnet"] == "" {
×
919
                        nodePortGroups = append(nodePortGroups, pg.Name)
×
920
                }
×
921
        }
922

923
        var podIP string
1✔
924
        var subnet *kubeovnv1.Subnet
1✔
925
        patch := util.KVPatch{}
1✔
926
        for _, podNet := range needRoutePodNets {
2✔
927
                // in case update handler overlap the annotation when cache is not in sync
1✔
928
                if pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, podNet.ProviderName)] == "" {
1✔
929
                        return fmt.Errorf("no address has been allocated to %s/%s", namespace, name)
×
930
                }
×
931

932
                podIP = pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]
1✔
933
                subnet = podNet.Subnet
1✔
934

1✔
935
                // Check if pod uses nodeSwitch subnet
1✔
936
                if subnet.Name == c.config.NodeSwitch {
1✔
937
                        return fmt.Errorf("NodeSwitch subnet %s is unavailable for pod", subnet.Name)
×
938
                }
×
939

940
                if portGroups, err = c.OVNNbClient.ListPortGroups(map[string]string{"subnet": subnet.Name, "node": "", networkPolicyKey: ""}); err != nil {
1✔
941
                        klog.Errorf("failed to list port groups: %v", err)
×
942
                        return err
×
943
                }
×
944

945
                pgName := getOverlaySubnetsPortGroupName(subnet.Name, pod.Spec.NodeName)
1✔
946
                portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
1✔
947
                subnetPortGroups := make([]string, 0, len(portGroups))
1✔
948
                for _, pg := range portGroups {
1✔
949
                        if pg.Name != pgName {
×
950
                                subnetPortGroups = append(subnetPortGroups, pg.Name)
×
951
                        }
×
952
                }
953

954
                if (!c.config.EnableLb || (subnet.Spec.EnableLb == nil || !*subnet.Spec.EnableLb)) &&
1✔
955
                        subnet.Spec.Vpc == c.config.ClusterRouter &&
1✔
956
                        subnet.Spec.U2OInterconnection &&
1✔
957
                        subnet.Spec.Vlan != "" &&
1✔
958
                        !subnet.Spec.LogicalGateway {
1✔
959
                        // remove lsp from other port groups
×
960
                        // we need to do this because the pod, e.g. a sts/vm, can be rescheduled to another node
×
961
                        if err = c.OVNNbClient.RemovePortFromPortGroups(portName, subnetPortGroups...); err != nil {
×
962
                                klog.Errorf("failed to remove port %s from port groups %v: %v", portName, subnetPortGroups, err)
×
963
                                return err
×
964
                        }
×
965
                        // add lsp to the port group
966
                        if err := c.OVNNbClient.PortGroupAddPorts(pgName, portName); err != nil {
×
967
                                klog.Errorf("failed to add port to u2o port group %s: %v", pgName, err)
×
968
                                return err
×
969
                        }
×
970
                }
971

972
                if podIP != "" && (subnet.Spec.Vlan == "" || subnet.Spec.LogicalGateway) && subnet.Spec.Vpc == c.config.ClusterRouter {
1✔
973
                        // remove lsp from other port groups
×
974
                        // we need to do this because the pod, e.g. a sts/vm, can be rescheduled to another node
×
975
                        if err = c.OVNNbClient.RemovePortFromPortGroups(portName, nodePortGroups...); err != nil {
×
976
                                klog.Errorf("failed to remove port %s from port groups %v: %v", portName, nodePortGroups, err)
×
977
                                return err
×
978
                        }
×
979
                        // add lsp to the port group
980
                        if err = c.OVNNbClient.PortGroupAddPorts(nodePortGroup, portName); err != nil {
×
981
                                klog.Errorf("failed to add port %s to port group %s: %v", portName, nodePortGroup, err)
×
982
                                return err
×
983
                        }
×
984

985
                        if c.config.EnableEipSnat && (pod.Annotations[util.EipAnnotation] != "" || pod.Annotations[util.SnatAnnotation] != "") {
×
986
                                cm, err := c.configMapsLister.ConfigMaps(c.config.ExternalGatewayConfigNS).Get(util.ExternalGatewayConfig)
×
987
                                if err != nil {
×
988
                                        klog.Errorf("failed to get ex-gateway config, %v", err)
×
989
                                        return err
×
990
                                }
×
991
                                nextHop := cm.Data["external-gw-addr"]
×
992
                                if nextHop == "" {
×
993
                                        externalSubnet, err := c.subnetsLister.Get(c.config.ExternalGatewaySwitch)
×
994
                                        if err != nil {
×
995
                                                klog.Errorf("failed to get subnet %s, %v", c.config.ExternalGatewaySwitch, err)
×
996
                                                return err
×
997
                                        }
×
998
                                        nextHop = externalSubnet.Spec.Gateway
×
999
                                        if nextHop == "" {
×
1000
                                                klog.Errorf("no available gateway address")
×
1001
                                                return errors.New("no available gateway address")
×
1002
                                        }
×
1003
                                }
1004
                                if strings.Contains(nextHop, "/") {
×
1005
                                        nextHop = strings.Split(nextHop, "/")[0]
×
1006
                                }
×
1007

1008
                                if err := c.addPolicyRouteToVpc(
×
1009
                                        subnet.Spec.Vpc,
×
1010
                                        &kubeovnv1.PolicyRoute{
×
1011
                                                Priority:  util.NorthGatewayRoutePolicyPriority,
×
1012
                                                Match:     "ip4.src == " + podIP,
×
1013
                                                Action:    kubeovnv1.PolicyRouteActionReroute,
×
1014
                                                NextHopIP: nextHop,
×
1015
                                        },
×
1016
                                        map[string]string{
×
1017
                                                "vendor": util.CniTypeName,
×
1018
                                                "subnet": subnet.Name,
×
1019
                                        },
×
1020
                                ); err != nil {
×
1021
                                        klog.Errorf("failed to add policy route, %v", err)
×
1022
                                        return err
×
1023
                                }
×
1024

1025
                                // remove lsp from port group to make EIP/SNAT work
1026
                                if err = c.OVNNbClient.PortGroupRemovePorts(pgName, portName); err != nil {
×
1027
                                        klog.Error(err)
×
1028
                                        return err
×
1029
                                }
×
1030
                        } else {
×
1031
                                if subnet.Spec.GatewayType == kubeovnv1.GWDistributedType && pod.Annotations[util.NorthGatewayAnnotation] == "" {
×
1032
                                        nodeTunlIPAddr, err := getNodeTunlIP(node)
×
1033
                                        if err != nil {
×
1034
                                                klog.Error(err)
×
1035
                                                return err
×
1036
                                        }
×
1037

1038
                                        var added bool
×
1039
                                        for _, nodeAddr := range nodeTunlIPAddr {
×
1040
                                                for podAddr := range strings.SplitSeq(podIP, ",") {
×
1041
                                                        if util.CheckProtocol(nodeAddr.String()) != util.CheckProtocol(podAddr) {
×
1042
                                                                continue
×
1043
                                                        }
1044

1045
                                                        // remove lsp from other port groups
1046
                                                        // we need to do this because the pod, e.g. a sts/vm, can be rescheduled to another node
1047
                                                        if err = c.OVNNbClient.RemovePortFromPortGroups(portName, subnetPortGroups...); err != nil {
×
1048
                                                                klog.Errorf("failed to remove port %s from port groups %v: %v", portName, subnetPortGroups, err)
×
1049
                                                                return err
×
1050
                                                        }
×
1051
                                                        if err := c.OVNNbClient.PortGroupAddPorts(pgName, portName); err != nil {
×
1052
                                                                klog.Errorf("failed to add port %s to port group %s: %v", portName, pgName, err)
×
1053
                                                                return err
×
1054
                                                        }
×
1055

1056
                                                        added = true
×
1057
                                                        break
×
1058
                                                }
1059
                                                if added {
×
1060
                                                        break
×
1061
                                                }
1062
                                        }
1063
                                }
1064

1065
                                if pod.Annotations[util.NorthGatewayAnnotation] != "" && pod.Annotations[util.IPAddressAnnotation] != "" {
×
1066
                                        for podAddr := range strings.SplitSeq(pod.Annotations[util.IPAddressAnnotation], ",") {
×
1067
                                                if util.CheckProtocol(podAddr) != util.CheckProtocol(pod.Annotations[util.NorthGatewayAnnotation]) {
×
1068
                                                        continue
×
1069
                                                }
1070
                                                ipSuffix := "ip4"
×
1071
                                                if util.CheckProtocol(podAddr) == kubeovnv1.ProtocolIPv6 {
×
1072
                                                        ipSuffix = "ip6"
×
1073
                                                }
×
1074

1075
                                                if err := c.addPolicyRouteToVpc(
×
1076
                                                        subnet.Spec.Vpc,
×
1077
                                                        &kubeovnv1.PolicyRoute{
×
1078
                                                                Priority:  util.NorthGatewayRoutePolicyPriority,
×
1079
                                                                Match:     fmt.Sprintf("%s.src == %s", ipSuffix, podAddr),
×
1080
                                                                Action:    kubeovnv1.PolicyRouteActionReroute,
×
1081
                                                                NextHopIP: pod.Annotations[util.NorthGatewayAnnotation],
×
1082
                                                        },
×
1083
                                                        map[string]string{
×
1084
                                                                "vendor": util.CniTypeName,
×
1085
                                                                "subnet": subnet.Name,
×
1086
                                                        },
×
1087
                                                ); err != nil {
×
1088
                                                        klog.Errorf("failed to add policy route, %v", err)
×
1089
                                                        return err
×
1090
                                                }
×
1091
                                        }
1092
                                } else if c.config.EnableEipSnat {
×
1093
                                        if err = c.deleteStaticRouteFromVpc(
×
1094
                                                c.config.ClusterRouter,
×
1095
                                                subnet.Spec.RouteTable,
×
1096
                                                podIP,
×
1097
                                                "",
×
1098
                                                kubeovnv1.PolicyDst,
×
1099
                                        ); err != nil {
×
1100
                                                klog.Error(err)
×
1101
                                                return err
×
1102
                                        }
×
1103
                                }
1104
                        }
1105

1106
                        if c.config.EnableEipSnat {
×
1107
                                for ipStr := range strings.SplitSeq(podIP, ",") {
×
1108
                                        if eip := pod.Annotations[util.EipAnnotation]; eip == "" {
×
1109
                                                if err = c.OVNNbClient.DeleteNats(c.config.ClusterRouter, ovnnb.NATTypeDNATAndSNAT, ipStr); err != nil {
×
1110
                                                        klog.Errorf("failed to delete nat rules: %v", err)
×
1111
                                                }
×
1112
                                        } else if util.CheckProtocol(eip) == util.CheckProtocol(ipStr) {
×
1113
                                                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 {
×
1114
                                                        klog.Errorf("failed to add nat rules, %v", err)
×
1115
                                                        return err
×
1116
                                                }
×
1117
                                        }
1118
                                        if eip := pod.Annotations[util.SnatAnnotation]; eip == "" {
×
1119
                                                if err = c.OVNNbClient.DeleteNats(c.config.ClusterRouter, ovnnb.NATTypeSNAT, ipStr); err != nil {
×
1120
                                                        klog.Errorf("failed to delete nat rules: %v", err)
×
1121
                                                }
×
1122
                                        } else if util.CheckProtocol(eip) == util.CheckProtocol(ipStr) {
×
1123
                                                if err = c.OVNNbClient.EnsureSnat(c.config.ClusterRouter, eip, ipStr); err != nil {
×
1124
                                                        klog.Errorf("failed to add nat rules, %v", err)
×
1125
                                                        return err
×
1126
                                                }
×
1127
                                        }
1128
                                }
1129
                        }
1130
                }
1131

1132
                if pod.Annotations[fmt.Sprintf(util.ActivationStrategyTemplate, podNet.ProviderName)] != "" {
1✔
1133
                        if err := c.OVNNbClient.SetLogicalSwitchPortActivationStrategy(portName, pod.Spec.NodeName); err != nil {
×
1134
                                klog.Errorf("failed to set activation strategy for lsp %s: %v", portName, err)
×
1135
                                return err
×
1136
                        }
×
1137
                }
1138

1139
                patch[fmt.Sprintf(util.RoutedAnnotationTemplate, podNet.ProviderName)] = "true"
1✔
1140
        }
1141
        if err := util.PatchAnnotations(c.config.KubeClient.CoreV1().Pods(namespace), name, patch); err != nil {
1✔
1142
                if k8serrors.IsNotFound(err) {
×
1143
                        // Sometimes pod is deleted between kube-ovn configure ovn-nb and patch pod.
×
1144
                        // Then we need to recycle the resource again.
×
1145
                        key := strings.Join([]string{namespace, name}, "/")
×
1146
                        c.deletingPodObjMap.Store(key, pod)
×
1147
                        c.deletePodQueue.AddRateLimited(key)
×
1148
                        return nil
×
1149
                }
×
1150
                klog.Errorf("failed to patch pod %s/%s: %v", namespace, name, err)
×
1151
                return err
×
1152
        }
1153
        return nil
1✔
1154
}
1155

1156
func (c *Controller) handleDeletePod(key string) (err error) {
1✔
1157
        pod, ok := c.deletingPodObjMap.Load(key)
1✔
1158
        if !ok {
1✔
1159
                return nil
×
1160
        }
×
1161
        now := time.Now()
1✔
1162
        klog.Infof("handle delete pod %s", key)
1✔
1163
        podName := c.getNameByPod(pod)
1✔
1164
        changed := false
1✔
1165
        stage := "prepare"
1✔
1166
        released := []string{}
1✔
1167
        var podNets []*kubeovnNet
1✔
1168
        var keepIPCR, isOwnerRefToDel, isOwnerRefDeleted bool
1✔
1169
        var ipcrToDelete []string
1✔
1170
        var vmOrphanedPorts map[string]bool
1✔
1171
        c.podKeyMutex.LockKey(key)
1✔
1172
        defer func() {
2✔
1173
                _ = c.podKeyMutex.UnlockKey(key)
1✔
1174
                if err != nil {
2✔
1175
                        c.recorder.Eventf(pod, v1.EventTypeWarning, "PodNetworkReleaseFailed", "stage=%s error=%v", stage, err)
1✔
1176
                } else if changed {
3✔
1177
                        details := strings.Join(released, "; ")
1✔
1178
                        if !keepIPCR {
2✔
1179
                                if networkDetails := c.podNetworkEventDetails(pod, podNets); networkDetails != "" {
2✔
1180
                                        details += "; " + networkDetails
1✔
1181
                                }
1✔
1182
                        }
1183
                        c.recorder.Eventf(pod, v1.EventTypeNormal, "PodNetworkReleased", "%s", strings.TrimPrefix(details, "; "))
1✔
1184
                }
1185
                if err == nil {
2✔
1186
                        c.deletingPodObjMap.Delete(key)
1✔
1187
                }
1✔
1188
                last := time.Since(now)
1✔
1189
                klog.Infof("take %d ms to handle delete pod %s", last.Milliseconds(), key)
1✔
1190
        }()
1191

1192
        p, _ := c.podsLister.Pods(pod.Namespace).Get(pod.Name)
1✔
1193
        if p != nil && p.UID != pod.UID {
2✔
1194
                // Pod with same name exists, just return here
1✔
1195
                return nil
1✔
1196
        }
1✔
1197

1198
        if aaps := pod.Annotations[util.AAPsAnnotation]; aaps != "" {
1✔
1199
                for vipName := range strings.SplitSeq(aaps, ",") {
×
1200
                        if vip, err := c.virtualIpsLister.Get(vipName); err == nil {
×
1201
                                if vip.Spec.Namespace != pod.Namespace {
×
1202
                                        continue
×
1203
                                }
1204
                                klog.Infof("enqueue update virtual parents for %s", vipName)
×
1205
                                c.updateVirtualParentsQueue.Add(vipName)
×
1206
                        }
1207
                }
1208
        }
1209

1210
        podKey := fmt.Sprintf("%s/%s", pod.Namespace, podName)
1✔
1211

1✔
1212
        isStsPod, stsName, stsUID := isStatefulSetPod(pod)
1✔
1213
        if isStsPod {
2✔
1214
                if !pod.DeletionTimestamp.IsZero() {
2✔
1215
                        klog.Infof("handle deletion of sts pod %s", podKey)
1✔
1216
                        isOwnerRefToDel = isStatefulSetPodToDel(c.config.KubeClient, pod, stsName, stsUID)
1✔
1217
                        if !isOwnerRefToDel {
2✔
1218
                                klog.Infof("try keep ip for sts pod %s", podKey)
1✔
1219
                                keepIPCR = true
1✔
1220
                        }
1✔
1221
                }
1222
                if keepIPCR {
2✔
1223
                        stage = "checkStatefulSetOwner"
1✔
1224
                        isOwnerRefDeleted, ipcrToDelete, err = appendCheckPodNetToDel(c, pod, stsName, util.KindStatefulSet)
1✔
1225
                        if err != nil {
2✔
1226
                                klog.Error(err)
1✔
1227
                                return err
1✔
1228
                        }
1✔
1229
                        if isOwnerRefDeleted || len(ipcrToDelete) != 0 {
×
1230
                                klog.Infof("not keep ip for sts pod %s", podKey)
×
1231
                                keepIPCR = false
×
1232
                        }
×
1233
                }
1234
        }
1235
        stage = "listLogicalSwitchPorts"
1✔
1236
        ports, err := c.OVNNbClient.ListNormalLogicalSwitchPorts(true, map[string]string{"pod": podKey})
1✔
1237
        if err != nil {
1✔
1238
                klog.Errorf("failed to list lsps of pod %s: %v", podKey, err)
×
1239
                return err
×
1240
        }
×
1241
        stage = "releaseNetworkResources"
1✔
1242

1✔
1243
        var hasAliveVMSibling bool
1✔
1244
        isVMPod, vmName := isVMPod(pod)
1✔
1245
        if isVMPod && c.config.EnableKeepVMIP {
2✔
1246
                for _, port := range ports {
2✔
1247
                        stage = "cleanLogicalSwitchPortMigrateOptions"
1✔
1248
                        if err := c.OVNNbClient.CleanLogicalSwitchPortMigrateOptions(port.Name); err != nil {
1✔
1249
                                err = fmt.Errorf("failed to clean migrate options for vm lsp %s, %w", port.Name, err)
×
1250
                                klog.Error(err)
×
1251
                                return err
×
1252
                        }
×
1253
                }
1254
                if pod.DeletionTimestamp != nil {
2✔
1255
                        klog.Infof("handle deletion of vm pod %s", podKey)
1✔
1256
                        isOwnerRefToDel = c.isVMToDel(pod, vmName)
1✔
1257
                        if !isOwnerRefToDel {
2✔
1258
                                klog.Infof("try keep ip for vm pod %s", podKey)
1✔
1259
                                keepIPCR = true
1✔
1260
                                // The VM LSP is shared across every virt-launcher pod of the VM
1✔
1261
                                // (ExternalIDs["pod"] is keyed by VM name). Port-group memberships,
1✔
1262
                                // however, belong to whichever virt-launcher pod is currently
1✔
1263
                                // running the VM. If another live sibling exists (e.g. a
1✔
1264
                                // live-migration destination while the completed source is being
1✔
1265
                                // GC'd), its memberships must not be wiped out.
1✔
1266
                                stage = "listVMPodSiblings"
1✔
1267
                                siblings, listErr := c.podsLister.Pods(pod.Namespace).List(labels.Everything())
1✔
1268
                                if listErr != nil {
1✔
1269
                                        klog.Errorf("failed to list pods in namespace %s: %v", pod.Namespace, listErr)
×
1270
                                        return listErr
×
1271
                                }
×
1272
                                hasAliveVMSibling = hasAliveSiblingVMPod(siblings, vmName, pod.Name)
1✔
1273
                        }
1274
                }
1275
                if keepIPCR {
2✔
1276
                        stage = "checkVMOwner"
1✔
1277
                        isOwnerRefDeleted, ipcrToDelete, err = appendCheckPodNetToDel(c, pod, vmName, util.KindVirtualMachineInstance)
1✔
1278
                        if err != nil {
1✔
1279
                                klog.Error(err)
×
1280
                                return err
×
1281
                        }
×
1282
                        if isOwnerRefDeleted || len(ipcrToDelete) != 0 {
1✔
1283
                                klog.Infof("not keep ip for vm pod %s", podKey)
×
1284
                                keepIPCR = false
×
1285
                        }
×
1286
                }
1287
                // Detect orphaned attachment ports from NAD hotplug (KubeVirt VEP #140).
1288
                // Compare OVN's actual ports against VM spec's desired ports.
1289
                // These are cleaned selectively in the keepIPCR=true branch to avoid
1290
                // deleting shared primary network LSPs during live migration.
1291
                if keepIPCR {
2✔
1292
                        vmOrphanedPorts = c.getVMOrphanedAttachmentPorts(pod.Namespace, vmName, ports)
1✔
1293
                }
1✔
1294
        }
1295

1296
        stage = "getPodKubeovnNets"
1✔
1297
        podNets, err = c.getPodKubeovnNets(pod)
1✔
1298
        if err != nil {
1✔
1299
                klog.Errorf("failed to get kube-ovn nets of pod %s: %v", podKey, err)
×
1300
                return err
×
1301
        }
×
1302
        if keepIPCR {
2✔
1303
                for _, port := range ports {
2✔
1304
                        switch {
1✔
1305
                        case vmOrphanedPorts[port.Name]:
1✔
1306
                                // Orphaned attachment LSP from NAD hotplug: delete and release its IP.
1✔
1307
                                stage = "getIPCR"
1✔
1308
                                ipCR, getErr := c.ipsLister.Get(port.Name)
1✔
1309
                                if getErr != nil && !k8serrors.IsNotFound(getErr) {
2✔
1310
                                        klog.Errorf("failed to get ip %s: %v", port.Name, getErr)
1✔
1311
                                        return getErr
1✔
1312
                                }
1✔
1313
                                klog.Infof("delete orphaned vm attachment lsp %s", port.Name)
1✔
1314
                                stage = "deleteLogicalSwitchPort"
1✔
1315
                                if err := c.OVNNbClient.DeleteLogicalSwitchPort(port.Name); err != nil {
1✔
1316
                                        klog.Errorf("failed to delete orphaned lsp %s: %v", port.Name, err)
×
1317
                                        return err
×
1318
                                }
×
1319
                                changed = true
1✔
1320
                                released = append(released, "logicalSwitchPort="+port.Name)
1✔
1321
                                if k8serrors.IsNotFound(getErr) {
1✔
1322
                                        continue
×
1323
                                }
1324
                                if ipCR.Labels[util.IPReservedLabel] != "true" {
2✔
1325
                                        klog.Infof("delete orphaned vm attachment ip CR %s", ipCR.Name)
1✔
1326
                                        stage = "deleteIPCR"
1✔
1327
                                        if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), ipCR.Name, metav1.DeleteOptions{}); err != nil {
1✔
1328
                                                if !k8serrors.IsNotFound(err) {
×
1329
                                                        klog.Errorf("failed to delete ip %s: %v", ipCR.Name, err)
×
1330
                                                        return err
×
1331
                                                }
×
1332
                                        } else {
1✔
1333
                                                changed = true
1✔
1334
                                                released = append(released, fmt.Sprintf("ipCR=%s subnet=%s", ipCR.Name, ipCR.Spec.Subnet))
1✔
1335
                                        }
1✔
1336
                                        if subnetName := ipCR.Spec.Subnet; subnetName != "" {
2✔
1337
                                                addressCount := len(c.ipam.GetPodAddress(podKey))
1✔
1338
                                                c.ipam.ReleaseAddressByNic(podKey, port.Name, subnetName)
1✔
1339
                                                if len(c.ipam.GetPodAddress(podKey)) < addressCount {
2✔
1340
                                                        changed = true
1✔
1341
                                                        released = append(released, fmt.Sprintf("ipam=%s subnet=%s", port.Name, subnetName))
1✔
1342
                                                }
1✔
1343
                                                c.updateSubnetStatusQueue.Add(subnetName)
1✔
1344
                                        }
1345
                                }
1346
                        case hasAliveVMSibling:
×
1347
                                klog.Infof("skip removing lsp %s from port groups: another alive virt-launcher pod exists for vm %s/%s", port.Name, pod.Namespace, vmName)
×
1348
                        default:
×
1349
                                klog.Infof("remove lsp %s from all port groups", port.Name)
×
1350
                                stage = "removeLogicalSwitchPortFromPortGroups"
×
1351
                                if err = c.OVNNbClient.RemovePortFromPortGroups(port.Name); err != nil {
×
1352
                                        klog.Errorf("failed to remove lsp %s from all port groups: %v", port.Name, err)
×
1353
                                        return err
×
1354
                                }
×
1355
                        }
1356
                }
1357
        } else {
1✔
1358
                if len(ports) != 0 {
2✔
1359
                        addresses := c.ipam.GetPodAddress(podKey)
1✔
1360
                        for _, address := range addresses {
1✔
1361
                                if strings.TrimSpace(address.IP) == "" {
×
1362
                                        continue
×
1363
                                }
1364
                                subnet, err := c.subnetsLister.Get(address.Subnet.Name)
×
1365
                                if k8serrors.IsNotFound(err) {
×
1366
                                        continue
×
1367
                                } else if err != nil {
×
1368
                                        klog.Error(err)
×
1369
                                        return err
×
1370
                                }
×
1371
                                vpc, err := c.vpcsLister.Get(subnet.Spec.Vpc)
×
1372
                                if k8serrors.IsNotFound(err) {
×
1373
                                        continue
×
1374
                                } else if err != nil {
×
1375
                                        klog.Error(err)
×
1376
                                        return err
×
1377
                                }
×
1378

1379
                                ipSuffix := "ip4"
×
1380
                                if util.CheckProtocol(address.IP) == kubeovnv1.ProtocolIPv6 {
×
1381
                                        ipSuffix = "ip6"
×
1382
                                }
×
1383
                                if err = c.deletePolicyRouteFromVpc(
×
1384
                                        vpc.Name,
×
1385
                                        util.NorthGatewayRoutePolicyPriority,
×
1386
                                        fmt.Sprintf("%s.src == %s", ipSuffix, address.IP),
×
1387
                                ); err != nil {
×
1388
                                        klog.Errorf("failed to delete static route, %v", err)
×
1389
                                        return err
×
1390
                                }
×
1391

1392
                                if c.config.EnableEipSnat {
×
1393
                                        if pod.Annotations[util.EipAnnotation] != "" {
×
1394
                                                if err = c.OVNNbClient.DeleteNat(c.config.ClusterRouter, ovnnb.NATTypeDNATAndSNAT, pod.Annotations[util.EipAnnotation], address.IP); err != nil {
×
1395
                                                        klog.Errorf("failed to delete nat rules: %v", err)
×
1396
                                                }
×
1397
                                        }
1398
                                        if snatEip := pod.Annotations[util.SnatAnnotation]; snatEip != "" {
×
1399
                                                if err = c.OVNNbClient.DeleteNat(c.config.ClusterRouter, ovnnb.NATTypeSNAT, snatEip, address.IP); err != nil {
×
1400
                                                        klog.Errorf("failed to delete nat rules: %v", err)
×
1401
                                                }
×
1402
                                        }
1403
                                }
1404
                        }
1405
                }
1406
                for _, port := range ports {
2✔
1407
                        // when lsp is deleted, the port of pod is deleted from any port-group automatically.
1✔
1408
                        klog.Infof("delete logical switch port %s", port.Name)
1✔
1409
                        stage = "deleteLogicalSwitchPort"
1✔
1410
                        if err := c.OVNNbClient.DeleteLogicalSwitchPort(port.Name); err != nil {
2✔
1411
                                klog.Errorf("failed to delete lsp %s, %v", port.Name, err)
1✔
1412
                                return err
1✔
1413
                        }
1✔
1414
                        changed = true
1✔
1415
                        released = append(released, "logicalSwitchPort="+port.Name)
1✔
1416
                }
1417
                klog.Infof("try release all ip address for deleting pod %s", podKey)
1✔
1418
                for _, podNet := range podNets {
2✔
1419
                        portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
1✔
1420
                        // if the OwnerRef has been deleted or is in the process of being deleted, all associated IPCRs must be cleaned up
1✔
1421
                        if (isStsPod || isVMPod) && !isOwnerRefToDel && !isOwnerRefDeleted &&
1✔
1422
                                !slices.Contains(ipcrToDelete, portName) {
1✔
1423
                                klog.Infof("skip clean ip CR %s", portName)
×
1424
                                continue
×
1425
                        }
1426
                        ipCR, err := c.ipsLister.Get(portName)
1✔
1427
                        if err != nil {
2✔
1428
                                if k8serrors.IsNotFound(err) {
2✔
1429
                                        continue
1✔
1430
                                }
1431
                                klog.Errorf("failed to get ip %s, %v", portName, err)
×
1432
                                return err
×
1433
                        }
1434
                        if ipCR.Labels[util.IPReservedLabel] != "true" {
×
1435
                                klog.Infof("delete ip CR %s", ipCR.Name)
×
1436
                                stage = "deleteIPCR"
×
1437
                                if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), ipCR.Name, metav1.DeleteOptions{}); err != nil {
×
1438
                                        if !k8serrors.IsNotFound(err) {
×
1439
                                                klog.Errorf("failed to delete ip %s, %v", ipCR.Name, err)
×
1440
                                                return err
×
1441
                                        }
×
1442
                                } else {
×
1443
                                        changed = true
×
1444
                                        released = append(released, fmt.Sprintf("ipCR=%s subnet=%s", ipCR.Name, podNet.Subnet.Name))
×
1445
                                }
×
1446
                                // release ipam address after delete ip CR
1447
                                addressCount := len(c.ipam.GetPodAddress(podKey))
×
1448
                                c.ipam.ReleaseAddressByNic(podKey, portName, podNet.Subnet.Name)
×
1449
                                if len(c.ipam.GetPodAddress(podKey)) < addressCount {
×
1450
                                        changed = true
×
1451
                                        released = append(released, fmt.Sprintf("ipam=%s subnet=%s", portName, podNet.Subnet.Name))
×
1452
                                }
×
1453
                                // Trigger subnet status update after IPAM release
1454
                                // This is needed when IP CR is deleted without finalizer (race condition)
1455
                                c.updateSubnetStatusQueue.Add(podNet.Subnet.Name)
×
1456
                        }
1457
                }
1458
                if pod.Annotations[util.VipAnnotation] != "" {
1✔
1459
                        vip, vipErr := c.virtualIpsLister.Get(pod.Annotations[util.VipAnnotation])
×
1460
                        vipWillChange := vipErr == nil && vip.Labels[util.IPReservedLabel] != ""
×
1461
                        stage = "releaseVIP"
×
1462
                        if err = c.releaseVip(pod.Annotations[util.VipAnnotation]); err != nil {
×
1463
                                klog.Errorf("failed to clean label from vip %s, %v", pod.Annotations[util.VipAnnotation], err)
×
1464
                                return err
×
1465
                        }
×
1466
                        if vipWillChange {
×
1467
                                changed = true
×
1468
                                released = append(released, "vip="+pod.Annotations[util.VipAnnotation])
×
1469
                        }
×
1470
                }
1471
        }
1472
        for _, podNet := range podNets {
2✔
1473
                // Skip non-OVN subnets for security group synchronization
1✔
1474
                if !isOvnSubnet(podNet.Subnet) {
1✔
1475
                        continue
×
1476
                }
1477

1478
                c.syncVirtualPortsQueue.Add(podNet.Subnet.Name)
1✔
1479
                securityGroupAnnotation := pod.Annotations[fmt.Sprintf(util.SecurityGroupAnnotationTemplate, podNet.ProviderName)]
1✔
1480
                if securityGroupAnnotation != "" {
1✔
1481
                        securityGroups := strings.ReplaceAll(securityGroupAnnotation, " ", "")
×
1482
                        for sgName := range strings.SplitSeq(securityGroups, ",") {
×
1483
                                if sgName != "" {
×
1484
                                        c.syncSgPortsQueue.Add(sgName)
×
1485
                                }
×
1486
                        }
1487
                }
1488
        }
1489
        return nil
1✔
1490
}
1491

1492
func (c *Controller) handleUpdatePodSecurity(key string) error {
1✔
1493
        now := time.Now()
1✔
1494
        klog.Infof("handle add/update pod security group %s", key)
1✔
1495

1✔
1496
        namespace, name, err := cache.SplitMetaNamespaceKey(key)
1✔
1497
        if err != nil {
1✔
1498
                utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
×
1499
                return nil
×
1500
        }
×
1501

1502
        c.podKeyMutex.LockKey(key)
1✔
1503
        defer func() {
2✔
1504
                _ = c.podKeyMutex.UnlockKey(key)
1✔
1505
                last := time.Since(now)
1✔
1506
                klog.Infof("take %d ms to handle sg for pod %s", last.Milliseconds(), key)
1✔
1507
        }()
1✔
1508

1509
        pod, err := c.podsLister.Pods(namespace).Get(name)
1✔
1510
        if err != nil {
1✔
1511
                if k8serrors.IsNotFound(err) {
×
1512
                        return nil
×
1513
                }
×
1514
                klog.Error(err)
×
1515
                return err
×
1516
        }
1517
        podName := c.getNameByPod(pod)
1✔
1518

1✔
1519
        podNets, err := c.getPodKubeovnNets(pod)
1✔
1520
        if err != nil {
2✔
1521
                klog.Errorf("failed to pod nets %v", err)
1✔
1522
                c.recorder.Eventf(pod, v1.EventTypeWarning, "PodSecurityUpdateFailed", "stage=getPodKubeovnNets error=%v", err)
1✔
1523
                return err
1✔
1524
        }
1✔
1525

1526
        vipsMap := c.getVirtualIPs(pod, podNets)
1✔
1527
        updatedPodNets := make([]*kubeovnNet, 0, len(podNets))
1✔
1528

1✔
1529
        // associated with security group
1✔
1530
        for _, podNet := range podNets {
2✔
1531
                // Skip non-OVN subnets (e.g., macvlan) that don't create OVN logical switch ports
1✔
1532
                if !isOvnSubnet(podNet.Subnet) {
2✔
1533
                        continue
1✔
1534
                }
1535

1536
                portSecurity := false
1✔
1537
                if pod.Annotations[fmt.Sprintf(util.PortSecurityAnnotationTemplate, podNet.ProviderName)] == "true" {
1✔
1538
                        portSecurity = true
×
1539
                }
×
1540

1541
                mac := pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)]
1✔
1542
                ipStr := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]
1✔
1543
                vips := vipsMap[fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)]
1✔
1544
                portName := ovs.PodNameToPortName(podName, namespace, podNet.ProviderName)
1✔
1545
                if err = c.OVNNbClient.SetLogicalSwitchPortSecurity(portSecurity, portName, mac, ipStr, vips); err != nil {
2✔
1546
                        klog.Errorf("failed to set security for logical switch port %s: %v", portName, err)
1✔
1547
                        c.recorder.Eventf(pod, v1.EventTypeWarning, "PodSecurityUpdateFailed", "stage=setLogicalSwitchPortSecurity error=%v", err)
1✔
1548
                        return err
1✔
1549
                }
1✔
1550

1551
                c.syncVirtualPortsQueue.Add(podNet.Subnet.Name)
1✔
1552
                securityGroupAnnotation := pod.Annotations[fmt.Sprintf(util.SecurityGroupAnnotationTemplate, podNet.ProviderName)]
1✔
1553
                var securityGroups string
1✔
1554
                if securityGroupAnnotation != "" {
1✔
1555
                        securityGroups = strings.ReplaceAll(securityGroupAnnotation, " ", "")
×
1556
                        for sgName := range strings.SplitSeq(securityGroups, ",") {
×
1557
                                if sgName != "" {
×
1558
                                        c.syncSgPortsQueue.Add(sgName)
×
1559
                                }
×
1560
                        }
1561
                }
1562
                if err = c.reconcilePortSg(portName, securityGroups); err != nil {
1✔
1563
                        klog.Errorf("reconcilePortSg failed. %v", err)
×
1564
                        c.recorder.Eventf(pod, v1.EventTypeWarning, "PodSecurityUpdateFailed", "stage=reconcilePortSg error=%v", err)
×
1565
                        return err
×
1566
                }
×
1567
                updatedPodNets = append(updatedPodNets, podNet)
1✔
1568
        }
1569
        if len(updatedPodNets) != 0 {
2✔
1570
                c.recorder.Eventf(pod, v1.EventTypeNormal, "PodSecurityUpdated", "%s", c.podNetworkEventDetails(pod, updatedPodNets))
1✔
1571
        }
1✔
1572
        return nil
1✔
1573
}
1574

1575
// we send pod net generated from     k8s.v1.cni.cncf.io/networks: '[{"name": "vm-overlay", "namespace": "default", "interface": "net1"}, {"name": "vm-overlay", "namespace": "default", "interface": "net2"}]'
1576
// there is no mac or ip address request here in the object generated from here
1577
// interface name in providerName is used for annotation for ip address
1578

1579
func stalePortNetworkDetails(pod *v1.Pod, podName string, port ovnnb.LogicalSwitchPort) (string, string, error) {
1✔
1580
        portPrefix := ovs.PodNameToPortName(podName, pod.Namespace, util.OvnProvider)
1✔
1581
        providerName := util.OvnProvider
1✔
1582
        if port.Name != portPrefix {
2✔
1583
                var ok bool
1✔
1584
                providerName, ok = strings.CutPrefix(port.Name, portPrefix+".")
1✔
1585
                if !ok || providerName == "" {
2✔
1586
                        return "", "", fmt.Errorf("logical switch port %q does not match pod prefix %q", port.Name, portPrefix)
1✔
1587
                }
1✔
1588
        }
1589
        details := fmt.Sprintf("provider=%s subnet=%s ip=%s mac=%s logicalSwitchPort=%s",
1✔
1590
                providerName,
1✔
1591
                port.ExternalIDs["ls"],
1✔
1592
                pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, providerName)],
1✔
1593
                pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, providerName)],
1✔
1594
                port.Name,
1✔
1595
        )
1✔
1596
        return providerName, details, nil
1✔
1597
}
1598

1599
func (c *Controller) syncKubeOvnNet(pod *v1.Pod, podNets []*kubeovnNet) (*v1.Pod, string, error) {
1✔
1600
        podName := c.getNameByPod(pod)
1✔
1601
        key := cache.NewObjectName(pod.Namespace, podName).String()
1✔
1602
        targetPortNameList := strset.NewWithSize(len(podNets))
1✔
1603
        portsNeedToDel := []string{}
1✔
1604
        annotationsNeedToDel := []string{}
1✔
1605
        annotationsNeedToAdd := make(map[string]string)
1✔
1606
        subnetUsedByPort := make(map[string]string)
1✔
1607
        changedPodNets := []*kubeovnNet{}
1✔
1608
        hotplugDetails := []string{}
1✔
1609

1✔
1610
        for _, podNet := range podNets {
2✔
1611
                portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
1✔
1612
                targetPortNameList.Add(portName)
1✔
1613
                changed := false
1✔
1614
                if podNet.IPRequest != "" && pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] != podNet.IPRequest {
2✔
1615
                        klog.Infof("pod %s/%s use custom IP %s for provider %s", pod.Namespace, pod.Name, podNet.IPRequest, podNet.ProviderName)
1✔
1616
                        annotationsNeedToAdd[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] = podNet.IPRequest
1✔
1617
                        changed = true
1✔
1618
                }
1✔
1619

1620
                if podNet.MacRequest != "" && pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] != podNet.MacRequest {
1✔
1621
                        klog.Infof("pod %s/%s use custom MAC %s for provider %s", pod.Namespace, pod.Name, podNet.MacRequest, podNet.ProviderName)
×
1622
                        annotationsNeedToAdd[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)] = podNet.MacRequest
×
1623
                        changed = true
×
1624
                }
×
1625
                if changed {
2✔
1626
                        changedPodNets = append(changedPodNets, podNet)
1✔
1627
                }
1✔
1628
        }
1629

1630
        ports, err := c.OVNNbClient.ListNormalLogicalSwitchPorts(true, map[string]string{"pod": key})
1✔
1631
        if err != nil {
1✔
1632
                klog.Errorf("failed to list lsps of pod '%s', %v", pod.Name, err)
×
1633
                return nil, "", err
×
1634
        }
×
1635

1636
        for _, port := range ports {
2✔
1637
                if !targetPortNameList.Has(port.Name) {
2✔
1638
                        portsNeedToDel = append(portsNeedToDel, port.Name)
1✔
1639
                        subnetUsedByPort[port.Name] = port.ExternalIDs["ls"]
1✔
1640
                        providerName, details, parseErr := stalePortNetworkDetails(pod, podName, port)
1✔
1641
                        if parseErr != nil {
2✔
1642
                                klog.Warning(parseErr)
1✔
1643
                                hotplugDetails = append(hotplugDetails, fmt.Sprintf("provider=unknown subnet=%s ip= mac= logicalSwitchPort=%s", port.ExternalIDs["ls"], port.Name))
1✔
1644
                                continue
1✔
1645
                        }
1646
                        hotplugDetails = append(hotplugDetails, details)
1✔
1647
                        if providerName == util.OvnProvider {
2✔
1648
                                continue
1✔
1649
                        }
1650
                        annotationsNeedToDel = append(annotationsNeedToDel, providerName)
1✔
1651
                }
1652
        }
1653

1654
        if len(portsNeedToDel) == 0 && len(annotationsNeedToAdd) == 0 {
2✔
1655
                return pod, "", nil
1✔
1656
        }
1✔
1657

1658
        for _, portNeedDel := range portsNeedToDel {
2✔
1659
                klog.Infof("release port %s for pod %s", portNeedDel, podName)
1✔
1660
                c.ipam.ReleaseAddressByNic(key, portNeedDel, subnetUsedByPort[portNeedDel])
1✔
1661
                if err := c.OVNNbClient.DeleteLogicalSwitchPort(portNeedDel); err != nil {
1✔
1662
                        klog.Errorf("failed to delete lsp %s, %v", portNeedDel, err)
×
1663
                        return nil, "", err
×
1664
                }
×
1665
                if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), portNeedDel, metav1.DeleteOptions{}); err != nil {
2✔
1666
                        if !k8serrors.IsNotFound(err) {
1✔
1667
                                klog.Errorf("failed to delete ip %s, %v", portNeedDel, err)
×
1668
                                return nil, "", err
×
1669
                        }
×
1670
                }
1671
        }
1672

1673
        patch := util.KVPatch{}
1✔
1674
        for _, providerName := range annotationsNeedToDel {
2✔
1675
                for key := range pod.Annotations {
2✔
1676
                        if strings.HasPrefix(key, providerName) {
2✔
1677
                                patch[key] = nil
1✔
1678
                        }
1✔
1679
                }
1680
        }
1681

1682
        for key, value := range annotationsNeedToAdd {
2✔
1683
                patch[key] = value
1✔
1684
        }
1✔
1685

1686
        if len(patch) == 0 {
2✔
1687
                return pod, strings.Join(hotplugDetails, "; "), nil
1✔
1688
        }
1✔
1689

1690
        if err = util.PatchAnnotations(c.config.KubeClient.CoreV1().Pods(pod.Namespace), pod.Name, patch); err != nil {
1✔
1691
                if k8serrors.IsNotFound(err) {
×
1692
                        return nil, "", nil
×
1693
                }
×
1694
                klog.Errorf("failed to clean annotations for pod %s/%s: %v", pod.Namespace, pod.Name, err)
×
1695
                return nil, "", err
×
1696
        }
1697

1698
        if pod, err = c.config.KubeClient.CoreV1().Pods(pod.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}); err != nil {
1✔
1699
                if k8serrors.IsNotFound(err) {
×
1700
                        return nil, "", nil
×
1701
                }
×
1702
                klog.Errorf("failed to get pod %s/%s: %v", pod.Namespace, pod.Name, err)
×
1703
                return nil, "", err
×
1704
        }
1705

1706
        if details := c.podNetworkEventDetails(pod, changedPodNets); details != "" {
2✔
1707
                hotplugDetails = append(hotplugDetails, details)
1✔
1708
        }
1✔
1709
        return pod, strings.Join(hotplugDetails, "; "), nil
1✔
1710
}
1711

1712
func (c *Controller) podNetworkEventDetails(pod *v1.Pod, podNets []*kubeovnNet) string {
1✔
1713
        podName := c.getNameByPod(pod)
1✔
1714
        details := make([]string, 0, len(podNets))
1✔
1715
        for _, podNet := range podNets {
2✔
1716
                details = append(details, fmt.Sprintf(
1✔
1717
                        "provider=%s subnet=%s ip=%s mac=%s logicalSwitchPort=%s",
1✔
1718
                        podNet.ProviderName,
1✔
1719
                        podNet.Subnet.Name,
1✔
1720
                        pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)],
1✔
1721
                        pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)],
1✔
1722
                        ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName),
1✔
1723
                ))
1✔
1724
        }
1✔
1725
        return strings.Join(details, "; ")
1✔
1726
}
1727

1728
func isStatefulSetPod(pod *v1.Pod) (bool, string, types.UID) {
1✔
1729
        for _, owner := range pod.OwnerReferences {
2✔
1730
                if owner.Kind == util.KindStatefulSet && strings.HasPrefix(owner.APIVersion, appsv1.SchemeGroupVersion.Group+"/") {
2✔
1731
                        if strings.HasPrefix(pod.Name, owner.Name) {
2✔
1732
                                return true, owner.Name, owner.UID
1✔
1733
                        }
1✔
1734
                }
1735
        }
1736
        return false, "", ""
1✔
1737
}
1738

1739
func isStatefulSetPodToDel(c kubernetes.Interface, pod *v1.Pod, statefulSetName string, statefulSetUID types.UID) bool {
1✔
1740
        // only delete statefulset pod lsp when statefulset deleted or down scaled
1✔
1741
        sts, err := c.AppsV1().StatefulSets(pod.Namespace).Get(context.Background(), statefulSetName, metav1.GetOptions{})
1✔
1742
        if err != nil {
2✔
1743
                // statefulset is deleted
1✔
1744
                if k8serrors.IsNotFound(err) {
1✔
1745
                        klog.Infof("statefulset %s/%s has been deleted", pod.Namespace, statefulSetName)
×
1746
                        return true
×
1747
                }
×
1748
                klog.Errorf("failed to get statefulset %s/%s: %v", pod.Namespace, statefulSetName, err)
1✔
1749
                return false
1✔
1750
        }
1751

1752
        // statefulset is being deleted, or it's a newly created one
1753
        if !sts.DeletionTimestamp.IsZero() {
×
1754
                klog.Infof("statefulset %s/%s is being deleted", pod.Namespace, statefulSetName)
×
1755
                return true
×
1756
        }
×
1757
        if sts.UID != statefulSetUID {
×
1758
                klog.Infof("statefulset %s/%s is a newly created one", pod.Namespace, statefulSetName)
×
1759
                return true
×
1760
        }
×
1761

1762
        // down scale statefulset
1763
        tempStrs := strings.Split(pod.Name, "-")
×
1764
        numStr := tempStrs[len(tempStrs)-1]
×
1765
        index, err := strconv.ParseInt(numStr, 10, 0)
×
1766
        if err != nil {
×
1767
                klog.Errorf("failed to parse %s to int", numStr)
×
1768
                return false
×
1769
        }
×
1770
        // down scaled
1771
        var startOrdinal int64
×
1772
        if sts.Spec.Ordinals != nil {
×
1773
                startOrdinal = int64(sts.Spec.Ordinals.Start)
×
1774
        }
×
1775
        if index >= startOrdinal+int64(*sts.Spec.Replicas) {
×
1776
                klog.Infof("statefulset %s/%s is down scaled", pod.Namespace, statefulSetName)
×
1777
                return true
×
1778
        }
×
1779
        return false
×
1780
}
1781

1782
// only gc statefulset pod lsp when:
1783
// 1. the statefulset has been deleted or is being deleted
1784
// 2. the statefulset has been deleted and recreated
1785
// 3. the statefulset is down scaled and the pod is not alive
1786
func isStatefulSetPodToGC(c kubernetes.Interface, pod *v1.Pod, statefulSetName string, statefulSetUID types.UID) bool {
×
1787
        sts, err := c.AppsV1().StatefulSets(pod.Namespace).Get(context.Background(), statefulSetName, metav1.GetOptions{})
×
1788
        if err != nil {
×
1789
                // the statefulset has been deleted
×
1790
                if k8serrors.IsNotFound(err) {
×
1791
                        klog.Infof("statefulset %s/%s has been deleted", pod.Namespace, statefulSetName)
×
1792
                        return true
×
1793
                }
×
1794
                klog.Errorf("failed to get statefulset %s/%s: %v", pod.Namespace, statefulSetName, err)
×
1795
                return false
×
1796
        }
1797

1798
        // statefulset is being deleted
1799
        if !sts.DeletionTimestamp.IsZero() {
×
1800
                klog.Infof("statefulset %s/%s is being deleted", pod.Namespace, statefulSetName)
×
1801
                return true
×
1802
        }
×
1803
        // the statefulset has been deleted and recreated
1804
        if sts.UID != statefulSetUID {
×
1805
                klog.Infof("statefulset %s/%s is a newly created one", pod.Namespace, statefulSetName)
×
1806
                return true
×
1807
        }
×
1808

1809
        // the statefulset is down scaled and the pod is not alive
1810

1811
        tempStrs := strings.Split(pod.Name, "-")
×
1812
        numStr := tempStrs[len(tempStrs)-1]
×
1813
        index, err := strconv.ParseInt(numStr, 10, 0)
×
1814
        if err != nil {
×
1815
                klog.Errorf("failed to parse %s to int", numStr)
×
1816
                return false
×
1817
        }
×
1818
        // down scaled
1819
        var startOrdinal int64
×
1820
        if sts.Spec.Ordinals != nil {
×
1821
                startOrdinal = int64(sts.Spec.Ordinals.Start)
×
1822
        }
×
1823
        if index >= startOrdinal+int64(*sts.Spec.Replicas) {
×
1824
                klog.Infof("statefulset %s/%s is down scaled", pod.Namespace, statefulSetName)
×
1825
                if !isPodAlive(pod) {
×
1826
                        // we must check whether the pod is alive because we have to consider the following case:
×
1827
                        // 1. the statefulset is down scaled to zero
×
1828
                        // 2. the lsp gc is triggered
×
1829
                        // 3. gc interval, e.g. 90s, is passed and the second gc is triggered
×
1830
                        // 4. the sts is up scaled to the original replicas
×
1831
                        // 5. the pod is still running and it will not be recreated
×
1832
                        return true
×
1833
                }
×
1834
        }
1835

1836
        return false
×
1837
}
1838

1839
func getNodeTunlIP(node *v1.Node) ([]net.IP, error) {
×
1840
        var nodeTunlIPAddr []net.IP
×
1841
        nodeTunlIP := node.Annotations[util.IPAddressAnnotation]
×
1842
        if nodeTunlIP == "" {
×
1843
                return nil, errors.New("node has no tunnel ip annotation")
×
1844
        }
×
1845

1846
        for ip := range strings.SplitSeq(nodeTunlIP, ",") {
×
1847
                parsed := net.ParseIP(ip)
×
1848
                if parsed == nil {
×
1849
                        return nil, fmt.Errorf("failed to parse tunnel IP %q on node %s", ip, node.Name)
×
1850
                }
×
1851
                nodeTunlIPAddr = append(nodeTunlIPAddr, parsed)
×
1852
        }
1853
        return nodeTunlIPAddr, nil
×
1854
}
1855

1856
func getNextHopByTunnelIP(gw []net.IP) string {
×
1857
        // validation check by caller
×
1858
        nextHop := gw[0].String()
×
1859
        if len(gw) == 2 {
×
1860
                nextHop = gw[0].String() + "," + gw[1].String()
×
1861
        }
×
1862
        return nextHop
×
1863
}
1864

1865
func needAllocateSubnets(pod *v1.Pod, nets []*kubeovnNet) []*kubeovnNet {
1✔
1866
        // check if allocate from subnet is need.
1✔
1867
        // allocate subnet when change subnet to hotplug nic
1✔
1868
        // allocate subnet when migrate vm
1✔
1869
        if !isPodAlive(pod) {
1✔
1870
                return nil
×
1871
        }
×
1872

1873
        if pod.Annotations == nil {
1✔
1874
                return nets
×
1875
        }
×
1876

1877
        migrate := false
1✔
1878
        if job, ok := pod.Annotations[kubevirtv1.MigrationJobNameAnnotation]; ok {
1✔
1879
                klog.Infof("pod %s/%s is in the migration job %s", pod.Namespace, pod.Name, job)
×
1880
                migrate = true
×
1881
        }
×
1882

1883
        result := make([]*kubeovnNet, 0, len(nets))
1✔
1884
        for _, n := range nets {
2✔
1885
                if migrate || pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, n.ProviderName)] != "true" {
2✔
1886
                        result = append(result, n)
1✔
1887
                }
1✔
1888
        }
1889
        return result
1✔
1890
}
1891

1892
func needRestartNatGatewayPod(pod *v1.Pod) bool {
×
1893
        for _, psc := range pod.Status.ContainerStatuses {
×
1894
                if psc.Name != "vpc-nat-gw" {
×
1895
                        continue
×
1896
                }
1897
                if psc.RestartCount > 0 {
×
1898
                        return true
×
1899
                }
×
1900
        }
1901
        return false
×
1902
}
1903

1904
func (c *Controller) podNeedSync(pod *v1.Pod) (bool, error) {
×
1905
        // 1. check annotations
×
1906
        if pod.Annotations == nil {
×
1907
                return true, nil
×
1908
        }
×
1909
        // 2. check annotation ovn subnet
1910
        if pod.Annotations[util.RoutedAnnotation] != "true" {
×
1911
                return true, nil
×
1912
        }
×
1913
        // 3. check multus subnet
1914
        attachmentNets, err := c.getPodAttachmentNet(pod)
×
1915
        if err != nil {
×
1916
                klog.Error(err)
×
1917
                return false, err
×
1918
        }
×
1919

1920
        podName := c.getNameByPod(pod)
×
1921
        for _, n := range attachmentNets {
×
1922
                if pod.Annotations[fmt.Sprintf(util.RoutedAnnotationTemplate, n.ProviderName)] != "true" {
×
1923
                        return true, nil
×
1924
                }
×
1925
                ipName := ovs.PodNameToPortName(podName, pod.Namespace, n.ProviderName)
×
1926
                if _, err = c.ipsLister.Get(ipName); err != nil {
×
1927
                        if !k8serrors.IsNotFound(err) {
×
1928
                                err = fmt.Errorf("failed to get ip %s: %w", ipName, err)
×
1929
                                klog.Error(err)
×
1930
                                return false, err
×
1931
                        }
×
1932
                        klog.Infof("ip %s not found", ipName)
×
1933
                        // need to sync to create ip
×
1934
                        return true, nil
×
1935
                }
1936
        }
1937
        return false, nil
×
1938
}
1939

1940
func needRouteSubnets(pod *v1.Pod, nets []*kubeovnNet) []*kubeovnNet {
1✔
1941
        if !isPodAlive(pod) {
1✔
1942
                return nil
×
1943
        }
×
1944

1945
        if pod.Annotations == nil {
1✔
1946
                return nets
×
1947
        }
×
1948

1949
        result := make([]*kubeovnNet, 0, len(nets))
1✔
1950
        for _, n := range nets {
2✔
1951
                if !isOvnSubnet(n.Subnet) {
1✔
1952
                        continue
×
1953
                }
1954

1955
                if pod.Annotations[fmt.Sprintf(util.AllocatedAnnotationTemplate, n.ProviderName)] == "true" && pod.Spec.NodeName != "" {
2✔
1956
                        if pod.Annotations[fmt.Sprintf(util.RoutedAnnotationTemplate, n.ProviderName)] != "true" {
2✔
1957
                                result = append(result, n)
1✔
1958
                        }
1✔
1959
                }
1960
        }
1961
        return result
1✔
1962
}
1963

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

1✔
1968
        // check pod annotations
1✔
1969
        if lsName := pod.Annotations[util.LogicalSwitchAnnotation]; lsName != "" {
2✔
1970
                // annotations only has one default subnet
1✔
1971
                subnet, err := c.subnetsLister.Get(lsName)
1✔
1972
                if err != nil {
1✔
1973
                        klog.Errorf("failed to get subnet %s: %v", lsName, err)
×
1974
                        if k8serrors.IsNotFound(err) {
×
1975
                                if ignoreSubnetNotExist {
×
1976
                                        klog.Errorf("deleting pod %s/%s default subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, lsName)
×
1977
                                        return nil, nil
×
1978
                                }
×
1979
                        }
1980
                        return nil, err
×
1981
                }
1982
                return subnet, nil
1✔
1983
        }
1984
        if poolName := strings.TrimSpace(pod.Annotations[util.IPPoolAnnotation]); poolName != "" &&
1✔
1985
                !strings.ContainsAny(poolName, ",;") && net.ParseIP(poolName) == nil {
2✔
1986
                pool, err := c.ippoolLister.Get(poolName)
1✔
1987
                if err != nil {
1✔
NEW
1988
                        return nil, fmt.Errorf("failed to get ippool %s: %w", poolName, err)
×
NEW
1989
                }
×
1990
                subnet, err := c.subnetsLister.Get(pool.Spec.Subnet)
1✔
1991
                if err != nil {
1✔
NEW
1992
                        return nil, fmt.Errorf("failed to get subnet %s for ippool %s: %w", pool.Spec.Subnet, poolName, err)
×
NEW
1993
                }
×
1994
                return subnet, nil
1✔
1995
        }
1996

1997
        ns, err := c.namespacesLister.Get(pod.Namespace)
1✔
1998
        if err != nil {
1✔
1999
                klog.Errorf("failed to get namespace %s: %v", pod.Namespace, err)
×
2000
                return nil, err
×
2001
        }
×
2002
        if len(ns.Annotations) == 0 {
1✔
2003
                err = fmt.Errorf("namespace %s network annotations is empty", ns.Name)
×
2004
                klog.Error(err)
×
2005
                return nil, err
×
2006
        }
×
2007

2008
        subnetNames := ns.Annotations[util.LogicalSwitchAnnotation]
1✔
2009
        for subnetName := range strings.SplitSeq(subnetNames, ",") {
2✔
2010
                if subnetName == "" {
1✔
2011
                        err = fmt.Errorf("namespace %s default logical switch is not found", ns.Name)
×
2012
                        klog.Error(err)
×
2013
                        return nil, err
×
2014
                }
×
2015
                subnet, err := c.subnetsLister.Get(subnetName)
1✔
2016
                if err != nil {
1✔
2017
                        klog.Errorf("failed to get subnet %s: %v", subnetName, err)
×
2018
                        if k8serrors.IsNotFound(err) {
×
2019
                                if ignoreSubnetNotExist {
×
2020
                                        klog.Errorf("deleting pod %s/%s namespace subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
2021
                                        // ip name is unique, it is ok if any subnet release it
×
2022
                                        // gc will handle their ip cr, if all subnets are not exist
×
2023
                                        continue
×
2024
                                }
2025
                        }
2026
                        return nil, err
×
2027
                }
2028

2029
                switch subnet.Spec.Protocol {
1✔
2030
                case kubeovnv1.ProtocolDual:
×
2031
                        if subnet.Status.V6AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) {
×
2032
                                klog.Infof("there's no available ipv6 address in subnet %s, try next one", subnet.Name)
×
2033
                                continue
×
2034
                        }
2035
                        fallthrough
×
2036
                case kubeovnv1.ProtocolIPv4:
1✔
2037
                        if subnet.Status.V4AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) {
1✔
2038
                                klog.Infof("there's no available ipv4 address in subnet %s, try next one", subnet.Name)
×
2039
                                continue
×
2040
                        }
2041
                case kubeovnv1.ProtocolIPv6:
×
2042
                        if subnet.Status.V6AvailableIPs.EqualInt64(0) && !c.podCanUseExcludeIPs(pod, subnet) {
×
2043
                                klog.Infof("there's no available ipv6 address in subnet %s, try next one", subnet.Name)
×
2044
                                continue
×
2045
                        }
2046
                }
2047
                return subnet, nil
1✔
2048
        }
2049
        return nil, ipam.ErrNoAvailable
×
2050
}
2051

2052
func (c *Controller) podCanUseExcludeIPs(pod *v1.Pod, subnet *kubeovnv1.Subnet) bool {
×
2053
        if ipAddr := pod.Annotations[util.IPAddressAnnotation]; ipAddr != "" {
×
2054
                return c.checkIPsInExcludeList(ipAddr, subnet.Spec.ExcludeIps, subnet.Spec.CIDRBlock)
×
2055
        }
×
2056
        if ipPool := pod.Annotations[util.IPPoolAnnotation]; ipPool != "" {
×
2057
                return c.checkIPsInExcludeList(ipPool, subnet.Spec.ExcludeIps, subnet.Spec.CIDRBlock)
×
2058
        }
×
2059

2060
        return false
×
2061
}
2062

2063
func (c *Controller) checkIPsInExcludeList(ips string, excludeIPs []string, cidr string) bool {
×
2064
        expandedExcludeIPs := util.ExpandExcludeIPs(excludeIPs, cidr)
×
2065

×
2066
        for ipAddr := range strings.SplitSeq(strings.TrimSpace(ips), ",") {
×
2067
                ipAddr = strings.TrimSpace(ipAddr)
×
2068
                if ipAddr == "" {
×
2069
                        continue
×
2070
                }
2071

2072
                for _, excludeIP := range expandedExcludeIPs {
×
2073
                        if util.ContainsIPs(excludeIP, ipAddr) {
×
2074
                                klog.V(3).Infof("IP %s is found in exclude IP %s, allowing allocation", ipAddr, excludeIP)
×
2075
                                return true
×
2076
                        }
×
2077
                }
2078
        }
2079
        return false
×
2080
}
2081

2082
type providerType int
2083

2084
const (
2085
        providerTypeIPAM providerType = iota
2086
        providerTypeOriginal
2087
)
2088

2089
type kubeovnNet struct {
2090
        Type               providerType
2091
        ProviderName       string
2092
        Subnet             *kubeovnv1.Subnet
2093
        IsDefault          bool
2094
        AllowLiveMigration bool
2095
        IPRequest          string
2096
        MacRequest         string
2097
        NadName            string
2098
        NadNamespace       string
2099
        InterfaceName      string
2100
}
2101

2102
func (c *Controller) getPodAttachmentNet(pod *v1.Pod) ([]*kubeovnNet, error) {
1✔
2103
        var multusNets []*nadv1.NetworkSelectionElement
1✔
2104
        defaultAttachNetworks := pod.Annotations[util.DefaultNetworkAnnotation]
1✔
2105
        if defaultAttachNetworks != "" {
1✔
2106
                attachments, err := nadutils.ParseNetworkAnnotation(defaultAttachNetworks, pod.Namespace)
×
2107
                if err != nil {
×
2108
                        klog.Errorf("failed to parse default attach net for pod '%s', %v", pod.Name, err)
×
2109
                        return nil, err
×
2110
                }
×
2111
                multusNets = attachments
×
2112
        }
2113

2114
        attachNetworks := pod.Annotations[nadv1.NetworkAttachmentAnnot]
1✔
2115
        if attachNetworks != "" {
2✔
2116
                attachments, err := nadutils.ParseNetworkAnnotation(attachNetworks, pod.Namespace)
1✔
2117
                if err != nil {
1✔
2118
                        klog.Errorf("failed to parse attach net for pod '%s', %v", pod.Name, err)
×
2119
                        return nil, err
×
2120
                }
×
2121
                multusNets = append(multusNets, attachments...)
1✔
2122
        }
2123
        // only pods with attachment networks need the full subnet list; skip the
2124
        // listing for the common case (no attachment network) to avoid allocating
2125
        // the whole subnet set on every pod reconcile
2126
        var subnets []*kubeovnv1.Subnet
1✔
2127
        if len(multusNets) != 0 {
2✔
2128
                var err error
1✔
2129
                subnets, err = c.subnetsLister.List(labels.Everything())
1✔
2130
                if err != nil {
1✔
2131
                        klog.Errorf("failed to list subnets: %v", err)
×
2132
                        return nil, err
×
2133
                }
×
2134
        }
2135

2136
        // ignore to return all existing subnets to clean its ip crd
2137
        ignoreSubnetNotExist := !pod.DeletionTimestamp.IsZero()
1✔
2138

1✔
2139
        nadCounts := make(map[string]int)
1✔
2140
        for _, attach := range multusNets {
2✔
2141
                nadCounts[fmt.Sprintf("%s/%s", attach.Namespace, attach.Name)]++
1✔
2142
        }
1✔
2143

2144
        result := make([]*kubeovnNet, 0, len(multusNets))
1✔
2145
        for _, attach := range multusNets {
2✔
2146
                nadKey := fmt.Sprintf("%s/%s", attach.Namespace, attach.Name)
1✔
2147
                network, err := c.netAttachLister.NetworkAttachmentDefinitions(attach.Namespace).Get(attach.Name)
1✔
2148
                if err != nil {
2✔
2149
                        klog.Errorf("failed to get net-attach-def %s, %v", attach.Name, err)
1✔
2150
                        if k8serrors.IsNotFound(err) && ignoreSubnetNotExist {
2✔
2151
                                // NAD deleted before pod, find subnet for cleanup
1✔
2152
                                providerName := fmt.Sprintf("%s.%s.%s", attach.Name, attach.Namespace, util.OvnProvider)
1✔
2153
                                // k8s.v1.cni.cncf.io/networks: '[{"name": "vm-overlay", "namespace": "default", "interface": "net1"}, {"name": "vm-overlay", "namespace": "default", "interface": "net2"}]'
1✔
2154
                                // will make providerName to be "vm-overlay.default.ovn.net1" and "vm-overlay.default.ovn.net2", but the subnet annotation is "vm-overlay.default.ovn"
1✔
2155
                                if nadCounts[nadKey] > 1 && attach.InterfaceRequest != "" {
1✔
2156
                                        providerName = fmt.Sprintf("%s.%s", providerName, attach.InterfaceRequest)
×
2157
                                }
×
2158

2159
                                // IPAM-only attachments (e.g. ipvlan/macvlan with ipam.type kube-ovn) bind to a
2160
                                // subnet whose provider is "<nad>.<namespace>" without the ".ovn" suffix, and the
2161
                                // add path never appends the interface name to it. Match this form as well so the
2162
                                // IP is released here instead of leaking (the periodic gc does not reclaim it).
2163
                                ipamProviderName := fmt.Sprintf("%s.%s", attach.Name, attach.Namespace)
1✔
2164

1✔
2165
                                // if interface name is provided this means providerName will contain interface name
1✔
2166
                                // say vm-overlay.default.ovn.net1 which will not match the `provider` definition in the config object
1✔
2167
                                // for each interface then we need annotation
1✔
2168
                                // vm-overlay.default.ovn.net1.kubernetes.io/logical_switch = "subnetName" to allow it to identify correct switch to be associated with this interface
1✔
2169
                                // interfaceName is included in the name
1✔
2170
                                subnetName := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, providerName)]
1✔
2171
                                if subnetName == "" {
2✔
2172
                                        for _, subnet := range subnets {
2✔
2173
                                                if subnet.Spec.Provider == providerName || subnet.Spec.Provider == ipamProviderName {
2✔
2174
                                                        subnetName = subnet.Name
1✔
2175
                                                        // use the subnet's real provider so the released IP CR name
1✔
2176
                                                        // (pod.namespace.provider) matches what was allocated
1✔
2177
                                                        providerName = subnet.Spec.Provider
1✔
2178
                                                        break
1✔
2179
                                                }
2180
                                        }
2181
                                }
2182

2183
                                if subnetName == "" {
1✔
2184
                                        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)
×
2185
                                        continue
×
2186
                                }
2187

2188
                                subnet, err := c.subnetsLister.Get(subnetName)
1✔
2189
                                if err != nil {
1✔
2190
                                        klog.Errorf("failed to get subnet %s, %v", subnetName, err)
×
2191
                                        if k8serrors.IsNotFound(err) {
×
2192
                                                klog.Errorf("deleting pod %s/%s attach subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
2193
                                                continue
×
2194
                                        }
2195
                                        return nil, err
×
2196
                                }
2197

2198
                                klog.Infof("pod %s/%s net-attach-def %s not found, using subnet %s for cleanup", pod.Namespace, pod.Name, attach.Name, subnetName)
1✔
2199
                                result = append(result, &kubeovnNet{
1✔
2200
                                        Type:          providerTypeIPAM,
1✔
2201
                                        ProviderName:  providerName,
1✔
2202
                                        Subnet:        subnet,
1✔
2203
                                        IsDefault:     util.IsDefaultNet(pod.Annotations[util.DefaultNetworkAnnotation], attach),
1✔
2204
                                        NadName:       attach.Name,
1✔
2205
                                        NadNamespace:  attach.Namespace,
1✔
2206
                                        InterfaceName: attach.InterfaceRequest,
1✔
2207
                                })
1✔
2208
                                continue
1✔
2209
                        }
2210
                        return nil, err
×
2211
                }
2212

2213
                if network.Spec.Config == "" {
1✔
2214
                        continue
×
2215
                }
2216

2217
                netCfg, err := loadNetConf([]byte(network.Spec.Config))
1✔
2218
                if err != nil {
1✔
2219
                        klog.Errorf("failed to load config of net-attach-def %s, %v", attach.Name, err)
×
2220
                        return nil, err
×
2221
                }
×
2222

2223
                // allocate kubeovn network
2224
                var providerName string
1✔
2225
                if util.IsOvnNetwork(netCfg) {
2✔
2226
                        allowLiveMigration := false
1✔
2227
                        isDefault := util.IsDefaultNet(pod.Annotations[util.DefaultNetworkAnnotation], attach)
1✔
2228

1✔
2229
                        // same logic as above we adding ifName in providerName
1✔
2230

1✔
2231
                        // vm-overlay.default.ovn
1✔
2232
                        providerName = fmt.Sprintf("%s.%s.%s", attach.Name, attach.Namespace, util.OvnProvider)
1✔
2233
                        if nadCounts[nadKey] > 1 && attach.InterfaceRequest != "" {
1✔
2234
                                // due to interface name in request this becomes
×
2235
                                // vm-overlay.default.ovn.net1
×
2236
                                providerName = fmt.Sprintf("%s.%s", providerName, attach.InterfaceRequest)
×
2237
                        }
×
2238
                        if pod.Annotations[kubevirtv1.MigrationJobNameAnnotation] != "" {
1✔
2239
                                allowLiveMigration = true
×
2240
                        }
×
2241

2242
                        // as a result when default subnetName i not provided then it will not match subnet spec.. as a result we find nothing and subnet is set to empty
2243
                        // key is vm-overlay.default.ovn.net1.kubernetes.io/logical_switch: vm-overlay
2244
                        subnetName := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, providerName)]
1✔
2245

1✔
2246
                        // helper function to try and identify correct subnet when interface name is provided in request, this is for the case when multiple attach with same NAD but different interface name and the providerName contains interface name but subnet spec does not contain it
1✔
2247
                        subnetMatches := func(subnet *kubeovnv1.Subnet, providerName, ifName string) bool {
2✔
2248
                                var subnetProviderName string
1✔
2249
                                // if providerName contains ifName, then we trim it from the providerName to match with subnet spec
1✔
2250
                                subnetProviderName, _ = strings.CutSuffix(providerName, "."+ifName)
1✔
2251
                                klog.Infof("subnet %s, subnet provider %s, providername %s, trimmed subnetprovider %s, ifName %s", subnet.Name, subnet.Spec.Provider, providerName, subnetProviderName, ifName)
1✔
2252

1✔
2253
                                if subnet.Spec.Provider == subnetProviderName {
1✔
2254
                                        klog.Infof("matched to subnet %s", subnet.Name)
×
2255
                                        return true
×
2256
                                }
×
2257

2258
                                return false
1✔
2259
                        }
2260
                        if subnetName == "" {
2✔
2261
                                // when ifName is provided in request, the `provider` in subnet spec will not match
1✔
2262
                                // since it does not contain substring for interface name and wrong subnet will be selected
1✔
2263
                                for _, subnet := range subnets {
2✔
2264
                                        if subnetMatches(subnet, providerName, attach.InterfaceRequest) {
1✔
2265
                                                subnetName = subnet.Name
×
2266
                                                break
×
2267
                                        }
2268
                                }
2269
                        }
2270
                        klog.V(5).Infof("found subnet %s for provider %s", subnetName, providerName)
1✔
2271
                        var subnet *kubeovnv1.Subnet
1✔
2272
                        if subnetName == "" {
2✔
2273
                                err = fmt.Errorf("provider %s is not bound to any subnet", providerName)
1✔
2274
                                if ignoreSubnetNotExist {
2✔
2275
                                        klog.Errorf("deleting pod %s/%s attach %s %v, gc will clean its ip cr", pod.Namespace, pod.Name, attach.Name, err)
1✔
2276
                                        continue
1✔
2277
                                }
2278
                                klog.Error(err)
1✔
2279
                                return nil, err
1✔
2280
                        }
2281
                        subnet, err = c.subnetsLister.Get(subnetName)
1✔
2282
                        if err != nil {
1✔
2283
                                klog.Errorf("failed to get subnet %s, %v", subnetName, err)
×
2284
                                if k8serrors.IsNotFound(err) {
×
2285
                                        if ignoreSubnetNotExist {
×
2286
                                                klog.Errorf("deleting pod %s/%s attach subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
2287
                                                // just continue to next attach subnet
×
2288
                                                // ip name is unique, so it is ok if the other subnet release it
×
2289
                                                continue
×
2290
                                        }
2291
                                }
2292
                                return nil, err
×
2293
                        }
2294

2295
                        // we send no macrequest or ip request so these should be empty in the response here
2296
                        ret := &kubeovnNet{
1✔
2297
                                Type:               providerTypeOriginal,
1✔
2298
                                ProviderName:       providerName,
1✔
2299
                                Subnet:             subnet,
1✔
2300
                                IsDefault:          isDefault,
1✔
2301
                                AllowLiveMigration: allowLiveMigration,
1✔
2302
                                MacRequest:         attach.MacRequest,
1✔
2303
                                IPRequest:          strings.Join(attach.IPRequest, ","),
1✔
2304
                                NadName:            attach.Name,
1✔
2305
                                NadNamespace:       attach.Namespace,
1✔
2306
                                InterfaceName:      attach.InterfaceRequest,
1✔
2307
                        }
1✔
2308
                        result = append(result, ret)
1✔
2309
                } else {
1✔
2310
                        if !isKubeOVNIPAMNetwork(netCfg) {
2✔
2311
                                continue
1✔
2312
                        }
2313
                        providerName = fmt.Sprintf("%s.%s", attach.Name, attach.Namespace)
1✔
2314
                        foundSubnet := false
1✔
2315
                        for _, subnet := range subnets {
2✔
2316
                                if subnet.Spec.Provider == providerName {
2✔
2317
                                        result = append(result, &kubeovnNet{
1✔
2318
                                                Type:          providerTypeIPAM,
1✔
2319
                                                ProviderName:  providerName,
1✔
2320
                                                Subnet:        subnet,
1✔
2321
                                                MacRequest:    attach.MacRequest,
1✔
2322
                                                IPRequest:     strings.Join(attach.IPRequest, ","),
1✔
2323
                                                NadName:       attach.Name,
1✔
2324
                                                NadNamespace:  attach.Namespace,
1✔
2325
                                                InterfaceName: attach.InterfaceRequest,
1✔
2326
                                        })
1✔
2327
                                        foundSubnet = true
1✔
2328
                                        break
1✔
2329
                                }
2330
                        }
2331
                        if !foundSubnet {
2✔
2332
                                err = fmt.Errorf("provider %s is not bound to any subnet", providerName)
1✔
2333
                                if ignoreSubnetNotExist {
2✔
2334
                                        klog.Errorf("deleting pod %s/%s IPAM network %s %v, gc will clean its ip cr", pod.Namespace, pod.Name, providerName, err)
1✔
2335
                                        continue
1✔
2336
                                }
2337
                                return nil, err
1✔
2338
                        }
2339
                }
2340
        }
2341
        return result, nil
1✔
2342
}
2343

2344
func isKubeOVNIPAMNetwork(netCfg *multustypes.DelegateNetConf) bool {
1✔
2345
        if netCfg.Conf.IPAM.Type == util.CniTypeName {
2✔
2346
                return true
1✔
2347
        }
1✔
2348
        for _, plugin := range netCfg.ConfList.Plugins {
2✔
2349
                if plugin.IPAM.Type == util.CniTypeName {
2✔
2350
                        return true
1✔
2351
                }
1✔
2352
        }
2353
        return false
1✔
2354
}
2355

2356
func (c *Controller) validatePodIP(podName, subnetName, ipv4, ipv6 string) (bool, bool, error) {
1✔
2357
        subnet, err := c.subnetsLister.Get(subnetName)
1✔
2358
        if err != nil {
1✔
2359
                klog.Errorf("failed to get subnet %s: %v", subnetName, err)
×
2360
                return false, false, err
×
2361
        }
×
2362

2363
        if subnet.Spec.Vlan == "" && subnet.Spec.Vpc == c.config.ClusterRouter {
2✔
2364
                nodes, err := c.nodesLister.List(labels.Everything())
1✔
2365
                if err != nil {
1✔
2366
                        klog.Errorf("failed to list nodes: %v", err)
×
2367
                        return false, false, err
×
2368
                }
×
2369

2370
                for _, node := range nodes {
1✔
2371
                        nodeIPv4, nodeIPv6 := util.GetNodeInternalIP(*node)
×
2372
                        if ipv4 != "" && ipv4 == nodeIPv4 {
×
2373
                                klog.Errorf("IP address (%s) assigned to pod %s is the same with internal IP address of node %s, reallocating...", ipv4, podName, node.Name)
×
2374
                                return false, true, nil
×
2375
                        }
×
2376
                        if ipv6 != "" && ipv6 == nodeIPv6 {
×
2377
                                klog.Errorf("IP address (%s) assigned to pod %s is the same with internal IP address of node %s, reallocating...", ipv6, podName, node.Name)
×
2378
                                return true, false, nil
×
2379
                        }
×
2380
                }
2381
        }
2382

2383
        return true, true, nil
1✔
2384
}
2385

2386
// acquireMacOnlyAddress allocates only a MAC address (no IP) for a pod NIC on an
2387
// underlay subnet without a CIDR (BYO-DHCP / external DHCP). The guest obtains its
2388
// IP from an external DHCP server. MAC allocation goes through IPAM so it is
2389
// idempotent per NIC (no churn on retries) and the subnet/MAC are tracked, which
2390
// prevents the GC from cleaning the pod's mac-only IP/LSP resources.
2391
func (c *Controller) acquireMacOnlyAddress(pod *v1.Pod, podNet *kubeovnNet, key, portName string) (string, string, string, *kubeovnv1.Subnet, error) {
×
2392
        klog.Infof("allocating MAC-only address for pod %s in mac-only subnet %s", key, podNet.Subnet.Name)
×
2393

×
2394
        // Prefer a per-interface MAC annotation, then fall back to the provider-level one.
×
2395
        var macPointer *string
×
2396
        if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
×
2397
                if macStr := pod.Annotations[perInterfaceMACAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)]; macStr != "" {
×
2398
                        if _, err := net.ParseMAC(macStr); err != nil {
×
2399
                                return "", "", "", podNet.Subnet, err
×
2400
                        }
×
2401
                        macPointer = &macStr
×
2402
                }
2403
        }
2404
        if macPointer == nil {
×
2405
                if annoMAC := pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)]; annoMAC != "" {
×
2406
                        if _, err := net.ParseMAC(annoMAC); err != nil {
×
2407
                                return "", "", "", podNet.Subnet, err
×
2408
                        }
×
2409
                        macPointer = &annoMAC
×
2410
                }
2411
        }
2412

2413
        _, _, mac, err := c.ipam.GetRandomAddress(key, portName, macPointer, podNet.Subnet.Name, "", nil, !podNet.AllowLiveMigration)
×
2414
        if err != nil {
×
2415
                klog.Errorf("failed to allocate MAC for pod %s in mac-only subnet %s: %v", key, podNet.Subnet.Name, err)
×
2416
                return "", "", "", podNet.Subnet, err
×
2417
        }
×
2418
        return "", "", mac, podNet.Subnet, nil
×
2419
}
2420

2421
// podNetRequestedIPFamily reads the family request from the annotation scoped to
2422
// this provider. The default provider naturally maps to ovn.kubernetes.io/ip_family.
2423
func podNetRequestedIPFamily(pod *v1.Pod, podNet *kubeovnNet) string {
1✔
2424
        if pod == nil || pod.Annotations == nil {
1✔
2425
                return ""
×
2426
        }
×
2427
        return util.NormalizeIPFamily(pod.Annotations[fmt.Sprintf(util.IPFamilyAnnotationTemplate, podNet.ProviderName)])
1✔
2428
}
2429

2430
// validateRequestedIPFamilyForSubnet rejects requests that cannot be satisfied
2431
// by a single-stack subnet. Dual-stack subnets are handled by family-aware IPAM.
2432
func validateRequestedIPFamilyForSubnet(ipFamily string, subnet *kubeovnv1.Subnet) error {
1✔
2433
        if ipFamily == "" || subnet == nil || subnet.Spec.Protocol == kubeovnv1.ProtocolDual || subnet.Spec.Protocol == kubeovnv1.ProtocolMac {
2✔
2434
                return nil
1✔
2435
        }
1✔
2436
        if ipFamily != subnet.Spec.Protocol {
×
2437
                return fmt.Errorf("requested ip family %s does not match subnet %s protocol %s", ipFamily, subnet.Name, subnet.Spec.Protocol)
×
2438
        }
×
2439
        return nil
×
2440
}
2441

2442
// ippoolHasAvailableIPFamily checks availability for the requested family.
2443
// Without a requested family, it keeps the existing subnet protocol behavior.
2444
func ippoolHasAvailableIPFamily(ippool *kubeovnv1.IPPool, subnetProtocol, ipFamily string) bool {
1✔
2445
        if ipFamily != "" {
2✔
2446
                switch ipFamily {
1✔
2447
                case kubeovnv1.ProtocolIPv4:
1✔
2448
                        return ippool.Status.V4AvailableIPs.Int64() != 0
1✔
2449
                case kubeovnv1.ProtocolIPv6:
1✔
2450
                        return ippool.Status.V6AvailableIPs.Int64() != 0
1✔
2451
                }
2452
        }
2453

2454
        switch subnetProtocol {
1✔
2455
        case kubeovnv1.ProtocolDual:
1✔
2456
                return ippool.Status.V4AvailableIPs.Int64() != 0 && ippool.Status.V6AvailableIPs.Int64() != 0
1✔
2457
        case kubeovnv1.ProtocolIPv4:
×
2458
                return ippool.Status.V4AvailableIPs.Int64() != 0
×
2459
        default:
×
2460
                return ippool.Status.V6AvailableIPs.Int64() != 0
×
2461
        }
2462
}
2463

2464
func (c *Controller) acquireAddress(pod *v1.Pod, podNet *kubeovnNet) (string, string, string, *kubeovnv1.Subnet, error) {
1✔
2465
        // becomes vmNAme when pod is owned by a kubevirt VM
1✔
2466
        podName := c.getNameByPod(pod)
1✔
2467
        key := cache.NewObjectName(pod.Namespace, podName).String()
1✔
2468
        portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
1✔
2469

1✔
2470
        // Handle underlay subnets without a CIDR (BYO-DHCP / external DHCP): only a MAC is
1✔
2471
        // allocated and the guest obtains its IP from an external DHCP server.
1✔
2472
        if podNet.Subnet.Spec.Vlan != "" && podNet.Subnet.Spec.CIDRBlock == "" {
1✔
2473
                return c.acquireMacOnlyAddress(pod, podNet, key, portName)
×
2474
        }
×
2475
        requestedIPFamily := podNetRequestedIPFamily(pod, podNet)
1✔
2476
        if err := validateRequestedIPFamilyForSubnet(requestedIPFamily, podNet.Subnet); err != nil {
1✔
2477
                return "", "", "", podNet.Subnet, err
×
2478
        }
×
2479

2480
        var checkVMPod bool
1✔
2481
        isStsPod, _, _ := isStatefulSetPod(pod)
1✔
2482
        // if pod has static vip
1✔
2483
        vipName := pod.Annotations[util.VipAnnotation]
1✔
2484
        if vipName != "" {
2✔
2485
                vip, err := c.virtualIpsLister.Get(vipName)
1✔
2486
                if err != nil {
1✔
2487
                        klog.Errorf("failed to get static vip '%s', %v", vipName, err)
×
2488
                        return "", "", "", podNet.Subnet, err
×
2489
                }
×
2490
                if c.config.EnableKeepVMIP {
1✔
2491
                        checkVMPod, _ = isVMPod(pod)
×
2492
                }
×
2493
                if err = c.podReuseVip(vipName, portName, isStsPod || checkVMPod); err != nil {
1✔
2494
                        return "", "", "", podNet.Subnet, err
×
2495
                }
×
2496
                return vip.Status.V4ip, vip.Status.V6ip, vip.Status.Mac, podNet.Subnet, nil
1✔
2497
        }
2498

2499
        var macPointer *string
1✔
2500
        if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
1✔
2501
                // will not use the mac address vm-overlay.default.kubernetes.io/mac_address.net1
×
2502
                key := perInterfaceMACAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
×
2503
                if macStr := pod.Annotations[key]; macStr != "" {
×
2504
                        if _, err := net.ParseMAC(macStr); err != nil {
×
2505
                                return "", "", "", podNet.Subnet, err
×
2506
                        }
×
2507
                        macPointer = &macStr
×
2508
                }
2509
        }
2510

2511
        if macPointer == nil && isOvnSubnet(podNet.Subnet) {
2✔
2512
                annoMAC := pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)]
1✔
2513
                if annoMAC != "" {
2✔
2514
                        if _, err := net.ParseMAC(annoMAC); err != nil {
1✔
2515
                                return "", "", "", podNet.Subnet, err
×
2516
                        }
×
2517
                        macPointer = &annoMAC
1✔
2518
                }
2519
        } else if macPointer == nil {
×
2520
                macPointer = new("")
×
2521
        }
×
2522

2523
        var nsNets []*kubeovnNet
1✔
2524
        ippoolStr := pod.Annotations[fmt.Sprintf(util.IPPoolAnnotationTemplate, podNet.ProviderName)]
1✔
2525
        subnetStr := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName)]
1✔
2526

1✔
2527
        // Prepare nsNets based on subnet annotation
1✔
2528
        var err error
1✔
2529
        if subnetStr != "" {
2✔
2530
                nsNets = []*kubeovnNet{podNet}
1✔
2531
        } else if nsNets, err = c.getNsAvailableSubnets(pod, podNet); err != nil {
2✔
2532
                klog.Errorf("failed to get available subnets for pod %s/%s, %v", pod.Namespace, pod.Name, err)
×
2533
                return "", "", "", podNet.Subnet, err
×
2534
        }
×
2535

2536
        if ippoolStr == "" && podNet.IsDefault {
2✔
2537
                // no ippool specified by pod annotation, use namespace ippools or ippools in the subnet specified by pod annotation
1✔
2538
                ns, err := c.namespacesLister.Get(pod.Namespace)
1✔
2539
                if err != nil {
1✔
2540
                        klog.Errorf("failed to get namespace %s: %v", pod.Namespace, err)
×
2541
                        return "", "", "", podNet.Subnet, err
×
2542
                }
×
2543
                subnetNames := make([]string, 0, len(nsNets))
1✔
2544
                for _, net := range nsNets {
2✔
2545
                        if net.Subnet.Name == subnetStr {
2✔
2546
                                // allocate from ippools in the subnet specified by pod annotation
1✔
2547
                                podNet.Subnet = net.Subnet
1✔
2548
                                subnetNames = []string{net.Subnet.Name}
1✔
2549
                                break
1✔
2550
                        }
2551
                        subnetNames = append(subnetNames, net.Subnet.Name)
1✔
2552
                }
2553

2554
                if subnetStr == "" || slices.Contains(subnetNames, subnetStr) {
2✔
2555
                        // no subnet specified by pod annotation or specified subnet is in namespace subnets
1✔
2556
                        if ipPoolList, ok := ns.Annotations[util.IPPoolAnnotation]; ok {
1✔
2557
                                for ipPoolName := range strings.SplitSeq(ipPoolList, ",") {
×
2558
                                        ippool, err := c.ippoolLister.Get(ipPoolName)
×
2559
                                        if err != nil {
×
2560
                                                klog.Errorf("failed to get ippool %s: %v", ipPoolName, err)
×
2561
                                                return "", "", "", podNet.Subnet, err
×
2562
                                        }
×
2563

2564
                                        if !ippoolHasAvailableIPFamily(ippool, podNet.Subnet.Spec.Protocol, requestedIPFamily) {
×
2565
                                                continue
×
2566
                                        }
2567

2568
                                        for _, net := range nsNets {
×
2569
                                                if net.Subnet.Name == ippool.Spec.Subnet && slices.Contains(subnetNames, net.Subnet.Name) {
×
2570
                                                        ippoolStr = ippool.Name
×
2571
                                                        podNet.Subnet = net.Subnet
×
2572
                                                        break
×
2573
                                                }
2574
                                        }
2575
                                        if ippoolStr != "" {
×
2576
                                                break
×
2577
                                        }
2578
                                }
2579
                                if ippoolStr == "" {
×
2580
                                        klog.Infof("no available ippool in subnet(s) %s for pod %s/%s", strings.Join(subnetNames, ","), pod.Namespace, pod.Name)
×
2581
                                        return "", "", "", podNet.Subnet, ipam.ErrNoAvailable
×
2582
                                }
×
2583
                        }
2584
                }
2585
        }
2586

2587
        // will be subnet.namespace.interface.ovn.kubernetes.io/ip_address in case of multiple interfaces with
2588
        // interface name provided or subnet.namespace.ovn.kubernetes.io/ip_address in case of single interface or multiple interfaces without interface name provided
2589
        if pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] == "" &&
1✔
2590
                ippoolStr == "" {
2✔
2591
                // check new IP annotation
1✔
2592
                if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
1✔
2593
                        // subnet.namespace.kubernetes.io/ip_address.ifName = ipAddress
×
2594
                        annoKey := perInterfaceIPAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
×
2595
                        if ipStr := pod.Annotations[annoKey]; ipStr != "" {
×
2596
                                return c.acquireStaticAddressHelper(pod, podNet, portName, macPointer, ippoolStr, nsNets, isStsPod, key, requestedIPFamily)
×
2597
                        }
×
2598
                }
2599

2600
                var skippedAddrs []string
1✔
2601
                for {
2✔
2602
                        ipv4, ipv6, mac, err := c.ipam.GetRandomAddressWithFamily(key, portName, macPointer, podNet.Subnet.Name, "", requestedIPFamily, skippedAddrs, !podNet.AllowLiveMigration)
1✔
2603
                        if err != nil {
2✔
2604
                                klog.Error(err)
1✔
2605
                                return "", "", "", podNet.Subnet, err
1✔
2606
                        }
1✔
2607
                        ipv4OK, ipv6OK, err := c.validatePodIP(pod.Name, podNet.Subnet.Name, ipv4, ipv6)
1✔
2608
                        if err != nil {
1✔
2609
                                klog.Error(err)
×
2610
                                return "", "", "", podNet.Subnet, err
×
2611
                        }
×
2612
                        if ipv4OK && ipv6OK {
2✔
2613
                                return ipv4, ipv6, mac, podNet.Subnet, nil
1✔
2614
                        }
1✔
2615

2616
                        if !ipv4OK {
×
2617
                                skippedAddrs = append(skippedAddrs, ipv4)
×
2618
                        }
×
2619
                        if !ipv6OK {
×
2620
                                skippedAddrs = append(skippedAddrs, ipv6)
×
2621
                        }
×
2622
                }
2623
        }
2624

2625
        return c.acquireStaticAddressHelper(pod, podNet, portName, macPointer, ippoolStr, nsNets, isStsPod, key, requestedIPFamily)
1✔
2626
}
2627

2628
func (c *Controller) acquireStaticAddressHelper(pod *v1.Pod, podNet *kubeovnNet, portName string, macPointer *string, ippoolStr string, nsNets []*kubeovnNet, isStsPod bool, key, requestedIPFamily string) (string, string, string, *kubeovnv1.Subnet, error) {
1✔
2629
        var v4IP, v6IP, mac string
1✔
2630
        var err error
1✔
2631

1✔
2632
        // Static allocate
1✔
2633
        if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
2✔
2634
                annotationKey := perInterfaceIPAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
1✔
2635
                if ipStr := pod.Annotations[annotationKey]; ipStr != "" {
2✔
2636
                        for _, net := range nsNets {
2✔
2637
                                v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipStr, macPointer, net.Subnet.Name, net.AllowLiveMigration, requestedIPFamily)
1✔
2638
                                if err == nil {
2✔
2639
                                        return v4IP, v6IP, mac, net.Subnet, nil
1✔
2640
                                }
1✔
2641
                        }
2642
                        return v4IP, v6IP, mac, podNet.Subnet, err
×
2643
                }
2644
        }
2645

2646
        if ipStr := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]; ipStr != "" {
2✔
2647
                for _, net := range nsNets {
2✔
2648
                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipStr, macPointer, net.Subnet.Name, net.AllowLiveMigration, requestedIPFamily)
1✔
2649
                        if err == nil {
2✔
2650
                                return v4IP, v6IP, mac, net.Subnet, nil
1✔
2651
                        }
1✔
2652
                }
2653
                return v4IP, v6IP, mac, podNet.Subnet, err
1✔
2654
        }
2655

2656
        // IPPool allocate
2657
        if ippoolStr != "" {
2✔
2658
                var ipPool []string
1✔
2659
                if strings.ContainsRune(ippoolStr, ';') {
1✔
2660
                        ipPool = strings.Split(ippoolStr, ";")
×
2661
                } else {
1✔
2662
                        ipPool = strings.Split(ippoolStr, ",")
1✔
2663
                        if len(ipPool) == 2 && util.CheckProtocol(ipPool[0]) != util.CheckProtocol(ipPool[1]) {
1✔
2664
                                ipPool = []string{ippoolStr}
×
2665
                        }
×
2666
                }
2667
                for i, ip := range ipPool {
2✔
2668
                        ipPool[i] = strings.TrimSpace(ip)
1✔
2669
                }
1✔
2670

2671
                if len(ipPool) == 1 && (!strings.ContainsRune(ipPool[0], ',') && net.ParseIP(ipPool[0]) == nil) {
2✔
2672
                        var skippedAddrs []string
1✔
2673
                        pool, err := c.ippoolLister.Get(ipPool[0])
1✔
2674
                        if err != nil {
1✔
2675
                                klog.Errorf("failed to get ippool %s: %v", ipPool[0], err)
×
2676
                                return "", "", "", podNet.Subnet, err
×
2677
                        }
×
2678
                        for {
2✔
2679
                                ipv4, ipv6, mac, err := c.ipam.GetRandomAddressWithFamily(key, portName, macPointer, pool.Spec.Subnet, ipPool[0], requestedIPFamily, skippedAddrs, !podNet.AllowLiveMigration)
1✔
2680
                                if err != nil {
1✔
2681
                                        klog.Error(err)
×
2682
                                        return "", "", "", podNet.Subnet, err
×
2683
                                }
×
2684
                                ipv4OK, ipv6OK, err := c.validatePodIP(pod.Name, podNet.Subnet.Name, ipv4, ipv6)
1✔
2685
                                if err != nil {
1✔
2686
                                        klog.Error(err)
×
2687
                                        return "", "", "", podNet.Subnet, err
×
2688
                                }
×
2689
                                if ipv4OK && ipv6OK {
2✔
2690
                                        return ipv4, ipv6, mac, podNet.Subnet, nil
1✔
2691
                                }
1✔
2692

2693
                                if !ipv4OK {
×
2694
                                        skippedAddrs = append(skippedAddrs, ipv4)
×
2695
                                }
×
2696
                                if !ipv6OK {
×
2697
                                        skippedAddrs = append(skippedAddrs, ipv6)
×
2698
                                }
×
2699
                        }
2700
                }
2701

2702
                if !isStsPod {
2✔
2703
                        for _, net := range nsNets {
2✔
2704
                                for _, staticIP := range ipPool {
2✔
2705
                                        var checkIP string
1✔
2706
                                        ipProtocol := util.CheckProtocol(staticIP)
1✔
2707
                                        if ipProtocol == kubeovnv1.ProtocolDual {
1✔
2708
                                                checkIP = strings.Split(staticIP, ",")[0]
×
2709
                                        } else {
1✔
2710
                                                checkIP = staticIP
1✔
2711
                                        }
1✔
2712

2713
                                        if assignedPod, ok := c.ipam.IsIPAssignedToOtherPod(checkIP, net.Subnet.Name, key); ok {
1✔
2714
                                                klog.Errorf("static address %s for %s has been assigned to %s", staticIP, key, assignedPod)
×
2715
                                                err = ipam.ErrConflict
×
2716
                                                continue
×
2717
                                        }
2718

2719
                                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, staticIP, macPointer, net.Subnet.Name, net.AllowLiveMigration, requestedIPFamily)
1✔
2720
                                        if err == nil {
1✔
2721
                                                return v4IP, v6IP, mac, net.Subnet, nil
×
2722
                                        }
×
2723
                                }
2724
                        }
2725
                        klog.Errorf("acquire address from ippool %s for %s failed, %v", ippoolStr, key, err)
1✔
2726
                        if err != nil {
2✔
2727
                                return "", "", "", podNet.Subnet, err
1✔
2728
                        }
1✔
2729
                } else {
×
2730
                        tempStrs := strings.Split(pod.Name, "-")
×
2731
                        numStr := tempStrs[len(tempStrs)-1]
×
2732
                        index, _ := strconv.Atoi(numStr)
×
2733

×
2734
                        if index < len(ipPool) {
×
2735
                                for _, net := range nsNets {
×
2736
                                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipPool[index], macPointer, net.Subnet.Name, net.AllowLiveMigration, requestedIPFamily)
×
2737
                                        if err == nil {
×
2738
                                                return v4IP, v6IP, mac, net.Subnet, nil
×
2739
                                        }
×
2740
                                }
2741
                                klog.Errorf("acquire address %s for %s failed, %v", ipPool[index], key, err)
×
2742
                                if err != nil {
×
2743
                                        return "", "", "", podNet.Subnet, err
×
2744
                                }
×
2745
                        }
2746
                }
2747
        }
2748
        klog.Errorf("allocate address for %s failed, return NoAvailableAddress", key)
×
2749
        return "", "", "", podNet.Subnet, ipam.ErrNoAvailable
×
2750
}
2751

2752
func (c *Controller) acquireStaticAddress(key, nicName, ip string, mac *string, subnet string, liveMigration bool, requestedIPFamily string) (string, string, string, error) {
1✔
2753
        var v4IP, v6IP, macStr string
1✔
2754
        var err error
1✔
2755
        for ipStr := range strings.SplitSeq(ip, ",") {
2✔
2756
                if net.ParseIP(ipStr) == nil {
1✔
2757
                        return "", "", "", fmt.Errorf("failed to parse IP %s", ipStr)
×
2758
                }
×
2759
        }
2760

2761
        if v4IP, v6IP, macStr, err = c.ipam.GetStaticAddressWithFamily(key, nicName, ip, mac, subnet, requestedIPFamily, !liveMigration); err != nil {
2✔
2762
                klog.Errorf("failed to get static ip %v, mac %v, subnet %v, err %v", ip, mac, subnet, err)
1✔
2763
                return "", "", "", err
1✔
2764
        }
1✔
2765
        return v4IP, v6IP, macStr, nil
1✔
2766
}
2767

2768
func appendCheckPodNetToDel(c *Controller, pod *v1.Pod, ownerRefName, ownerRefKind string) (bool, []string, error) {
1✔
2769
        // subnet for ns has been changed, and statefulset/vm pod's ip is not in the range of subnet's cidr anymore
1✔
2770
        podNs, err := c.namespacesLister.Get(pod.Namespace)
1✔
2771
        if err != nil {
1✔
2772
                klog.Errorf("failed to get namespace %s, %v", pod.Namespace, err)
×
2773
                return false, nil, err
×
2774
        }
×
2775

2776
        var ownerRefAnnotations map[string]string
1✔
2777
        switch ownerRefKind {
1✔
2778
        case util.KindStatefulSet:
1✔
2779
                ss, err := c.config.KubeClient.AppsV1().StatefulSets(pod.Namespace).Get(context.Background(), ownerRefName, metav1.GetOptions{})
1✔
2780
                if err != nil {
2✔
2781
                        if k8serrors.IsNotFound(err) {
1✔
2782
                                klog.Infof("Statefulset %s is not found", ownerRefName)
×
2783
                                return true, nil, nil
×
2784
                        }
×
2785
                        klog.Errorf("failed to get StatefulSet %s, %v", ownerRefName, err)
1✔
2786
                        return false, nil, err
1✔
2787
                }
2788
                if ss.Spec.Template.Annotations != nil {
×
2789
                        ownerRefAnnotations = ss.Spec.Template.Annotations
×
2790
                }
×
2791

2792
        case util.KindVirtualMachineInstance:
1✔
2793
                vm, err := c.config.KubevirtClient.VirtualMachine(pod.Namespace).Get(context.Background(), ownerRefName, metav1.GetOptions{})
1✔
2794
                if err != nil {
1✔
2795
                        if k8serrors.IsNotFound(err) {
×
2796
                                klog.Infof("VirtualMachine %s is not found", ownerRefName)
×
2797
                                return true, nil, nil
×
2798
                        }
×
2799
                        klog.Errorf("failed to get VirtualMachine %s, %v", ownerRefName, err)
×
2800
                        return false, nil, err
×
2801
                }
2802
                if vm.Spec.Template != nil &&
1✔
2803
                        vm.Spec.Template.ObjectMeta.Annotations != nil {
2✔
2804
                        ownerRefAnnotations = vm.Spec.Template.ObjectMeta.Annotations
1✔
2805
                }
1✔
2806
        }
2807

2808
        var ipcrToDelete []string
1✔
2809
        if defaultIPCRName := appendCheckPodNonMultusNetToDel(c, pod, ownerRefName, ownerRefAnnotations, podNs); defaultIPCRName != "" {
1✔
2810
                ipcrToDelete = append(ipcrToDelete, defaultIPCRName)
×
2811
        }
×
2812

2813
        if multusIPCRNames := appendCheckPodMultusNetToDel(c, pod, ownerRefName, ownerRefAnnotations); len(multusIPCRNames) != 0 {
1✔
2814
                ipcrToDelete = append(ipcrToDelete, multusIPCRNames...)
×
2815
        }
×
2816

2817
        return false, ipcrToDelete, nil
1✔
2818
}
2819

2820
func appendCheckPodNonMultusNetToDel(c *Controller, pod *v1.Pod, ownerRefName string, ownerRefAnnotations map[string]string, podNs *v1.Namespace) string {
1✔
2821
        podDefaultSwitch := strings.TrimSpace(pod.Annotations[util.LogicalSwitchAnnotation])
1✔
2822
        if podDefaultSwitch != "" {
2✔
2823
                ownerRefSubnet := ownerRefAnnotations[util.LogicalSwitchAnnotation]
1✔
2824
                defaultIPCRName := ovs.PodNameToPortName(ownerRefName, pod.Namespace, util.OvnProvider)
1✔
2825
                if ownerRefSubnet == "" {
1✔
2826
                        nsSubnetNames := podNs.Annotations[util.LogicalSwitchAnnotation]
×
2827
                        // check if pod use the subnet of its ns
×
2828
                        if nsSubnetNames != "" && !slices.Contains(strings.Split(nsSubnetNames, ","), podDefaultSwitch) {
×
2829
                                klog.Infof("ns %s annotation subnet is %s, which is inconstant with subnet for pod %s, delete pod", pod.Namespace, nsSubnetNames, pod.Name)
×
2830
                                return defaultIPCRName
×
2831
                        }
×
2832
                } else {
1✔
2833
                        podIP := pod.Annotations[util.IPAddressAnnotation]
1✔
2834
                        if shouldCleanPodNet(c, pod, ownerRefName, ownerRefSubnet, podDefaultSwitch, podIP) {
1✔
2835
                                return defaultIPCRName
×
2836
                        }
×
2837
                }
2838
        }
2839
        return ""
1✔
2840
}
2841

2842
func appendCheckPodMultusNetToDel(c *Controller, pod *v1.Pod, ownerRefName string, ownerRefAnnotations map[string]string) []string {
1✔
2843
        var multusIPCRNames []string
1✔
2844
        attachmentNets, _ := c.getPodAttachmentNet(pod)
1✔
2845
        for _, attachmentNet := range attachmentNets {
1✔
2846
                ipCRName := ovs.PodNameToPortName(ownerRefName, pod.Namespace, attachmentNet.ProviderName)
×
2847
                podSwitch := strings.TrimSpace(pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, attachmentNet.ProviderName)])
×
2848
                ownerRefSubnet := ownerRefAnnotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, attachmentNet.ProviderName)]
×
2849
                podIP := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, attachmentNet.ProviderName)]
×
2850
                if shouldCleanPodNet(c, pod, ownerRefName, ownerRefSubnet, podSwitch, podIP) {
×
2851
                        multusIPCRNames = append(multusIPCRNames, ipCRName)
×
2852
                }
×
2853
        }
2854
        return multusIPCRNames
1✔
2855
}
2856

2857
func shouldCleanPodNet(c *Controller, pod *v1.Pod, ownerRefName, ownerRefSubnet, podSwitch, podIP string) bool {
1✔
2858
        // subnet cidr has been changed, and statefulset pod's ip is not in the range of subnet's cidr anymore
1✔
2859
        podSubnet, err := c.subnetsLister.Get(podSwitch)
1✔
2860
        if err != nil {
1✔
2861
                if k8serrors.IsNotFound(err) {
×
2862
                        klog.Infof("subnet %s not found for pod %s/%s, not auto clean ip", podSwitch, pod.Namespace, pod.Name)
×
2863
                        return false
×
2864
                }
×
2865
                klog.Errorf("failed to get subnet %s, %v, not auto clean ip", podSwitch, err)
×
2866
                return false
×
2867
        }
2868
        if podSubnet == nil {
1✔
2869
                // TODO: remove: CRD get interface will retrun a nil subnet ?
×
2870
                klog.Errorf("pod %s/%s subnet %s is nil, not auto clean ip", pod.Namespace, pod.Name, podSwitch)
×
2871
                return false
×
2872
        }
×
2873
        if podIP == "" {
2✔
2874
                // delete pod just after it created < 1ms
1✔
2875
                klog.Infof("pod %s/%s annotaions has no ip address, not auto clean ip", pod.Namespace, pod.Name)
1✔
2876
                return false
1✔
2877
        }
1✔
2878
        podSubnetCidr := podSubnet.Spec.CIDRBlock
×
2879
        if podSubnetCidr == "" {
×
2880
                // subnet spec cidr changed by user
×
2881
                klog.Errorf("invalid pod subnet %s empty cidr %s, not auto clean ip", podSwitch, podSubnetCidr)
×
2882
                return false
×
2883
        }
×
2884
        if !util.CIDRContainIP(podSubnetCidr, podIP) {
×
2885
                klog.Infof("pod's ip %s is not in the range of subnet %s, delete pod", podIP, podSubnet.Name)
×
2886
                return true
×
2887
        }
×
2888
        // subnet of ownerReference(sts/vm) has been changed, it needs to handle delete pod and create port on the new logical switch
2889
        if ownerRefSubnet != "" && podSubnet.Name != ownerRefSubnet {
×
2890
                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)
×
2891
                return true
×
2892
        }
×
2893

2894
        return false
×
2895
}
2896

2897
// getVMOrphanedAttachmentPorts finds OVN ports that belong to the VM but are
2898
// no longer desired by the VM's current spec (e.g., KubeVirt NAD hotplug).
2899
// It compares OVN's actual ports (existingPorts) against the VM spec's desired
2900
// ports, rather than relying on pod annotations which may be stale or incomplete.
2901
func (c *Controller) getVMOrphanedAttachmentPorts(namespace, vmName string, existingPorts []ovnnb.LogicalSwitchPort) map[string]bool {
1✔
2902
        vm, err := c.config.KubevirtClient.VirtualMachine(namespace).Get(context.Background(), vmName, metav1.GetOptions{})
1✔
2903
        if err != nil {
1✔
2904
                klog.Errorf("failed to get vm %s/%s for orphaned port detection: %v", namespace, vmName, err)
×
2905
                return nil
×
2906
        }
×
2907

2908
        if vm.Spec.Template == nil {
1✔
2909
                return nil
×
2910
        }
×
2911

2912
        // Build expected port names from VM spec in a single pass (pattern from gc.go:1108-1145)
2913
        expectedPorts := make(map[string]bool)
1✔
2914
        defaultMultus := false
1✔
2915
        hasMultusNetwork := false
1✔
2916
        for _, network := range vm.Spec.Template.Spec.Networks {
2✔
2917
                if network.Multus == nil {
1✔
2918
                        continue
×
2919
                }
2920
                if network.Multus.Default {
1✔
2921
                        defaultMultus = true
×
2922
                }
×
2923
                if network.Multus.NetworkName != "" {
2✔
2924
                        hasMultusNetwork = true
1✔
2925
                        items := strings.Split(network.Multus.NetworkName, "/")
1✔
2926
                        if len(items) != 2 {
1✔
2927
                                items = []string{namespace, items[0]}
×
2928
                        }
×
2929
                        provider := fmt.Sprintf("%s.%s.%s", items[1], items[0], util.OvnProvider)
1✔
2930
                        expectedPorts[ovs.PodNameToPortName(vmName, namespace, provider)] = true
1✔
2931
                }
2932
        }
2933
        if !defaultMultus {
2✔
2934
                expectedPorts[ovs.PodNameToPortName(vmName, namespace, util.OvnProvider)] = true
1✔
2935
        }
1✔
2936

2937
        // If VM spec has no multus networks, skip detection to avoid
2938
        // false positives for VMs that only use template annotations.
2939
        if !hasMultusNetwork {
1✔
2940
                return nil
×
2941
        }
×
2942

2943
        // Find orphaned: ports in OVN but not in expected
2944
        orphanedPorts := make(map[string]bool)
1✔
2945
        for _, port := range existingPorts {
2✔
2946
                if !expectedPorts[port.Name] {
2✔
2947
                        klog.Infof("OVN port %s for vm %s/%s is not in VM spec, marking as orphaned",
1✔
2948
                                port.Name, namespace, vmName)
1✔
2949
                        orphanedPorts[port.Name] = true
1✔
2950
                }
1✔
2951
        }
2952

2953
        if len(orphanedPorts) == 0 {
1✔
2954
                return nil
×
2955
        }
×
2956
        return orphanedPorts
1✔
2957
}
2958

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

×
2966
        // List existing LSPs first (cheap OVN query) to bail out early if none exist
×
2967
        ports, err := c.OVNNbClient.ListNormalLogicalSwitchPorts(true, map[string]string{"pod": podKey})
×
2968
        if err != nil {
×
2969
                klog.Errorf("failed to list lsps of vm %s for stale cleanup: %v", podKey, err)
×
2970
                return
×
2971
        }
×
2972
        if len(ports) == 0 {
×
2973
                return
×
2974
        }
×
2975

2976
        // Build current port names from the pod's full network list
2977
        podNets, err := c.getPodKubeovnNets(pod)
×
2978
        if err != nil {
×
2979
                klog.Errorf("failed to get kube-ovn nets of pod %s for stale cleanup: %v", podKey, err)
×
2980
                return
×
2981
        }
×
2982
        currentPorts := make(map[string]bool, len(podNets)+1)
×
2983
        for _, podNet := range podNets {
×
2984
                currentPorts[ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)] = true
×
2985
        }
×
2986
        currentPorts[ovs.PodNameToPortName(podName, pod.Namespace, util.OvnProvider)] = true
×
2987

×
2988
        for _, port := range ports {
×
2989
                if currentPorts[port.Name] {
×
2990
                        continue
×
2991
                }
2992
                klog.Infof("cleaning stale vm attachment lsp %s (not in current pod networks)", port.Name)
×
2993
                if err := c.OVNNbClient.DeleteLogicalSwitchPort(port.Name); err != nil {
×
2994
                        klog.Errorf("failed to delete stale lsp %s, skipping IP cleanup to avoid inconsistency: %v", port.Name, err)
×
2995
                        continue
×
2996
                }
2997

2998
                ipCR, err := c.ipsLister.Get(port.Name)
×
2999
                if err != nil {
×
3000
                        if !k8serrors.IsNotFound(err) {
×
3001
                                klog.Errorf("failed to get ip %s: %v", port.Name, err)
×
3002
                        }
×
3003
                        continue
×
3004
                }
3005
                if ipCR.Labels[util.IPReservedLabel] != "true" {
×
3006
                        klog.Infof("deleting stale vm attachment ip CR %s", ipCR.Name)
×
3007
                        if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), ipCR.Name, metav1.DeleteOptions{}); err != nil {
×
3008
                                if !k8serrors.IsNotFound(err) {
×
3009
                                        klog.Errorf("failed to delete ip %s: %v", ipCR.Name, err)
×
3010
                                }
×
3011
                        }
3012
                        if subnetName := ipCR.Spec.Subnet; subnetName != "" {
×
3013
                                c.ipam.ReleaseAddressByNic(podKey, port.Name, subnetName)
×
3014
                                c.updateSubnetStatusQueue.Add(subnetName)
×
3015
                        }
×
3016
                }
3017
        }
3018
}
3019

3020
func isVMPod(pod *v1.Pod) (bool, string) {
1✔
3021
        for _, owner := range pod.OwnerReferences {
2✔
3022
                // The name of vmi is consistent with vm's name.
1✔
3023
                if owner.Kind == util.KindVirtualMachineInstance &&
1✔
3024
                        strings.HasPrefix(owner.APIVersion, kubevirtv1.SchemeGroupVersion.Group+"/") {
2✔
3025
                        return true, owner.Name
1✔
3026
                }
1✔
3027
        }
3028
        return false, ""
1✔
3029
}
3030

3031
// hasAliveSiblingVMPod reports whether pods contains any alive virt-launcher
3032
// pod owned by the VMI vmName, excluding the pod with name excludePodName.
3033
// It is used to decide whether a VM LSP's port-group memberships are still
3034
// owned by another running sibling (e.g. a live-migration destination) when a
3035
// completed/GC'd source pod is being processed.
3036
func hasAliveSiblingVMPod(pods []*v1.Pod, vmName, excludePodName string) bool {
1✔
3037
        for _, p := range pods {
2✔
3038
                if p == nil || p.Name == excludePodName {
2✔
3039
                        continue
1✔
3040
                }
3041
                isVM, name := isVMPod(p)
1✔
3042
                if !isVM || name != vmName {
2✔
3043
                        continue
1✔
3044
                }
3045
                if isPodAlive(p) {
2✔
3046
                        return true
1✔
3047
                }
1✔
3048
        }
3049
        return false
1✔
3050
}
3051

3052
func isOwnsByTheVM(vmi metav1.Object) (bool, string) {
1✔
3053
        for _, owner := range vmi.GetOwnerReferences() {
1✔
3054
                if owner.Kind == util.KindVirtualMachine &&
×
3055
                        strings.HasPrefix(owner.APIVersion, kubevirtv1.SchemeGroupVersion.Group+"/") {
×
3056
                        return true, owner.Name
×
3057
                }
×
3058
        }
3059
        return false, ""
1✔
3060
}
3061

3062
func (c *Controller) isVMToDel(pod *v1.Pod, vmiName string) bool {
1✔
3063
        var (
1✔
3064
                vmiAlive bool
1✔
3065
                vmName   string
1✔
3066
        )
1✔
3067
        // The vmi is also deleted when pod is deleted, only left vm exists.
1✔
3068
        vmi, err := c.config.KubevirtClient.VirtualMachineInstance(pod.Namespace).Get(context.Background(), vmiName, metav1.GetOptions{})
1✔
3069
        if err != nil {
1✔
3070
                if k8serrors.IsNotFound(err) {
×
3071
                        vmiAlive = false
×
3072
                        // The name of vmi is consistent with vm's name.
×
3073
                        vmName = vmiName
×
3074
                        klog.ErrorS(err, "failed to get vmi, will try to get the vm directly", "name", vmiName)
×
3075
                } else {
×
3076
                        klog.ErrorS(err, "failed to get vmi", "name", vmiName)
×
3077
                        return false
×
3078
                }
×
3079
        } else {
1✔
3080
                var ownsByVM bool
1✔
3081
                ownsByVM, vmName = isOwnsByTheVM(vmi)
1✔
3082
                if !ownsByVM && !vmi.DeletionTimestamp.IsZero() {
1✔
3083
                        klog.Infof("ephemeral vmi %s is deleting", vmiName)
×
3084
                        return true
×
3085
                }
×
3086
                vmiAlive = vmi.DeletionTimestamp.IsZero()
1✔
3087
        }
3088

3089
        if vmiAlive {
2✔
3090
                return false
1✔
3091
        }
1✔
3092

3093
        vm, err := c.config.KubevirtClient.VirtualMachine(pod.Namespace).Get(context.Background(), vmName, metav1.GetOptions{})
×
3094
        if err != nil {
×
3095
                // the vm has gone
×
3096
                if k8serrors.IsNotFound(err) {
×
3097
                        klog.ErrorS(err, "failed to get vm", "name", vmName)
×
3098
                        return true
×
3099
                }
×
3100
                klog.ErrorS(err, "failed to get vm", "name", vmName)
×
3101
                return false
×
3102
        }
3103

3104
        if !vm.DeletionTimestamp.IsZero() {
×
3105
                klog.Infof("vm %s is deleting", vmName)
×
3106
                return true
×
3107
        }
×
3108
        return false
×
3109
}
3110

3111
func (c *Controller) getNameByPod(pod *v1.Pod) string {
1✔
3112
        if c.config.EnableKeepVMIP {
2✔
3113
                if isVMPod, vmName := isVMPod(pod); isVMPod {
2✔
3114
                        return vmName
1✔
3115
                }
1✔
3116
        }
3117
        return pod.Name
1✔
3118
}
3119

3120
// 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.
3121
func (c *Controller) getNsAvailableSubnets(pod *v1.Pod, podNet *kubeovnNet) ([]*kubeovnNet, error) {
1✔
3122
        // keep the annotation subnet of the pod in first position
1✔
3123
        result := []*kubeovnNet{podNet}
1✔
3124

1✔
3125
        ns, err := c.namespacesLister.Get(pod.Namespace)
1✔
3126
        if err != nil {
1✔
3127
                klog.Errorf("failed to get namespace %s, %v", pod.Namespace, err)
×
3128
                return nil, err
×
3129
        }
×
3130
        if ns.Annotations == nil {
1✔
3131
                return []*kubeovnNet{}, nil
×
3132
        }
×
3133

3134
        subnetNames := ns.Annotations[util.LogicalSwitchAnnotation]
1✔
3135
        for subnetName := range strings.SplitSeq(subnetNames, ",") {
2✔
3136
                if subnetName == "" || subnetName == podNet.Subnet.Name {
2✔
3137
                        continue
1✔
3138
                }
3139
                subnet, err := c.subnetsLister.Get(subnetName)
1✔
3140
                if err != nil {
1✔
3141
                        klog.Errorf("failed to get subnet %v", err)
×
3142
                        return nil, err
×
3143
                }
×
3144

3145
                result = append(result, &kubeovnNet{
1✔
3146
                        Type:         providerTypeOriginal,
1✔
3147
                        ProviderName: subnet.Spec.Provider,
1✔
3148
                        Subnet:       subnet,
1✔
3149
                })
1✔
3150
        }
3151

3152
        return result, nil
1✔
3153
}
3154

3155
func getPodType(pod *v1.Pod) string {
1✔
3156
        if ok, _, _ := isStatefulSetPod(pod); ok {
1✔
3157
                return util.KindStatefulSet
×
3158
        }
×
3159

3160
        if isVMPod, _ := isVMPod(pod); isVMPod {
1✔
3161
                return util.KindVirtualMachine
×
3162
        }
×
3163
        return ""
1✔
3164
}
3165

3166
func (c *Controller) getVirtualIPs(pod *v1.Pod, podNets []*kubeovnNet) map[string]string {
1✔
3167
        vipsListMap := make(map[string][]string)
1✔
3168
        var vipNamesList []string
1✔
3169
        for vipName := range strings.SplitSeq(strings.TrimSpace(pod.Annotations[util.AAPsAnnotation]), ",") {
2✔
3170
                if vipName = strings.TrimSpace(vipName); vipName == "" {
2✔
3171
                        continue
1✔
3172
                }
3173
                if !slices.Contains(vipNamesList, vipName) {
×
3174
                        vipNamesList = append(vipNamesList, vipName)
×
3175
                } else {
×
3176
                        continue
×
3177
                }
3178
                vip, err := c.virtualIpsLister.Get(vipName)
×
3179
                if err != nil {
×
3180
                        klog.Errorf("failed to get vip %s, %v", vipName, err)
×
3181
                        continue
×
3182
                }
3183
                if vip.Spec.Namespace != pod.Namespace || (vip.Status.V4ip == "" && vip.Status.V6ip == "") {
×
3184
                        continue
×
3185
                }
3186
                for _, podNet := range podNets {
×
3187
                        if podNet.Subnet.Name == vip.Spec.Subnet {
×
3188
                                key := fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)
×
3189
                                vipsList := vipsListMap[key]
×
3190
                                if vipsList == nil {
×
3191
                                        vipsList = []string{}
×
3192
                                }
×
3193
                                // ipam will ensure the uniqueness of VIP
3194
                                if util.IsValidIP(vip.Status.V4ip) {
×
3195
                                        vipsList = append(vipsList, vip.Status.V4ip)
×
3196
                                }
×
3197
                                if util.IsValidIP(vip.Status.V6ip) {
×
3198
                                        vipsList = append(vipsList, vip.Status.V6ip)
×
3199
                                }
×
3200

3201
                                vipsListMap[key] = vipsList
×
3202
                        }
3203
                }
3204
        }
3205

3206
        for _, podNet := range podNets {
2✔
3207
                vipStr := pod.Annotations[fmt.Sprintf(util.PortVipAnnotationTemplate, podNet.ProviderName)]
1✔
3208
                if vipStr == "" {
2✔
3209
                        continue
1✔
3210
                }
3211
                key := fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)
1✔
3212
                vipsList := vipsListMap[key]
1✔
3213
                if vipsList == nil {
2✔
3214
                        vipsList = []string{}
1✔
3215
                }
1✔
3216

3217
                for vip := range strings.SplitSeq(vipStr, ",") {
2✔
3218
                        if util.IsValidIP(vip) && !slices.Contains(vipsList, vip) {
2✔
3219
                                vipsList = append(vipsList, vip)
1✔
3220
                        }
1✔
3221
                }
3222

3223
                vipsListMap[key] = vipsList
1✔
3224
        }
3225

3226
        vipsMap := make(map[string]string)
1✔
3227
        for key, vipsList := range vipsListMap {
2✔
3228
                vipsMap[key] = strings.Join(vipsList, ",")
1✔
3229
        }
1✔
3230
        return vipsMap
1✔
3231
}
3232

3233
// Check if pod is a VPC NAT gateway using pod annotations
3234
func (c *Controller) checkIsPodVpcNatGw(pod *v1.Pod) (bool, string) {
1✔
3235
        if pod == nil {
2✔
3236
                return false, ""
1✔
3237
        }
1✔
3238
        if pod.Annotations == nil {
2✔
3239
                return false, ""
1✔
3240
        }
1✔
3241
        vpcGwName, isVpcNatGw := pod.Annotations[util.VpcNatGatewayAnnotation]
1✔
3242
        if isVpcNatGw {
2✔
3243
                if vpcGwName == "" {
2✔
3244
                        klog.Errorf("pod %s is vpc nat gateway but name is empty", pod.Name)
1✔
3245
                        return false, ""
1✔
3246
                }
1✔
3247
                klog.Infof("pod %s is vpc nat gateway %s", pod.Name, vpcGwName)
1✔
3248
        }
3249
        return isVpcNatGw, vpcGwName
1✔
3250
}
3251

3252
func natGwNameFromStatefulSetOwner(pod *v1.Pod) string {
1✔
3253
        isStsPod, stsName, _ := isStatefulSetPod(pod)
1✔
3254
        if !isStsPod {
1✔
3255
                return ""
×
3256
        }
×
3257

3258
        prefix := util.VpcNatGwNamePrefix + "-"
1✔
3259
        if !strings.HasPrefix(stsName, prefix) {
1✔
3260
                return ""
×
3261
        }
×
3262
        return strings.TrimPrefix(stsName, prefix)
1✔
3263
}
3264

3265
func (c *Controller) backfillVpcNatGwLanIPFromPod(pod *v1.Pod, gwName string) error {
1✔
3266
        if pod == nil {
1✔
3267
                return nil
×
3268
        }
×
3269

3270
        ownerGwName := natGwNameFromStatefulSetOwner(pod)
1✔
3271
        if ownerGwName == "" {
1✔
3272
                return nil
×
3273
        }
×
3274
        if gwName == "" {
2✔
3275
                gwName = ownerGwName
1✔
3276
        }
1✔
3277
        // Use owner reference as a guard to avoid patching unrelated pods carrying a stale annotation.
3278
        if ownerGwName != gwName {
1✔
3279
                klog.Warningf("skip backfill for pod %s/%s: gw annotation %q does not match owner statefulset %q",
×
3280
                        pod.Namespace, pod.Name, gwName, ownerGwName)
×
3281
                return nil
×
3282
        }
×
3283

3284
        var (
1✔
3285
                gw  *kubeovnv1.VpcNatGateway
1✔
3286
                err error
1✔
3287
        )
1✔
3288
        if c.vpcNatGatewayLister != nil {
2✔
3289
                gw, err = c.vpcNatGatewayLister.Get(gwName)
1✔
3290
        } else {
1✔
3291
                gw, err = c.config.KubeOvnClient.KubeovnV1().VpcNatGateways().Get(context.Background(), gwName, metav1.GetOptions{})
×
3292
        }
×
3293
        if err != nil {
1✔
3294
                if k8serrors.IsNotFound(err) {
×
3295
                        return nil
×
3296
                }
×
3297
                return err
×
3298
        }
3299
        if gw.Spec.LanIP != "" || gw.Spec.Replicas > 1 {
2✔
3300
                return nil
1✔
3301
        }
1✔
3302

3303
        subnet, err := c.subnetsLister.Get(gw.Spec.Subnet)
1✔
3304
        if err != nil {
1✔
3305
                return fmt.Errorf("failed to get subnet %s: %w", gw.Spec.Subnet, err)
×
3306
        }
×
3307
        if !isOvnSubnet(subnet) {
1✔
3308
                return fmt.Errorf("subnet %s is not an OVN subnet", gw.Spec.Subnet)
×
3309
        }
×
3310
        provider := subnet.Spec.Provider
1✔
3311

1✔
3312
        lanIP := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, provider)]
1✔
3313
        v4IP, v6IP := util.SplitStringIP(lanIP)
1✔
3314
        switch subnet.Spec.Protocol {
1✔
3315
        case kubeovnv1.ProtocolIPv6:
1✔
3316
                lanIP = v6IP
1✔
3317
        case kubeovnv1.ProtocolIPv4:
1✔
3318
                lanIP = v4IP
1✔
3319
        case kubeovnv1.ProtocolDual:
×
3320
                if v4IP != "" {
×
3321
                        lanIP = v4IP
×
3322
                } else {
×
3323
                        lanIP = v6IP
×
3324
                }
×
3325
        default:
×
3326
                lanIP = v4IP
×
3327
        }
3328
        if lanIP == "" || net.ParseIP(lanIP) == nil {
2✔
3329
                return nil
1✔
3330
        }
1✔
3331

3332
        patchPayload := map[string]any{
1✔
3333
                "spec": map[string]string{
1✔
3334
                        "lanIp": lanIP,
1✔
3335
                },
1✔
3336
        }
1✔
3337
        raw, err := json.Marshal(patchPayload)
1✔
3338
        if err != nil {
1✔
3339
                return err
×
3340
        }
×
3341

3342
        _, err = c.config.KubeOvnClient.KubeovnV1().VpcNatGateways().Patch(context.Background(),
1✔
3343
                gw.Name, types.MergePatchType, raw, metav1.PatchOptions{})
1✔
3344
        if err != nil {
1✔
3345
                return err
×
3346
        }
×
3347
        klog.Infof("backfilled vpc nat gateway %s spec.lanIP with pod %s/%s ip %s", gw.Name, pod.Namespace, pod.Name, lanIP)
1✔
3348
        return nil
1✔
3349
}
3350

3351
func perInterfaceIPAnnotationKey(nadName, nadNamespace, ifaceName string) string {
1✔
3352
        return fmt.Sprintf("%s.%s.kubernetes.io/ip_address.%s", nadName, nadNamespace, ifaceName)
1✔
3353
}
1✔
3354

3355
func perInterfaceMACAnnotationKey(nadName, nadNamespace, ifaceName string) string {
×
3356
        return fmt.Sprintf("%s.%s.kubernetes.io/mac_address.%s", nadName, nadNamespace, ifaceName)
×
3357
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc