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

kubeovn / kube-ovn / 29626812688

18 Jul 2026 02:18AM UTC coverage: 25.826% (+0.06%) from 25.762%
29626812688

push

github

oilbeater
fix(chart): correct ovs-ipsec-keys volume indentation and Talos IPSEC docs (#7041)

* fix(chart): correct ovs-ipsec-keys volume indentation

The ovs-ipsec-keys entry in the agent DaemonSet volumes list is indented
two spaces deeper than its siblings, so it renders as a nested mapping of
the preceding volume instead of a new list item. The result is invalid
YAML and the chart fails to render whenever features.enableOvnIpsec is
enabled:

  Error: YAML parse error on kube-ovn-v2/templates/agent/agent-daemonset.yaml:
  error converting YAML to JSON: yaml: line 263: did not find expected key

The matching volumeMounts entry is already correct; only the volumes
entry is affected. The flag defaults to false and no CI job sets it,
which is why the chart shipped in this state.

Signed-off-by: Aleksei Sviridkin <f@lex.la>

* docs(chart): document the IPSEC key directory for Talos Linux

Both charts describe a Talos install but list only two of the three host
directories that must be moved off the read-only /etc filesystem. The
IPSEC key directory was added later and kept the same /etc/origin
default, so following the documented instructions and then enabling IPSEC
leaves kube-ovn-cni in CreateContainerError:

  failed to mkdir "/etc/origin/ovs_ipsec_keys": mkdir /etc/origin:
  read-only file system

The directory is configurable, so only the documented value list needs
completing; no default changes here.

Also annotate ovsIpsecKeysDirectory with the section its two neighbours
already declare, so helm-docs groups it with them instead of listing it
under Other Values.

Signed-off-by: Aleksei Sviridkin <f@lex.la>

---------

Signed-off-by: Aleksei Sviridkin <f@lex.la>
(cherry picked from commit db1e2d00a)

14764 of 57168 relevant lines covered (25.83%)

0.3 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

490
        return podNets, nil
1✔
491
}
492

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

827
        return pod, nil
×
828
}
829

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1454
// 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"}]'
1455
// there is no mac or ip address request here in the object generated from here
1456
// interface name in providerName is used for annotation for ip address
1457

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

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

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

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

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

1500
        if len(portsNeedToDel) == 0 && len(annotationsNeedToAdd) == 0 {
×
1501
                return pod, nil
×
1502
        }
×
1503

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

1519
        patch := util.KVPatch{}
×
1520
        for _, providerName := range annotationsNeedToDel {
×
1521
                for key := range pod.Annotations {
×
1522
                        if strings.HasPrefix(key, providerName) {
×
1523
                                patch[key] = nil
×
1524
                        }
×
1525
                }
1526
        }
1527

1528
        for key, value := range annotationsNeedToAdd {
×
1529
                patch[key] = value
×
1530
        }
×
1531

1532
        if len(patch) == 0 {
×
1533
                return pod, nil
×
1534
        }
×
1535

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

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

1552
        return pod, nil
×
1553
}
1554

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

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

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

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

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

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

1636
        // the statefulset is down scaled and the pod is not alive
1637

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

1663
        return false
×
1664
}
1665

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

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

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

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

1700
        if pod.Annotations == nil {
×
1701
                return nets
×
1702
        }
×
1703

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

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

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

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

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

1767
func needRouteSubnets(pod *v1.Pod, nets []*kubeovnNet) []*kubeovnNet {
×
1768
        if !isPodAlive(pod) {
×
1769
                return nil
×
1770
        }
×
1771

1772
        if pod.Annotations == nil {
×
1773
                return nets
×
1774
        }
×
1775

1776
        result := make([]*kubeovnNet, 0, len(nets))
×
1777
        for _, n := range nets {
×
1778
                if !isOvnSubnet(n.Subnet) {
×
1779
                        continue
×
1780
                }
1781

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

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

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

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

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

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

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

1875
        return false
×
1876
}
1877

1878
func (c *Controller) checkIPsInExcludeList(ips string, excludeIPs []string, cidr string) bool {
×
1879
        expandedExcludeIPs := util.ExpandExcludeIPs(excludeIPs, cidr)
×
1880

×
1881
        for ipAddr := range strings.SplitSeq(strings.TrimSpace(ips), ",") {
×
1882
                ipAddr = strings.TrimSpace(ipAddr)
×
1883
                if ipAddr == "" {
×
1884
                        continue
×
1885
                }
1886

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

1897
type providerType int
1898

1899
const (
1900
        providerTypeIPAM providerType = iota
1901
        providerTypeOriginal
1902
)
1903

1904
type kubeovnNet struct {
1905
        Type               providerType
1906
        ProviderName       string
1907
        Subnet             *kubeovnv1.Subnet
1908
        IsDefault          bool
1909
        AllowLiveMigration bool
1910
        IPRequest          string
1911
        MacRequest         string
1912
        NadName            string
1913
        NadNamespace       string
1914
        InterfaceName      string
1915
}
1916

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

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

1944
        // ignore to return all existing subnets to clean its ip crd
1945
        ignoreSubnetNotExist := !pod.DeletionTimestamp.IsZero()
1✔
1946

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

1952
        result := make([]*kubeovnNet, 0, len(multusNets))
1✔
1953
        for _, attach := range multusNets {
2✔
1954
                nadKey := fmt.Sprintf("%s/%s", attach.Namespace, attach.Name)
1✔
1955
                network, err := c.netAttachLister.NetworkAttachmentDefinitions(attach.Namespace).Get(attach.Name)
1✔
1956
                if err != nil {
2✔
1957
                        klog.Errorf("failed to get net-attach-def %s, %v", attach.Name, err)
1✔
1958
                        if k8serrors.IsNotFound(err) && ignoreSubnetNotExist {
2✔
1959
                                // NAD deleted before pod, find subnet for cleanup
1✔
1960
                                providerName := fmt.Sprintf("%s.%s.%s", attach.Name, attach.Namespace, util.OvnProvider)
1✔
1961
                                // k8s.v1.cni.cncf.io/networks: '[{"name": "vm-overlay", "namespace": "default", "interface": "net1"}, {"name": "vm-overlay", "namespace": "default", "interface": "net2"}]'
1✔
1962
                                // 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✔
1963
                                if nadCounts[nadKey] > 1 && attach.InterfaceRequest != "" {
1✔
1964
                                        providerName = fmt.Sprintf("%s.%s", providerName, attach.InterfaceRequest)
×
1965
                                }
×
1966

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

1✔
1973
                                // if interface name is provided this means providerName will contain interface name
1✔
1974
                                // say vm-overlay.default.ovn.net1 which will not match the `provider` definition in the config object
1✔
1975
                                // for each interface then we need annotation
1✔
1976
                                // vm-overlay.default.ovn.net1.kubernetes.io/logical_switch = "subnetName" to allow it to identify correct switch to be associated with this interface
1✔
1977
                                // interfaceName is included in the name
1✔
1978
                                subnetName := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, providerName)]
1✔
1979
                                if subnetName == "" {
2✔
1980
                                        for _, subnet := range subnets {
2✔
1981
                                                if subnet.Spec.Provider == providerName || subnet.Spec.Provider == ipamProviderName {
2✔
1982
                                                        subnetName = subnet.Name
1✔
1983
                                                        // use the subnet's real provider so the released IP CR name
1✔
1984
                                                        // (pod.namespace.provider) matches what was allocated
1✔
1985
                                                        providerName = subnet.Spec.Provider
1✔
1986
                                                        break
1✔
1987
                                                }
1988
                                        }
1989
                                }
1990

1991
                                if subnetName == "" {
1✔
1992
                                        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)
×
1993
                                        continue
×
1994
                                }
1995

1996
                                subnet, err := c.subnetsLister.Get(subnetName)
1✔
1997
                                if err != nil {
1✔
1998
                                        klog.Errorf("failed to get subnet %s, %v", subnetName, err)
×
1999
                                        if k8serrors.IsNotFound(err) {
×
2000
                                                klog.Errorf("deleting pod %s/%s attach subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
2001
                                                continue
×
2002
                                        }
2003
                                        return nil, err
×
2004
                                }
2005

2006
                                klog.Infof("pod %s/%s net-attach-def %s not found, using subnet %s for cleanup", pod.Namespace, pod.Name, attach.Name, subnetName)
1✔
2007
                                result = append(result, &kubeovnNet{
1✔
2008
                                        Type:          providerTypeIPAM,
1✔
2009
                                        ProviderName:  providerName,
1✔
2010
                                        Subnet:        subnet,
1✔
2011
                                        IsDefault:     util.IsDefaultNet(pod.Annotations[util.DefaultNetworkAnnotation], attach),
1✔
2012
                                        NadName:       attach.Name,
1✔
2013
                                        NadNamespace:  attach.Namespace,
1✔
2014
                                        InterfaceName: attach.InterfaceRequest,
1✔
2015
                                })
1✔
2016
                                continue
1✔
2017
                        }
2018
                        return nil, err
×
2019
                }
2020

2021
                if network.Spec.Config == "" {
1✔
2022
                        continue
×
2023
                }
2024

2025
                netCfg, err := loadNetConf([]byte(network.Spec.Config))
1✔
2026
                if err != nil {
1✔
2027
                        klog.Errorf("failed to load config of net-attach-def %s, %v", attach.Name, err)
×
2028
                        return nil, err
×
2029
                }
×
2030

2031
                // allocate kubeovn network
2032
                var providerName string
1✔
2033
                if util.IsOvnNetwork(netCfg) {
2✔
2034
                        allowLiveMigration := false
1✔
2035
                        isDefault := util.IsDefaultNet(pod.Annotations[util.DefaultNetworkAnnotation], attach)
1✔
2036

1✔
2037
                        // same logic as above we adding ifName in providerName
1✔
2038

1✔
2039
                        // vm-overlay.default.ovn
1✔
2040
                        providerName = fmt.Sprintf("%s.%s.%s", attach.Name, attach.Namespace, util.OvnProvider)
1✔
2041
                        if nadCounts[nadKey] > 1 && attach.InterfaceRequest != "" {
1✔
2042
                                // due to interface name in request this becomes
×
2043
                                // vm-overlay.default.ovn.net1
×
2044
                                providerName = fmt.Sprintf("%s.%s", providerName, attach.InterfaceRequest)
×
2045
                        }
×
2046
                        if pod.Annotations[kubevirtv1.MigrationJobNameAnnotation] != "" {
1✔
2047
                                allowLiveMigration = true
×
2048
                        }
×
2049

2050
                        // 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
2051
                        // key is vm-overlay.default.ovn.net1.kubernetes.io/logical_switch: vm-overlay
2052
                        subnetName := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, providerName)]
1✔
2053

1✔
2054
                        // 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✔
2055
                        subnetMatches := func(subnet *kubeovnv1.Subnet, providerName, ifName string) bool {
2✔
2056
                                var subnetProviderName string
1✔
2057
                                // if providerName contains ifName, then we trim it from the providerName to match with subnet spec
1✔
2058
                                subnetProviderName, _ = strings.CutSuffix(providerName, "."+ifName)
1✔
2059
                                klog.Infof("subnet %s, subnet provider %s, providername %s, trimmed subnetprovider %s, ifName %s", subnet.Name, subnet.Spec.Provider, providerName, subnetProviderName, ifName)
1✔
2060

1✔
2061
                                if subnet.Spec.Provider == subnetProviderName {
1✔
2062
                                        klog.Infof("matched to subnet %s", subnet.Name)
×
2063
                                        return true
×
2064
                                }
×
2065

2066
                                return false
1✔
2067
                        }
2068
                        if subnetName == "" {
2✔
2069
                                // when ifName is provided in request, the `provider` in subnet spec will not match
1✔
2070
                                // since it does not contain substring for interface name and wrong subnet will be selected
1✔
2071
                                for _, subnet := range subnets {
2✔
2072
                                        if subnetMatches(subnet, providerName, attach.InterfaceRequest) {
1✔
2073
                                                subnetName = subnet.Name
×
2074
                                                break
×
2075
                                        }
2076
                                }
2077
                        }
2078
                        klog.V(5).Infof("found subnet %s for provider %s", subnetName, providerName)
1✔
2079
                        var subnet *kubeovnv1.Subnet
1✔
2080
                        if subnetName == "" {
2✔
2081
                                err = fmt.Errorf("provider %s is not bound to any subnet", providerName)
1✔
2082
                                if ignoreSubnetNotExist {
2✔
2083
                                        klog.Errorf("deleting pod %s/%s attach %s %v, gc will clean its ip cr", pod.Namespace, pod.Name, attach.Name, err)
1✔
2084
                                        continue
1✔
2085
                                }
2086
                                klog.Error(err)
1✔
2087
                                return nil, err
1✔
2088
                        }
2089
                        subnet, err = c.subnetsLister.Get(subnetName)
1✔
2090
                        if err != nil {
1✔
2091
                                klog.Errorf("failed to get subnet %s, %v", subnetName, err)
×
2092
                                if k8serrors.IsNotFound(err) {
×
2093
                                        if ignoreSubnetNotExist {
×
2094
                                                klog.Errorf("deleting pod %s/%s attach subnet %s already not exist, gc will clean its ip cr", pod.Namespace, pod.Name, subnetName)
×
2095
                                                // just continue to next attach subnet
×
2096
                                                // ip name is unique, so it is ok if the other subnet release it
×
2097
                                                continue
×
2098
                                        }
2099
                                }
2100
                                return nil, err
×
2101
                        }
2102

2103
                        // we send no macrequest or ip request so these should be empty in the response here
2104
                        ret := &kubeovnNet{
1✔
2105
                                Type:               providerTypeOriginal,
1✔
2106
                                ProviderName:       providerName,
1✔
2107
                                Subnet:             subnet,
1✔
2108
                                IsDefault:          isDefault,
1✔
2109
                                AllowLiveMigration: allowLiveMigration,
1✔
2110
                                MacRequest:         attach.MacRequest,
1✔
2111
                                IPRequest:          strings.Join(attach.IPRequest, ","),
1✔
2112
                                NadName:            attach.Name,
1✔
2113
                                NadNamespace:       attach.Namespace,
1✔
2114
                                InterfaceName:      attach.InterfaceRequest,
1✔
2115
                        }
1✔
2116
                        result = append(result, ret)
1✔
2117
                } else {
×
2118
                        providerName = fmt.Sprintf("%s.%s", attach.Name, attach.Namespace)
×
2119
                        for _, subnet := range subnets {
×
2120
                                if subnet.Spec.Provider == providerName {
×
2121
                                        result = append(result, &kubeovnNet{
×
2122
                                                Type:          providerTypeIPAM,
×
2123
                                                ProviderName:  providerName,
×
2124
                                                Subnet:        subnet,
×
2125
                                                MacRequest:    attach.MacRequest,
×
2126
                                                IPRequest:     strings.Join(attach.IPRequest, ","),
×
2127
                                                NadName:       attach.Name,
×
2128
                                                NadNamespace:  attach.Namespace,
×
2129
                                                InterfaceName: attach.InterfaceRequest,
×
2130
                                        })
×
2131
                                        break
×
2132
                                }
2133
                        }
2134
                }
2135
        }
2136
        return result, nil
1✔
2137
}
2138

2139
func (c *Controller) validatePodIP(podName, subnetName, ipv4, ipv6 string) (bool, bool, error) {
×
2140
        subnet, err := c.subnetsLister.Get(subnetName)
×
2141
        if err != nil {
×
2142
                klog.Errorf("failed to get subnet %s: %v", subnetName, err)
×
2143
                return false, false, err
×
2144
        }
×
2145

2146
        if subnet.Spec.Vlan == "" && subnet.Spec.Vpc == c.config.ClusterRouter {
×
2147
                nodes, err := c.nodesLister.List(labels.Everything())
×
2148
                if err != nil {
×
2149
                        klog.Errorf("failed to list nodes: %v", err)
×
2150
                        return false, false, err
×
2151
                }
×
2152

2153
                for _, node := range nodes {
×
2154
                        nodeIPv4, nodeIPv6 := util.GetNodeInternalIP(*node)
×
2155
                        if ipv4 != "" && ipv4 == nodeIPv4 {
×
2156
                                klog.Errorf("IP address (%s) assigned to pod %s is the same with internal IP address of node %s, reallocating...", ipv4, podName, node.Name)
×
2157
                                return false, true, nil
×
2158
                        }
×
2159
                        if ipv6 != "" && ipv6 == nodeIPv6 {
×
2160
                                klog.Errorf("IP address (%s) assigned to pod %s is the same with internal IP address of node %s, reallocating...", ipv6, podName, node.Name)
×
2161
                                return true, false, nil
×
2162
                        }
×
2163
                }
2164
        }
2165

2166
        return true, true, nil
×
2167
}
2168

2169
func (c *Controller) acquireAddress(pod *v1.Pod, podNet *kubeovnNet) (string, string, string, *kubeovnv1.Subnet, error) {
1✔
2170
        // becomes vmNAme when pod is owned by a kubevirt VM
1✔
2171
        podName := c.getNameByPod(pod)
1✔
2172
        key := cache.NewObjectName(pod.Namespace, podName).String()
1✔
2173
        portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)
1✔
2174

1✔
2175
        var checkVMPod bool
1✔
2176
        isStsPod, _, _ := isStatefulSetPod(pod)
1✔
2177
        // if pod has static vip
1✔
2178
        vipName := pod.Annotations[util.VipAnnotation]
1✔
2179
        if vipName != "" {
1✔
2180
                vip, err := c.virtualIpsLister.Get(vipName)
×
2181
                if err != nil {
×
2182
                        klog.Errorf("failed to get static vip '%s', %v", vipName, err)
×
2183
                        return "", "", "", podNet.Subnet, err
×
2184
                }
×
2185
                if c.config.EnableKeepVMIP {
×
2186
                        checkVMPod, _ = isVMPod(pod)
×
2187
                }
×
2188
                if err = c.podReuseVip(vipName, portName, isStsPod || checkVMPod); err != nil {
×
2189
                        return "", "", "", podNet.Subnet, err
×
2190
                }
×
2191
                return vip.Status.V4ip, vip.Status.V6ip, vip.Status.Mac, podNet.Subnet, nil
×
2192
        }
2193

2194
        var macPointer *string
1✔
2195
        if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
1✔
2196
                // will not use the mac address vm-overlay.default.kubernetes.io/mac_address.net1
×
2197
                key := perInterfaceMACAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
×
2198
                if macStr := pod.Annotations[key]; macStr != "" {
×
2199
                        if _, err := net.ParseMAC(macStr); err != nil {
×
2200
                                return "", "", "", podNet.Subnet, err
×
2201
                        }
×
2202
                        macPointer = &macStr
×
2203
                }
2204
        }
2205

2206
        if macPointer == nil && isOvnSubnet(podNet.Subnet) {
2✔
2207
                annoMAC := pod.Annotations[fmt.Sprintf(util.MacAddressAnnotationTemplate, podNet.ProviderName)]
1✔
2208
                if annoMAC != "" {
1✔
2209
                        if _, err := net.ParseMAC(annoMAC); err != nil {
×
2210
                                return "", "", "", podNet.Subnet, err
×
2211
                        }
×
2212
                        macPointer = &annoMAC
×
2213
                }
2214
        } else if macPointer == nil {
×
2215
                macPointer = new("")
×
2216
        }
×
2217

2218
        var nsNets []*kubeovnNet
1✔
2219
        ippoolStr := pod.Annotations[fmt.Sprintf(util.IPPoolAnnotationTemplate, podNet.ProviderName)]
1✔
2220
        subnetStr := pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, podNet.ProviderName)]
1✔
2221

1✔
2222
        // Prepare nsNets based on subnet annotation
1✔
2223
        var err error
1✔
2224
        if subnetStr != "" {
2✔
2225
                nsNets = []*kubeovnNet{podNet}
1✔
2226
        } else if nsNets, err = c.getNsAvailableSubnets(pod, podNet); err != nil {
2✔
2227
                klog.Errorf("failed to get available subnets for pod %s/%s, %v", pod.Namespace, pod.Name, err)
×
2228
                return "", "", "", podNet.Subnet, err
×
2229
        }
×
2230

2231
        if ippoolStr == "" && podNet.IsDefault {
2✔
2232
                // no ippool specified by pod annotation, use namespace ippools or ippools in the subnet specified by pod annotation
1✔
2233
                ns, err := c.namespacesLister.Get(pod.Namespace)
1✔
2234
                if err != nil {
1✔
2235
                        klog.Errorf("failed to get namespace %s: %v", pod.Namespace, err)
×
2236
                        return "", "", "", podNet.Subnet, err
×
2237
                }
×
2238
                subnetNames := make([]string, 0, len(nsNets))
1✔
2239
                for _, net := range nsNets {
2✔
2240
                        if net.Subnet.Name == subnetStr {
2✔
2241
                                // allocate from ippools in the subnet specified by pod annotation
1✔
2242
                                podNet.Subnet = net.Subnet
1✔
2243
                                subnetNames = []string{net.Subnet.Name}
1✔
2244
                                break
1✔
2245
                        }
2246
                        subnetNames = append(subnetNames, net.Subnet.Name)
1✔
2247
                }
2248

2249
                if subnetStr == "" || slices.Contains(subnetNames, subnetStr) {
2✔
2250
                        // no subnet specified by pod annotation or specified subnet is in namespace subnets
1✔
2251
                        if ipPoolList, ok := ns.Annotations[util.IPPoolAnnotation]; ok {
1✔
2252
                                for ipPoolName := range strings.SplitSeq(ipPoolList, ",") {
×
2253
                                        ippool, err := c.ippoolLister.Get(ipPoolName)
×
2254
                                        if err != nil {
×
2255
                                                klog.Errorf("failed to get ippool %s: %v", ipPoolName, err)
×
2256
                                                return "", "", "", podNet.Subnet, err
×
2257
                                        }
×
2258

2259
                                        switch podNet.Subnet.Spec.Protocol {
×
2260
                                        case kubeovnv1.ProtocolDual:
×
2261
                                                if ippool.Status.V4AvailableIPs.Int64() == 0 || ippool.Status.V6AvailableIPs.Int64() == 0 {
×
2262
                                                        continue
×
2263
                                                }
2264
                                        case kubeovnv1.ProtocolIPv4:
×
2265
                                                if ippool.Status.V4AvailableIPs.Int64() == 0 {
×
2266
                                                        continue
×
2267
                                                }
2268

2269
                                        default:
×
2270
                                                if ippool.Status.V6AvailableIPs.Int64() == 0 {
×
2271
                                                        continue
×
2272
                                                }
2273
                                        }
2274

2275
                                        for _, net := range nsNets {
×
2276
                                                if net.Subnet.Name == ippool.Spec.Subnet && slices.Contains(subnetNames, net.Subnet.Name) {
×
2277
                                                        ippoolStr = ippool.Name
×
2278
                                                        podNet.Subnet = net.Subnet
×
2279
                                                        break
×
2280
                                                }
2281
                                        }
2282
                                        if ippoolStr != "" {
×
2283
                                                break
×
2284
                                        }
2285
                                }
2286
                                if ippoolStr == "" {
×
2287
                                        klog.Infof("no available ippool in subnet(s) %s for pod %s/%s", strings.Join(subnetNames, ","), pod.Namespace, pod.Name)
×
2288
                                        return "", "", "", podNet.Subnet, ipam.ErrNoAvailable
×
2289
                                }
×
2290
                        }
2291
                }
2292
        }
2293

2294
        // will be subnet.namespace.interface.ovn.kubernetes.io/ip_address in case of multiple interfaces with
2295
        // interface name provided or subnet.namespace.ovn.kubernetes.io/ip_address in case of single interface or multiple interfaces without interface name provided
2296
        if pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)] == "" &&
1✔
2297
                ippoolStr == "" {
1✔
2298
                // check new IP annotation
×
2299
                if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
×
2300
                        // subnet.namespace.kubernetes.io/ip_address.ifName = ipAddress
×
2301
                        annoKey := perInterfaceIPAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
×
2302
                        if ipStr := pod.Annotations[annoKey]; ipStr != "" {
×
2303
                                return c.acquireStaticAddressHelper(pod, podNet, portName, macPointer, ippoolStr, nsNets, isStsPod, key)
×
2304
                        }
×
2305
                }
2306

2307
                var skippedAddrs []string
×
2308
                for {
×
2309
                        ipv4, ipv6, mac, err := c.ipam.GetRandomAddress(key, portName, macPointer, podNet.Subnet.Name, "", skippedAddrs, !podNet.AllowLiveMigration)
×
2310
                        if err != nil {
×
2311
                                klog.Error(err)
×
2312
                                return "", "", "", podNet.Subnet, err
×
2313
                        }
×
2314
                        ipv4OK, ipv6OK, err := c.validatePodIP(pod.Name, podNet.Subnet.Name, ipv4, ipv6)
×
2315
                        if err != nil {
×
2316
                                klog.Error(err)
×
2317
                                return "", "", "", podNet.Subnet, err
×
2318
                        }
×
2319
                        if ipv4OK && ipv6OK {
×
2320
                                return ipv4, ipv6, mac, podNet.Subnet, nil
×
2321
                        }
×
2322

2323
                        if !ipv4OK {
×
2324
                                skippedAddrs = append(skippedAddrs, ipv4)
×
2325
                        }
×
2326
                        if !ipv6OK {
×
2327
                                skippedAddrs = append(skippedAddrs, ipv6)
×
2328
                        }
×
2329
                }
2330
        }
2331

2332
        return c.acquireStaticAddressHelper(pod, podNet, portName, macPointer, ippoolStr, nsNets, isStsPod, key)
1✔
2333
}
2334

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

1✔
2339
        // Static allocate
1✔
2340
        if podNet.NadName != "" && podNet.NadNamespace != "" && podNet.InterfaceName != "" {
2✔
2341
                annotationKey := perInterfaceIPAnnotationKey(podNet.NadName, podNet.NadNamespace, podNet.InterfaceName)
1✔
2342
                if ipStr := pod.Annotations[annotationKey]; ipStr != "" {
2✔
2343
                        for _, net := range nsNets {
2✔
2344
                                v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipStr, macPointer, net.Subnet.Name, net.AllowLiveMigration)
1✔
2345
                                if err == nil {
2✔
2346
                                        return v4IP, v6IP, mac, net.Subnet, nil
1✔
2347
                                }
1✔
2348
                        }
2349
                        return v4IP, v6IP, mac, podNet.Subnet, err
×
2350
                }
2351
        }
2352

2353
        if ipStr := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, podNet.ProviderName)]; ipStr != "" {
2✔
2354
                for _, net := range nsNets {
2✔
2355
                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipStr, macPointer, net.Subnet.Name, net.AllowLiveMigration)
1✔
2356
                        if err == nil {
2✔
2357
                                return v4IP, v6IP, mac, net.Subnet, nil
1✔
2358
                        }
1✔
2359
                }
2360
                return v4IP, v6IP, mac, podNet.Subnet, err
1✔
2361
        }
2362

2363
        // IPPool allocate
2364
        if ippoolStr != "" {
2✔
2365
                var ipPool []string
1✔
2366
                if strings.ContainsRune(ippoolStr, ';') {
1✔
2367
                        ipPool = strings.Split(ippoolStr, ";")
×
2368
                } else {
1✔
2369
                        ipPool = strings.Split(ippoolStr, ",")
1✔
2370
                        if len(ipPool) == 2 && util.CheckProtocol(ipPool[0]) != util.CheckProtocol(ipPool[1]) {
1✔
2371
                                ipPool = []string{ippoolStr}
×
2372
                        }
×
2373
                }
2374
                for i, ip := range ipPool {
2✔
2375
                        ipPool[i] = strings.TrimSpace(ip)
1✔
2376
                }
1✔
2377

2378
                if len(ipPool) == 1 && (!strings.ContainsRune(ipPool[0], ',') && net.ParseIP(ipPool[0]) == nil) {
1✔
2379
                        var skippedAddrs []string
×
2380
                        pool, err := c.ippoolLister.Get(ipPool[0])
×
2381
                        if err != nil {
×
2382
                                klog.Errorf("failed to get ippool %s: %v", ipPool[0], err)
×
2383
                                return "", "", "", podNet.Subnet, err
×
2384
                        }
×
2385
                        for {
×
2386
                                ipv4, ipv6, mac, err := c.ipam.GetRandomAddress(key, portName, macPointer, pool.Spec.Subnet, ipPool[0], skippedAddrs, !podNet.AllowLiveMigration)
×
2387
                                if err != nil {
×
2388
                                        klog.Error(err)
×
2389
                                        return "", "", "", podNet.Subnet, err
×
2390
                                }
×
2391
                                ipv4OK, ipv6OK, err := c.validatePodIP(pod.Name, podNet.Subnet.Name, ipv4, ipv6)
×
2392
                                if err != nil {
×
2393
                                        klog.Error(err)
×
2394
                                        return "", "", "", podNet.Subnet, err
×
2395
                                }
×
2396
                                if ipv4OK && ipv6OK {
×
2397
                                        return ipv4, ipv6, mac, podNet.Subnet, nil
×
2398
                                }
×
2399

2400
                                if !ipv4OK {
×
2401
                                        skippedAddrs = append(skippedAddrs, ipv4)
×
2402
                                }
×
2403
                                if !ipv6OK {
×
2404
                                        skippedAddrs = append(skippedAddrs, ipv6)
×
2405
                                }
×
2406
                        }
2407
                }
2408

2409
                if !isStsPod {
2✔
2410
                        for _, net := range nsNets {
2✔
2411
                                for _, staticIP := range ipPool {
2✔
2412
                                        var checkIP string
1✔
2413
                                        ipProtocol := util.CheckProtocol(staticIP)
1✔
2414
                                        if ipProtocol == kubeovnv1.ProtocolDual {
1✔
2415
                                                checkIP = strings.Split(staticIP, ",")[0]
×
2416
                                        } else {
1✔
2417
                                                checkIP = staticIP
1✔
2418
                                        }
1✔
2419

2420
                                        if assignedPod, ok := c.ipam.IsIPAssignedToOtherPod(checkIP, net.Subnet.Name, key); ok {
1✔
2421
                                                klog.Errorf("static address %s for %s has been assigned to %s", staticIP, key, assignedPod)
×
2422
                                                err = ipam.ErrConflict
×
2423
                                                continue
×
2424
                                        }
2425

2426
                                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, staticIP, macPointer, net.Subnet.Name, net.AllowLiveMigration)
1✔
2427
                                        if err == nil {
1✔
2428
                                                return v4IP, v6IP, mac, net.Subnet, nil
×
2429
                                        }
×
2430
                                }
2431
                        }
2432
                        klog.Errorf("acquire address from ippool %s for %s failed, %v", ippoolStr, key, err)
1✔
2433
                        if err != nil {
2✔
2434
                                return "", "", "", podNet.Subnet, err
1✔
2435
                        }
1✔
2436
                } else {
×
2437
                        tempStrs := strings.Split(pod.Name, "-")
×
2438
                        numStr := tempStrs[len(tempStrs)-1]
×
2439
                        index, _ := strconv.Atoi(numStr)
×
2440

×
2441
                        if index < len(ipPool) {
×
2442
                                for _, net := range nsNets {
×
2443
                                        v4IP, v6IP, mac, err = c.acquireStaticAddress(key, portName, ipPool[index], macPointer, net.Subnet.Name, net.AllowLiveMigration)
×
2444
                                        if err == nil {
×
2445
                                                return v4IP, v6IP, mac, net.Subnet, nil
×
2446
                                        }
×
2447
                                }
2448
                                klog.Errorf("acquire address %s for %s failed, %v", ipPool[index], key, err)
×
2449
                                if err != nil {
×
2450
                                        return "", "", "", podNet.Subnet, err
×
2451
                                }
×
2452
                        }
2453
                }
2454
        }
2455
        klog.Errorf("allocate address for %s failed, return NoAvailableAddress", key)
×
2456
        return "", "", "", podNet.Subnet, ipam.ErrNoAvailable
×
2457
}
2458

2459
func (c *Controller) acquireStaticAddress(key, nicName, ip string, mac *string, subnet string, liveMigration bool) (string, string, string, error) {
1✔
2460
        var v4IP, v6IP, macStr string
1✔
2461
        var err error
1✔
2462
        for ipStr := range strings.SplitSeq(ip, ",") {
2✔
2463
                if net.ParseIP(ipStr) == nil {
1✔
2464
                        return "", "", "", fmt.Errorf("failed to parse IP %s", ipStr)
×
2465
                }
×
2466
        }
2467

2468
        if v4IP, v6IP, macStr, err = c.ipam.GetStaticAddress(key, nicName, ip, mac, subnet, !liveMigration); err != nil {
2✔
2469
                klog.Errorf("failed to get static ip %v, mac %v, subnet %v, err %v", ip, mac, subnet, err)
1✔
2470
                return "", "", "", err
1✔
2471
        }
1✔
2472
        return v4IP, v6IP, macStr, nil
1✔
2473
}
2474

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

2483
        var ownerRefAnnotations map[string]string
×
2484
        switch ownerRefKind {
×
2485
        case util.KindStatefulSet:
×
2486
                ss, err := c.config.KubeClient.AppsV1().StatefulSets(pod.Namespace).Get(context.Background(), ownerRefName, metav1.GetOptions{})
×
2487
                if err != nil {
×
2488
                        if k8serrors.IsNotFound(err) {
×
2489
                                klog.Infof("Statefulset %s is not found", ownerRefName)
×
2490
                                return true, nil, nil
×
2491
                        }
×
2492
                        klog.Errorf("failed to get StatefulSet %s, %v", ownerRefName, err)
×
2493
                        return false, nil, err
×
2494
                }
2495
                if ss.Spec.Template.Annotations != nil {
×
2496
                        ownerRefAnnotations = ss.Spec.Template.Annotations
×
2497
                }
×
2498

2499
        case util.KindVirtualMachineInstance:
×
2500
                vm, err := c.config.KubevirtClient.VirtualMachine(pod.Namespace).Get(context.Background(), ownerRefName, metav1.GetOptions{})
×
2501
                if err != nil {
×
2502
                        if k8serrors.IsNotFound(err) {
×
2503
                                klog.Infof("VirtualMachine %s is not found", ownerRefName)
×
2504
                                return true, nil, nil
×
2505
                        }
×
2506
                        klog.Errorf("failed to get VirtualMachine %s, %v", ownerRefName, err)
×
2507
                        return false, nil, err
×
2508
                }
2509
                if vm.Spec.Template != nil &&
×
2510
                        vm.Spec.Template.ObjectMeta.Annotations != nil {
×
2511
                        ownerRefAnnotations = vm.Spec.Template.ObjectMeta.Annotations
×
2512
                }
×
2513
        }
2514

2515
        var ipcrToDelete []string
×
2516
        if defaultIPCRName := appendCheckPodNonMultusNetToDel(c, pod, ownerRefName, ownerRefAnnotations, podNs); defaultIPCRName != "" {
×
2517
                ipcrToDelete = append(ipcrToDelete, defaultIPCRName)
×
2518
        }
×
2519

2520
        if multusIPCRNames := appendCheckPodMultusNetToDel(c, pod, ownerRefName, ownerRefAnnotations); len(multusIPCRNames) != 0 {
×
2521
                ipcrToDelete = append(ipcrToDelete, multusIPCRNames...)
×
2522
        }
×
2523

2524
        return false, ipcrToDelete, nil
×
2525
}
2526

2527
func appendCheckPodNonMultusNetToDel(c *Controller, pod *v1.Pod, ownerRefName string, ownerRefAnnotations map[string]string, podNs *v1.Namespace) string {
×
2528
        podDefaultSwitch := strings.TrimSpace(pod.Annotations[util.LogicalSwitchAnnotation])
×
2529
        if podDefaultSwitch != "" {
×
2530
                ownerRefSubnet := ownerRefAnnotations[util.LogicalSwitchAnnotation]
×
2531
                defaultIPCRName := ovs.PodNameToPortName(ownerRefName, pod.Namespace, util.OvnProvider)
×
2532
                if ownerRefSubnet == "" {
×
2533
                        nsSubnetNames := podNs.Annotations[util.LogicalSwitchAnnotation]
×
2534
                        // check if pod use the subnet of its ns
×
2535
                        if nsSubnetNames != "" && !slices.Contains(strings.Split(nsSubnetNames, ","), podDefaultSwitch) {
×
2536
                                klog.Infof("ns %s annotation subnet is %s, which is inconstant with subnet for pod %s, delete pod", pod.Namespace, nsSubnetNames, pod.Name)
×
2537
                                return defaultIPCRName
×
2538
                        }
×
2539
                } else {
×
2540
                        podIP := pod.Annotations[util.IPAddressAnnotation]
×
2541
                        if shouldCleanPodNet(c, pod, ownerRefName, ownerRefSubnet, podDefaultSwitch, podIP) {
×
2542
                                return defaultIPCRName
×
2543
                        }
×
2544
                }
2545
        }
2546
        return ""
×
2547
}
2548

2549
func appendCheckPodMultusNetToDel(c *Controller, pod *v1.Pod, ownerRefName string, ownerRefAnnotations map[string]string) []string {
×
2550
        var multusIPCRNames []string
×
2551
        attachmentNets, _ := c.getPodAttachmentNet(pod)
×
2552
        for _, attachmentNet := range attachmentNets {
×
2553
                ipCRName := ovs.PodNameToPortName(ownerRefName, pod.Namespace, attachmentNet.ProviderName)
×
2554
                podSwitch := strings.TrimSpace(pod.Annotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, attachmentNet.ProviderName)])
×
2555
                ownerRefSubnet := ownerRefAnnotations[fmt.Sprintf(util.LogicalSwitchAnnotationTemplate, attachmentNet.ProviderName)]
×
2556
                podIP := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, attachmentNet.ProviderName)]
×
2557
                if shouldCleanPodNet(c, pod, ownerRefName, ownerRefSubnet, podSwitch, podIP) {
×
2558
                        multusIPCRNames = append(multusIPCRNames, ipCRName)
×
2559
                }
×
2560
        }
2561
        return multusIPCRNames
×
2562
}
2563

2564
func shouldCleanPodNet(c *Controller, pod *v1.Pod, ownerRefName, ownerRefSubnet, podSwitch, podIP string) bool {
×
2565
        // subnet cidr has been changed, and statefulset pod's ip is not in the range of subnet's cidr anymore
×
2566
        podSubnet, err := c.subnetsLister.Get(podSwitch)
×
2567
        if err != nil {
×
2568
                if k8serrors.IsNotFound(err) {
×
2569
                        klog.Infof("subnet %s not found for pod %s/%s, not auto clean ip", podSwitch, pod.Namespace, pod.Name)
×
2570
                        return false
×
2571
                }
×
2572
                klog.Errorf("failed to get subnet %s, %v, not auto clean ip", podSwitch, err)
×
2573
                return false
×
2574
        }
2575
        if podSubnet == nil {
×
2576
                // TODO: remove: CRD get interface will retrun a nil subnet ?
×
2577
                klog.Errorf("pod %s/%s subnet %s is nil, not auto clean ip", pod.Namespace, pod.Name, podSwitch)
×
2578
                return false
×
2579
        }
×
2580
        if podIP == "" {
×
2581
                // delete pod just after it created < 1ms
×
2582
                klog.Infof("pod %s/%s annotaions has no ip address, not auto clean ip", pod.Namespace, pod.Name)
×
2583
                return false
×
2584
        }
×
2585
        podSubnetCidr := podSubnet.Spec.CIDRBlock
×
2586
        if podSubnetCidr == "" {
×
2587
                // subnet spec cidr changed by user
×
2588
                klog.Errorf("invalid pod subnet %s empty cidr %s, not auto clean ip", podSwitch, podSubnetCidr)
×
2589
                return false
×
2590
        }
×
2591
        if !util.CIDRContainIP(podSubnetCidr, podIP) {
×
2592
                klog.Infof("pod's ip %s is not in the range of subnet %s, delete pod", podIP, podSubnet.Name)
×
2593
                return true
×
2594
        }
×
2595
        // subnet of ownerReference(sts/vm) has been changed, it needs to handle delete pod and create port on the new logical switch
2596
        if ownerRefSubnet != "" && podSubnet.Name != ownerRefSubnet {
×
2597
                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)
×
2598
                return true
×
2599
        }
×
2600

2601
        return false
×
2602
}
2603

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

2615
        if vm.Spec.Template == nil {
×
2616
                return nil
×
2617
        }
×
2618

2619
        // Build expected port names from VM spec in a single pass (pattern from gc.go:1108-1145)
2620
        expectedPorts := make(map[string]bool)
×
2621
        defaultMultus := false
×
2622
        hasMultusNetwork := false
×
2623
        for _, network := range vm.Spec.Template.Spec.Networks {
×
2624
                if network.Multus == nil {
×
2625
                        continue
×
2626
                }
2627
                if network.Multus.Default {
×
2628
                        defaultMultus = true
×
2629
                }
×
2630
                if network.Multus.NetworkName != "" {
×
2631
                        hasMultusNetwork = true
×
2632
                        items := strings.Split(network.Multus.NetworkName, "/")
×
2633
                        if len(items) != 2 {
×
2634
                                items = []string{namespace, items[0]}
×
2635
                        }
×
2636
                        provider := fmt.Sprintf("%s.%s.%s", items[1], items[0], util.OvnProvider)
×
2637
                        expectedPorts[ovs.PodNameToPortName(vmName, namespace, provider)] = true
×
2638
                }
2639
        }
2640
        if !defaultMultus {
×
2641
                expectedPorts[ovs.PodNameToPortName(vmName, namespace, util.OvnProvider)] = true
×
2642
        }
×
2643

2644
        // If VM spec has no multus networks, skip detection to avoid
2645
        // false positives for VMs that only use template annotations.
2646
        if !hasMultusNetwork {
×
2647
                return nil
×
2648
        }
×
2649

2650
        // Find orphaned: ports in OVN but not in expected
2651
        orphanedPorts := make(map[string]bool)
×
2652
        for _, port := range existingPorts {
×
2653
                if !expectedPorts[port.Name] {
×
2654
                        klog.Infof("OVN port %s for vm %s/%s is not in VM spec, marking as orphaned",
×
2655
                                port.Name, namespace, vmName)
×
2656
                        orphanedPorts[port.Name] = true
×
2657
                }
×
2658
        }
2659

2660
        if len(orphanedPorts) == 0 {
×
2661
                return nil
×
2662
        }
×
2663
        return orphanedPorts
×
2664
}
2665

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

×
2673
        // List existing LSPs first (cheap OVN query) to bail out early if none exist
×
2674
        ports, err := c.OVNNbClient.ListNormalLogicalSwitchPorts(true, map[string]string{"pod": podKey})
×
2675
        if err != nil {
×
2676
                klog.Errorf("failed to list lsps of vm %s for stale cleanup: %v", podKey, err)
×
2677
                return
×
2678
        }
×
2679
        if len(ports) == 0 {
×
2680
                return
×
2681
        }
×
2682

2683
        // Build current port names from the pod's full network list
2684
        podNets, err := c.getPodKubeovnNets(pod)
×
2685
        if err != nil {
×
2686
                klog.Errorf("failed to get kube-ovn nets of pod %s for stale cleanup: %v", podKey, err)
×
2687
                return
×
2688
        }
×
2689
        currentPorts := make(map[string]bool, len(podNets)+1)
×
2690
        for _, podNet := range podNets {
×
2691
                currentPorts[ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName)] = true
×
2692
        }
×
2693
        currentPorts[ovs.PodNameToPortName(podName, pod.Namespace, util.OvnProvider)] = true
×
2694

×
2695
        for _, port := range ports {
×
2696
                if currentPorts[port.Name] {
×
2697
                        continue
×
2698
                }
2699
                klog.Infof("cleaning stale vm attachment lsp %s (not in current pod networks)", port.Name)
×
2700
                if err := c.OVNNbClient.DeleteLogicalSwitchPort(port.Name); err != nil {
×
2701
                        klog.Errorf("failed to delete stale lsp %s, skipping IP cleanup to avoid inconsistency: %v", port.Name, err)
×
2702
                        continue
×
2703
                }
2704

2705
                ipCR, err := c.ipsLister.Get(port.Name)
×
2706
                if err != nil {
×
2707
                        if !k8serrors.IsNotFound(err) {
×
2708
                                klog.Errorf("failed to get ip %s: %v", port.Name, err)
×
2709
                        }
×
2710
                        continue
×
2711
                }
2712
                if ipCR.Labels[util.IPReservedLabel] != "true" {
×
2713
                        klog.Infof("deleting stale vm attachment ip CR %s", ipCR.Name)
×
2714
                        if err := c.config.KubeOvnClient.KubeovnV1().IPs().Delete(context.Background(), ipCR.Name, metav1.DeleteOptions{}); err != nil {
×
2715
                                if !k8serrors.IsNotFound(err) {
×
2716
                                        klog.Errorf("failed to delete ip %s: %v", ipCR.Name, err)
×
2717
                                }
×
2718
                        }
2719
                        if subnetName := ipCR.Spec.Subnet; subnetName != "" {
×
2720
                                c.ipam.ReleaseAddressByNic(podKey, port.Name, subnetName)
×
2721
                                c.updateSubnetStatusQueue.Add(subnetName)
×
2722
                        }
×
2723
                }
2724
        }
2725
}
2726

2727
func isVMPod(pod *v1.Pod) (bool, string) {
1✔
2728
        for _, owner := range pod.OwnerReferences {
2✔
2729
                // The name of vmi is consistent with vm's name.
1✔
2730
                if owner.Kind == util.KindVirtualMachineInstance &&
1✔
2731
                        strings.HasPrefix(owner.APIVersion, kubevirtv1.SchemeGroupVersion.Group+"/") {
2✔
2732
                        return true, owner.Name
1✔
2733
                }
1✔
2734
        }
2735
        return false, ""
1✔
2736
}
2737

2738
// hasAliveSiblingVMPod reports whether pods contains any alive virt-launcher
2739
// pod owned by the VMI vmName, excluding the pod with name excludePodName.
2740
// It is used to decide whether a VM LSP's port-group memberships are still
2741
// owned by another running sibling (e.g. a live-migration destination) when a
2742
// completed/GC'd source pod is being processed.
2743
func hasAliveSiblingVMPod(pods []*v1.Pod, vmName, excludePodName string) bool {
1✔
2744
        for _, p := range pods {
2✔
2745
                if p == nil || p.Name == excludePodName {
2✔
2746
                        continue
1✔
2747
                }
2748
                isVM, name := isVMPod(p)
1✔
2749
                if !isVM || name != vmName {
2✔
2750
                        continue
1✔
2751
                }
2752
                if isPodAlive(p) {
2✔
2753
                        return true
1✔
2754
                }
1✔
2755
        }
2756
        return false
1✔
2757
}
2758

2759
func isOwnsByTheVM(vmi metav1.Object) (bool, string) {
×
2760
        for _, owner := range vmi.GetOwnerReferences() {
×
2761
                if owner.Kind == util.KindVirtualMachine &&
×
2762
                        strings.HasPrefix(owner.APIVersion, kubevirtv1.SchemeGroupVersion.Group+"/") {
×
2763
                        return true, owner.Name
×
2764
                }
×
2765
        }
2766
        return false, ""
×
2767
}
2768

2769
func (c *Controller) isVMToDel(pod *v1.Pod, vmiName string) bool {
×
2770
        var (
×
2771
                vmiAlive bool
×
2772
                vmName   string
×
2773
        )
×
2774
        // The vmi is also deleted when pod is deleted, only left vm exists.
×
2775
        vmi, err := c.config.KubevirtClient.VirtualMachineInstance(pod.Namespace).Get(context.Background(), vmiName, metav1.GetOptions{})
×
2776
        if err != nil {
×
2777
                if k8serrors.IsNotFound(err) {
×
2778
                        vmiAlive = false
×
2779
                        // The name of vmi is consistent with vm's name.
×
2780
                        vmName = vmiName
×
2781
                        klog.ErrorS(err, "failed to get vmi, will try to get the vm directly", "name", vmiName)
×
2782
                } else {
×
2783
                        klog.ErrorS(err, "failed to get vmi", "name", vmiName)
×
2784
                        return false
×
2785
                }
×
2786
        } else {
×
2787
                var ownsByVM bool
×
2788
                ownsByVM, vmName = isOwnsByTheVM(vmi)
×
2789
                if !ownsByVM && !vmi.DeletionTimestamp.IsZero() {
×
2790
                        klog.Infof("ephemeral vmi %s is deleting", vmiName)
×
2791
                        return true
×
2792
                }
×
2793
                vmiAlive = vmi.DeletionTimestamp.IsZero()
×
2794
        }
2795

2796
        if vmiAlive {
×
2797
                return false
×
2798
        }
×
2799

2800
        vm, err := c.config.KubevirtClient.VirtualMachine(pod.Namespace).Get(context.Background(), vmName, metav1.GetOptions{})
×
2801
        if err != nil {
×
2802
                // the vm has gone
×
2803
                if k8serrors.IsNotFound(err) {
×
2804
                        klog.ErrorS(err, "failed to get vm", "name", vmName)
×
2805
                        return true
×
2806
                }
×
2807
                klog.ErrorS(err, "failed to get vm", "name", vmName)
×
2808
                return false
×
2809
        }
2810

2811
        if !vm.DeletionTimestamp.IsZero() {
×
2812
                klog.Infof("vm %s is deleting", vmName)
×
2813
                return true
×
2814
        }
×
2815
        return false
×
2816
}
2817

2818
func (c *Controller) getNameByPod(pod *v1.Pod) string {
1✔
2819
        if c.config.EnableKeepVMIP {
1✔
2820
                if isVMPod, vmName := isVMPod(pod); isVMPod {
×
2821
                        return vmName
×
2822
                }
×
2823
        }
2824
        return pod.Name
1✔
2825
}
2826

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

1✔
2832
        ns, err := c.namespacesLister.Get(pod.Namespace)
1✔
2833
        if err != nil {
1✔
2834
                klog.Errorf("failed to get namespace %s, %v", pod.Namespace, err)
×
2835
                return nil, err
×
2836
        }
×
2837
        if ns.Annotations == nil {
1✔
2838
                return []*kubeovnNet{}, nil
×
2839
        }
×
2840

2841
        subnetNames := ns.Annotations[util.LogicalSwitchAnnotation]
1✔
2842
        for subnetName := range strings.SplitSeq(subnetNames, ",") {
2✔
2843
                if subnetName == "" || subnetName == podNet.Subnet.Name {
2✔
2844
                        continue
1✔
2845
                }
2846
                subnet, err := c.subnetsLister.Get(subnetName)
1✔
2847
                if err != nil {
1✔
2848
                        klog.Errorf("failed to get subnet %v", err)
×
2849
                        return nil, err
×
2850
                }
×
2851

2852
                result = append(result, &kubeovnNet{
1✔
2853
                        Type:         providerTypeOriginal,
1✔
2854
                        ProviderName: subnet.Spec.Provider,
1✔
2855
                        Subnet:       subnet,
1✔
2856
                })
1✔
2857
        }
2858

2859
        return result, nil
1✔
2860
}
2861

2862
func getPodType(pod *v1.Pod) string {
×
2863
        if ok, _, _ := isStatefulSetPod(pod); ok {
×
2864
                return util.KindStatefulSet
×
2865
        }
×
2866

2867
        if isVMPod, _ := isVMPod(pod); isVMPod {
×
2868
                return util.KindVirtualMachine
×
2869
        }
×
2870
        return ""
×
2871
}
2872

2873
func (c *Controller) getVirtualIPs(pod *v1.Pod, podNets []*kubeovnNet) map[string]string {
×
2874
        vipsListMap := make(map[string][]string)
×
2875
        var vipNamesList []string
×
2876
        for vipName := range strings.SplitSeq(strings.TrimSpace(pod.Annotations[util.AAPsAnnotation]), ",") {
×
2877
                if vipName = strings.TrimSpace(vipName); vipName == "" {
×
2878
                        continue
×
2879
                }
2880
                if !slices.Contains(vipNamesList, vipName) {
×
2881
                        vipNamesList = append(vipNamesList, vipName)
×
2882
                } else {
×
2883
                        continue
×
2884
                }
2885
                vip, err := c.virtualIpsLister.Get(vipName)
×
2886
                if err != nil {
×
2887
                        klog.Errorf("failed to get vip %s, %v", vipName, err)
×
2888
                        continue
×
2889
                }
2890
                if vip.Spec.Namespace != pod.Namespace || (vip.Status.V4ip == "" && vip.Status.V6ip == "") {
×
2891
                        continue
×
2892
                }
2893
                for _, podNet := range podNets {
×
2894
                        if podNet.Subnet.Name == vip.Spec.Subnet {
×
2895
                                key := fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)
×
2896
                                vipsList := vipsListMap[key]
×
2897
                                if vipsList == nil {
×
2898
                                        vipsList = []string{}
×
2899
                                }
×
2900
                                // ipam will ensure the uniqueness of VIP
2901
                                if util.IsValidIP(vip.Status.V4ip) {
×
2902
                                        vipsList = append(vipsList, vip.Status.V4ip)
×
2903
                                }
×
2904
                                if util.IsValidIP(vip.Status.V6ip) {
×
2905
                                        vipsList = append(vipsList, vip.Status.V6ip)
×
2906
                                }
×
2907

2908
                                vipsListMap[key] = vipsList
×
2909
                        }
2910
                }
2911
        }
2912

2913
        for _, podNet := range podNets {
×
2914
                vipStr := pod.Annotations[fmt.Sprintf(util.PortVipAnnotationTemplate, podNet.ProviderName)]
×
2915
                if vipStr == "" {
×
2916
                        continue
×
2917
                }
2918
                key := fmt.Sprintf("%s.%s", podNet.Subnet.Name, podNet.ProviderName)
×
2919
                vipsList := vipsListMap[key]
×
2920
                if vipsList == nil {
×
2921
                        vipsList = []string{}
×
2922
                }
×
2923

2924
                for vip := range strings.SplitSeq(vipStr, ",") {
×
2925
                        if util.IsValidIP(vip) && !slices.Contains(vipsList, vip) {
×
2926
                                vipsList = append(vipsList, vip)
×
2927
                        }
×
2928
                }
2929

2930
                vipsListMap[key] = vipsList
×
2931
        }
2932

2933
        vipsMap := make(map[string]string)
×
2934
        for key, vipsList := range vipsListMap {
×
2935
                vipsMap[key] = strings.Join(vipsList, ",")
×
2936
        }
×
2937
        return vipsMap
×
2938
}
2939

2940
func setPodRoutesAnnotation(annotations map[string]string, provider string, routes []request.Route) error {
×
2941
        key := fmt.Sprintf(util.RoutesAnnotationTemplate, provider)
×
2942
        if len(routes) == 0 {
×
2943
                delete(annotations, key)
×
2944
                return nil
×
2945
        }
×
2946

2947
        buf, err := json.Marshal(routes)
×
2948
        if err != nil {
×
2949
                err = fmt.Errorf("failed to marshal routes %+v: %w", routes, err)
×
2950
                klog.Error(err)
×
2951
                return err
×
2952
        }
×
2953
        annotations[key] = string(buf)
×
2954

×
2955
        return nil
×
2956
}
2957

2958
// Check if pod is a VPC NAT gateway using pod annotations
2959
func (c *Controller) checkIsPodVpcNatGw(pod *v1.Pod) (bool, string) {
1✔
2960
        if pod == nil {
2✔
2961
                return false, ""
1✔
2962
        }
1✔
2963
        if pod.Annotations == nil {
2✔
2964
                return false, ""
1✔
2965
        }
1✔
2966
        vpcGwName, isVpcNatGw := pod.Annotations[util.VpcNatGatewayAnnotation]
1✔
2967
        if isVpcNatGw {
2✔
2968
                if vpcGwName == "" {
2✔
2969
                        klog.Errorf("pod %s is vpc nat gateway but name is empty", pod.Name)
1✔
2970
                        return false, ""
1✔
2971
                }
1✔
2972
                klog.Infof("pod %s is vpc nat gateway %s", pod.Name, vpcGwName)
1✔
2973
        }
2974
        return isVpcNatGw, vpcGwName
1✔
2975
}
2976

2977
func natGwNameFromStatefulSetOwner(pod *v1.Pod) string {
1✔
2978
        isStsPod, stsName, _ := isStatefulSetPod(pod)
1✔
2979
        if !isStsPod {
1✔
2980
                return ""
×
2981
        }
×
2982

2983
        prefix := util.VpcNatGwNamePrefix + "-"
1✔
2984
        if !strings.HasPrefix(stsName, prefix) {
1✔
2985
                return ""
×
2986
        }
×
2987
        return strings.TrimPrefix(stsName, prefix)
1✔
2988
}
2989

2990
func (c *Controller) backfillVpcNatGwLanIPFromPod(pod *v1.Pod, gwName string) error {
1✔
2991
        if pod == nil {
1✔
2992
                return nil
×
2993
        }
×
2994
        if pod.Namespace != c.config.PodNamespace {
2✔
2995
                return nil
1✔
2996
        }
1✔
2997

2998
        ownerGwName := natGwNameFromStatefulSetOwner(pod)
1✔
2999
        if ownerGwName == "" {
1✔
3000
                return nil
×
3001
        }
×
3002
        if gwName == "" {
2✔
3003
                gwName = ownerGwName
1✔
3004
        }
1✔
3005
        // Use owner reference as a guard to avoid patching unrelated pods carrying a stale annotation.
3006
        if ownerGwName != gwName {
1✔
3007
                klog.Warningf("skip backfill for pod %s/%s: gw annotation %q does not match owner statefulset %q",
×
3008
                        pod.Namespace, pod.Name, gwName, ownerGwName)
×
3009
                return nil
×
3010
        }
×
3011

3012
        var (
1✔
3013
                gw  *kubeovnv1.VpcNatGateway
1✔
3014
                err error
1✔
3015
        )
1✔
3016
        if c.vpcNatGatewayLister != nil {
1✔
3017
                gw, err = c.vpcNatGatewayLister.Get(gwName)
×
3018
        } else {
1✔
3019
                gw, err = c.config.KubeOvnClient.KubeovnV1().VpcNatGateways().Get(context.Background(), gwName, metav1.GetOptions{})
1✔
3020
        }
1✔
3021
        if err != nil {
1✔
3022
                if k8serrors.IsNotFound(err) {
×
3023
                        return nil
×
3024
                }
×
3025
                return err
×
3026
        }
3027
        if gw.Spec.LanIP != "" {
2✔
3028
                return nil
1✔
3029
        }
1✔
3030

3031
        subnet, err := c.subnetsLister.Get(gw.Spec.Subnet)
1✔
3032
        if err != nil {
1✔
3033
                return fmt.Errorf("failed to get subnet %s: %w", gw.Spec.Subnet, err)
×
3034
        }
×
3035
        if !isOvnSubnet(subnet) {
1✔
3036
                return fmt.Errorf("subnet %s is not an OVN subnet", gw.Spec.Subnet)
×
3037
        }
×
3038
        provider := subnet.Spec.Provider
1✔
3039

1✔
3040
        lanIP := pod.Annotations[fmt.Sprintf(util.IPAddressAnnotationTemplate, provider)]
1✔
3041
        v4IP, v6IP := util.SplitStringIP(lanIP)
1✔
3042
        switch subnet.Spec.Protocol {
1✔
3043
        case kubeovnv1.ProtocolIPv6:
1✔
3044
                lanIP = v6IP
1✔
3045
        case kubeovnv1.ProtocolIPv4:
1✔
3046
                lanIP = v4IP
1✔
3047
        case kubeovnv1.ProtocolDual:
×
3048
                if v4IP != "" {
×
3049
                        lanIP = v4IP
×
3050
                } else {
×
3051
                        lanIP = v6IP
×
3052
                }
×
3053
        default:
×
3054
                lanIP = v4IP
×
3055
        }
3056
        if lanIP == "" || net.ParseIP(lanIP) == nil {
2✔
3057
                return nil
1✔
3058
        }
1✔
3059

3060
        patchPayload := map[string]any{
1✔
3061
                "spec": map[string]string{
1✔
3062
                        "lanIp": lanIP,
1✔
3063
                },
1✔
3064
        }
1✔
3065
        raw, err := json.Marshal(patchPayload)
1✔
3066
        if err != nil {
1✔
3067
                return err
×
3068
        }
×
3069

3070
        _, err = c.config.KubeOvnClient.KubeovnV1().VpcNatGateways().Patch(context.Background(),
1✔
3071
                gw.Name, types.MergePatchType, raw, metav1.PatchOptions{})
1✔
3072
        if err != nil {
1✔
3073
                return err
×
3074
        }
×
3075
        klog.Infof("backfilled vpc nat gateway %s spec.lanIP with pod %s/%s ip %s", gw.Name, pod.Namespace, pod.Name, lanIP)
1✔
3076
        return nil
1✔
3077
}
3078

3079
func perInterfaceIPAnnotationKey(nadName, nadNamespace, ifaceName string) string {
1✔
3080
        return fmt.Sprintf("%s.%s.kubernetes.io/ip_address.%s", nadName, nadNamespace, ifaceName)
1✔
3081
}
1✔
3082

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