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

kubeovn / kube-ovn / 15703224874

17 Jun 2025 09:16AM UTC coverage: 21.733% (-0.02%) from 21.755%
15703224874

Pull #5364

github

zbb88888
fix: allow any subnet is deleted before pod deletion

Signed-off-by: zbb88888 <jmdxjsjgcxy@gmail.com>
Pull Request #5364: fix ip cr not delete with pod in the case of subnet not exist

0 of 58 new or added lines in 5 files covered. (0.0%)

145 existing lines in 1 file now uncovered.

10441 of 48043 relevant lines covered (21.73%)

0.25 hits per line

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

55.72
/pkg/ovs/ovs-vsctl.go
1
package ovs
2

3
import (
4
        "context"
5
        "fmt"
6
        "os/exec"
7
        "regexp"
8
        "slices"
9
        "strconv"
10
        "strings"
11
        "time"
12

13
        "k8s.io/klog/v2"
14

15
        "github.com/kubeovn/kube-ovn/pkg/util"
16
)
17

18
var limiter *Limiter
19

20
func init() {
1✔
21
        limiter = new(Limiter)
1✔
22
}
1✔
23

24
func UpdateOVSVsctlLimiter(c int32) {
1✔
25
        if c >= 0 {
2✔
26
                limiter.Update(c)
1✔
27
                klog.V(4).Infof("update ovs-vsctl concurrency limit to %d", limiter.Limit())
1✔
28
        }
1✔
29
}
30

31
// Glory belongs to openvswitch/ovn-kubernetes
32
// https://github.com/openvswitch/ovn-kubernetes/blob/master/go-controller/pkg/util/ovs.go
33

34
var podNetNsRegexp = regexp.MustCompile(`pod_netns="([^"]+)"`)
35

36
func Exec(args ...string) (string, error) {
1✔
37
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
1✔
38
        defer cancel()
1✔
39

1✔
40
        var (
1✔
41
                start        time.Time
1✔
42
                elapsed      float64
1✔
43
                output       []byte
1✔
44
                method, code string
1✔
45
                err          error
1✔
46
        )
1✔
47

1✔
48
        if err = limiter.Wait(ctx); err != nil {
1✔
49
                klog.V(4).Infof("command %s %s waiting for execution timeout by concurrency limit of %d", OvsVsCtl, strings.Join(args, " "), limiter.Limit())
×
50
                return "", err
×
51
        }
×
52
        defer limiter.Done()
1✔
53
        klog.V(4).Infof("command %s %s waiting for execution concurrency %d/%d", OvsVsCtl, strings.Join(args, " "), limiter.Current(), limiter.Limit())
1✔
54

1✔
55
        start = time.Now()
1✔
56
        args = append([]string{"--timeout=30"}, args...)
1✔
57
        output, err = exec.Command(OvsVsCtl, args...).CombinedOutput()
1✔
58
        elapsed = float64((time.Since(start)) / time.Millisecond)
1✔
59
        klog.V(4).Infof("command %s %s in %vms", OvsVsCtl, strings.Join(args, " "), elapsed)
1✔
60

1✔
61
        for _, arg := range args {
2✔
62
                if !strings.HasPrefix(arg, "--") {
2✔
63
                        method = arg
1✔
64
                        break
1✔
65
                }
66
        }
67

68
        code = "0"
1✔
69
        defer func() {
2✔
70
                ovsClientRequestLatency.WithLabelValues("ovsdb", method, code).Observe(elapsed)
1✔
71
        }()
1✔
72

73
        if err != nil {
2✔
74
                code = "1"
1✔
75
                klog.Warningf("ovs-vsctl command error: %s %s in %vms", OvsVsCtl, strings.Join(args, " "), elapsed)
1✔
76
                return "", fmt.Errorf("failed to run '%s %s': %w\n  %q", OvsVsCtl, strings.Join(args, " "), err, output)
1✔
77
        } else if elapsed > 500 {
1✔
78
                klog.Warningf("ovs-vsctl command took too long: %s %s in %vms", OvsVsCtl, strings.Join(args, " "), elapsed)
×
79
        }
×
80
        return trimCommandOutput(output), nil
×
81
}
82

83
func ovsCreate(table string, values ...string) (string, error) {
1✔
84
        args := append([]string{"create", table}, values...)
1✔
85
        return Exec(args...)
1✔
86
}
1✔
87

88
func ovsDestroy(table, record string) error {
1✔
89
        _, err := Exec("--if-exists", "destroy", table, record)
1✔
90
        return err
1✔
91
}
1✔
92

93
func ovsSet(table, record string, values ...string) error {
1✔
94
        args := append([]string{"set", table, record}, values...)
1✔
95
        _, err := Exec(args...)
1✔
96
        return err
1✔
97
}
1✔
98

99
func ovsAdd(table, record, column string, values ...string) error {
1✔
100
        args := append([]string{"add", table, record, column}, values...)
1✔
101
        _, err := Exec(args...)
1✔
102
        return err
1✔
103
}
1✔
104

105
// Returns the given column of records that match the condition
106
func ovsFind(table, column string, conditions ...string) ([]string, error) {
1✔
107
        args := make([]string, len(conditions)+4)
1✔
108
        args[0], args[1], args[2], args[3] = "--no-heading", "--columns="+column, "find", table
1✔
109
        copy(args[4:], conditions)
1✔
110
        output, err := Exec(args...)
1✔
111
        if err != nil {
2✔
112
                klog.Error(err)
1✔
113
                return nil, err
1✔
114
        }
1✔
115
        ret := parseOvsFindOutput(output)
×
116
        return ret, nil
×
117
}
118

119
func parseOvsFindOutput(output string) []string {
1✔
120
        values := strings.Split(output, "\n\n")
1✔
121
        // We want "bare" values for strings, but we can't pass --bare to ovs-vsctl because
1✔
122
        // it breaks more complicated types. So try passing each value through Unquote();
1✔
123
        // if it fails, that means the value wasn't a quoted string, so use it as-is.
1✔
124
        for i, val := range values {
2✔
125
                if unquoted, err := strconv.Unquote(val); err == nil {
1✔
126
                        values[i] = unquoted
×
127
                }
×
128
        }
129
        ret := make([]string, 0, len(values))
1✔
130
        for _, val := range values {
2✔
131
                if strings.TrimSpace(val) != "" {
2✔
132
                        ret = append(ret, strings.Trim(strings.TrimSpace(val), "\""))
1✔
133
                }
1✔
134
        }
135
        return ret
1✔
136
}
137

138
func ovsClear(table, record string, columns ...string) error {
1✔
139
        args := append([]string{"--if-exists", "clear", table, record}, columns...)
1✔
140
        _, err := Exec(args...)
1✔
141
        return err
1✔
142
}
1✔
143

144
func ovsGet(table, record, column, key string) (string, error) {
1✔
145
        var columnVal string
1✔
146
        if key == "" {
2✔
147
                columnVal = column
1✔
148
        } else {
2✔
149
                columnVal = column + ":" + key
1✔
150
        }
1✔
151
        args := []string{"get", table, record, columnVal}
1✔
152
        return Exec(args...)
1✔
153
}
154

155
// Bridges returns bridges created by Kube-OVN
156
func Bridges() ([]string, error) {
1✔
157
        return ovsFind("bridge", "name", "external-ids:vendor="+util.CniTypeName)
1✔
158
}
1✔
159

160
// BridgeExists checks whether the bridge already exists
161
func BridgeExists(name string) (bool, error) {
1✔
162
        bridges, err := Bridges()
1✔
163
        if err != nil {
2✔
164
                klog.Error(err)
1✔
165
                return false, err
1✔
166
        }
1✔
167
        return slices.Contains(bridges, name), nil
×
168
}
169

170
// PortExists checks whether the port already exists
171
func PortExists(name string) (bool, error) {
1✔
172
        result, err := ovsFind("port", "_uuid", "name="+name)
1✔
173
        if err != nil {
2✔
174
                klog.Errorf("failed to find port with name %s: %v", name, err)
1✔
175
                return false, err
1✔
176
        }
1✔
177
        return len(result) != 0, nil
×
178
}
179

180
func GetQosList(podName, podNamespace, ifaceID string) ([]string, error) {
1✔
181
        var qosList []string
1✔
182
        var err error
1✔
183

1✔
184
        if ifaceID != "" {
2✔
185
                qosList, err = ovsFind("qos", "_uuid", fmt.Sprintf(`external-ids:iface-id="%s"`, ifaceID))
1✔
186
                if err != nil {
2✔
187
                        klog.Error(err)
1✔
188
                        return qosList, err
1✔
189
                }
1✔
190
        } else {
1✔
191
                qosList, err = ovsFind("qos", "_uuid", fmt.Sprintf(`external-ids:pod="%s/%s"`, podNamespace, podName))
1✔
192
                if err != nil {
2✔
193
                        klog.Error(err)
1✔
194
                        return qosList, err
1✔
195
                }
1✔
196
        }
197

198
        return qosList, nil
×
199
}
200

201
// ClearPodBandwidth remove qos related to this pod.
202
func ClearPodBandwidth(podName, podNamespace, ifaceID string) error {
1✔
203
        qosList, err := GetQosList(podName, podNamespace, ifaceID)
1✔
204
        if err != nil {
2✔
205
                klog.Error(err)
1✔
206
                return err
1✔
207
        }
1✔
208

209
        // https://github.com/kubeovn/kube-ovn/issues/1191
210
        usedQosList, err := ovsFind("port", "qos", "qos!=[]")
×
211
        if err != nil {
×
212
                klog.Error(err)
×
213
                return err
×
214
        }
×
215

216
        for _, qosID := range qosList {
×
217
                found := slices.Contains(usedQosList, qosID)
×
218
                if found {
×
219
                        continue
×
220
                }
221

222
                if err := ovsDestroy("qos", qosID); err != nil {
×
223
                        klog.Error(err)
×
224
                        return err
×
225
                }
×
226
        }
227
        return nil
×
228
}
229

230
// CleanLostInterface will clean up related ovs port, interface and qos
231
// When reboot node, the ovs internal interface will be deleted.
232
func CleanLostInterface() {
1✔
233
        // when interface error ofport will be -1
1✔
234
        interfaceList, err := ovsFind("interface", "name,error", "ofport=-1", "external_ids:pod_netns!=[]")
1✔
235
        if err != nil {
2✔
236
                klog.Errorf("failed to list failed interface %v", err)
1✔
237
                return
1✔
238
        }
1✔
239
        if len(interfaceList) > 0 {
×
240
                klog.Infof("error interfaces:\n %v", interfaceList)
×
241
        }
×
242

243
        for _, intf := range interfaceList {
×
244
                name, errText := strings.Trim(strings.Split(intf, "\n")[0], "\""), strings.Split(intf, "\n")[1]
×
245
                if strings.Contains(errText, "No such device") {
×
246
                        qosList, err := ovsFind("port", "qos", "name="+name)
×
247
                        if err != nil {
×
248
                                klog.Errorf("failed to find related port %v", err)
×
249
                                return
×
250
                        }
×
251
                        klog.Infof("delete lost port %s", name)
×
252
                        output, err := Exec("--if-exists", "--with-iface", "del-port", name)
×
253
                        if err != nil {
×
254
                                klog.Errorf("failed to delete ovs port %v, %s", err, output)
×
255
                                return
×
256
                        }
×
257
                        for _, qos := range qosList {
×
258
                                qos = strings.TrimSpace(qos)
×
259
                                if qos != "" && qos != "[]" {
×
260
                                        klog.Infof("delete lost qos %s", qos)
×
261
                                        err = ovsDestroy("qos", qos)
×
262
                                        if err != nil {
×
263
                                                klog.Errorf("failed to delete qos %s, %v", qos, err)
×
264
                                                return
×
265
                                        }
×
266
                                }
267
                        }
268
                }
269
        }
270
}
271

272
// Find and remove any existing OVS port with this iface-id. Pods can
273
// have multiple sandboxes if some are waiting for garbage collection,
274
// but only the latest one should have the iface-id set.
275
// See: https://github.com/ovn-org/ovn-kubernetes/pull/869
276
func CleanDuplicatePort(ifaceID, portName string) {
1✔
277
        uuids, _ := ovsFind("Interface", "_uuid", "external-ids:iface-id="+ifaceID, "name!="+portName)
1✔
278
        for _, uuid := range uuids {
1✔
279
                if out, err := Exec("remove", "Interface", uuid, "external-ids", "iface-id"); err != nil {
×
280
                        klog.Errorf("failed to clear stale OVS port %q iface-id %q: %v\n  %q", uuid, ifaceID, err, out)
×
281
                }
×
282
        }
283
}
284

285
// ValidatePortVendor returns true if the port's external_ids:vendor=kube-ovn
286
func ValidatePortVendor(port string) (bool, error) {
1✔
287
        output, err := ovsFind("Port", "name", "external_ids:vendor="+util.CniTypeName)
1✔
288
        return slices.Contains(output, port), err
1✔
289
}
1✔
290

291
func GetInterfacePodNs(iface string) (string, error) {
1✔
292
        ret, err := ovsFind("interface", "external-ids", "external-ids:iface-id="+iface)
1✔
293
        if err != nil {
2✔
294
                klog.Error(err)
1✔
295
                return "", err
1✔
296
        }
1✔
297

298
        if len(ret) == 0 {
×
299
                return "", nil
×
300
        }
×
301

302
        podNetNs := ""
×
303
        match := podNetNsRegexp.FindStringSubmatch(ret[0])
×
304
        if len(match) > 1 {
×
305
                podNetNs = match[1]
×
306
        }
×
307

308
        return podNetNs, nil
×
309
}
310

311
// config mirror for interface by pod annotations and install param
312
func ConfigInterfaceMirror(globalMirror bool, open, iface string) error {
1✔
313
        if globalMirror {
2✔
314
                return nil
1✔
315
        }
1✔
316
        // find interface name for port
317
        interfaceList, err := ovsFind("interface", "name", "external-ids:iface-id="+iface)
1✔
318
        if err != nil {
2✔
319
                klog.Error(err)
1✔
320
                return err
1✔
321
        }
1✔
322
        for _, ifName := range interfaceList {
×
323
                // ifName example: xxx_h
×
324
                // find port uuid by interface name
×
325
                portUUIDs, err := ovsFind("port", "_uuid", "name="+ifName)
×
326
                if err != nil {
×
327
                        klog.Error(err)
×
328
                        return err
×
329
                }
×
330
                if len(portUUIDs) != 1 {
×
331
                        return fmt.Errorf("find port failed, portName=%s", ifName)
×
332
                }
×
333
                portID := portUUIDs[0]
×
334
                if open == "true" {
×
335
                        // add port to mirror
×
336
                        err = ovsAdd("mirror", util.MirrorDefaultName, "select_dst_port", portID)
×
337
                        if err != nil {
×
338
                                klog.Error(err)
×
339
                                return err
×
340
                        }
×
341
                } else {
×
342
                        mirrorPorts, err := ovsFind("mirror", "select_dst_port", "name="+util.MirrorDefaultName)
×
343
                        if err != nil {
×
344
                                klog.Error(err)
×
345
                                return err
×
346
                        }
×
347
                        if len(mirrorPorts) == 0 {
×
348
                                return fmt.Errorf("find mirror failed, mirror name=%s", util.MirrorDefaultName)
×
349
                        }
×
350
                        if len(mirrorPorts) > 1 {
×
351
                                return fmt.Errorf("repeated mirror data, mirror name=%s", util.MirrorDefaultName)
×
352
                        }
×
353
                        for _, mirrorPortIDs := range mirrorPorts {
×
354
                                if strings.Contains(mirrorPortIDs, portID) {
×
355
                                        // remove port from mirror
×
356
                                        _, err := Exec("remove", "mirror", util.MirrorDefaultName, "select_dst_port", portID)
×
357
                                        if err != nil {
×
358
                                                klog.Error(err)
×
359
                                                return err
×
360
                                        }
×
361
                                }
362
                        }
363
                }
364
        }
365
        return nil
×
366
}
367

368
func GetResidualInternalPorts() []string {
1✔
369
        residualPorts := make([]string, 0)
1✔
370
        interfaceList, err := ovsFind("interface", "name,external_ids", "type=internal")
1✔
371
        if err != nil {
2✔
372
                klog.Errorf("failed to list ovs internal interface %v", err)
1✔
373
                return residualPorts
1✔
374
        }
1✔
375

376
        for _, intf := range interfaceList {
×
377
                name := strings.Trim(strings.Split(intf, "\n")[0], "\"")
×
378
                if !strings.Contains(name, "_c") {
×
379
                        continue
×
380
                }
381

382
                // iface-id field does not exist in external_ids for residual internal port
383
                externalIDs := strings.Split(intf, "\n")[1]
×
384
                if !strings.Contains(externalIDs, "iface-id") {
×
385
                        residualPorts = append(residualPorts, name)
×
386
                }
×
387
        }
388
        return residualPorts
×
389
}
390

391
// remove qos related to this port.
392
func ClearPortQosBinding(ifaceID string) error {
1✔
393
        interfaceList, err := ovsFind("interface", "name", fmt.Sprintf(`external-ids:iface-id="%s"`, ifaceID))
1✔
394
        if err != nil {
2✔
395
                klog.Error(err)
1✔
396
                return err
1✔
397
        }
1✔
398

399
        for _, ifName := range interfaceList {
×
400
                if err = ovsClear("port", ifName, "qos"); err != nil {
×
401
                        klog.Error(err)
×
402
                        return err
×
403
                }
×
404
        }
405
        return nil
×
406
}
407

408
func ListExternalIDs(table string) (map[string]string, error) {
1✔
409
        args := []string{"--data=bare", "--format=csv", "--no-heading", "--columns=_uuid,external_ids", "find", table, "external_ids:iface-id!=[]"}
1✔
410
        output, err := Exec(args...)
1✔
411
        if err != nil {
2✔
412
                klog.Errorf("failed to list %s, %v", table, err)
1✔
413
                return nil, err
1✔
414
        }
1✔
415
        lines := strings.Split(output, "\n")
×
416
        result := make(map[string]string, len(lines))
×
417
        for _, l := range lines {
×
418
                if len(strings.TrimSpace(l)) == 0 {
×
419
                        continue
×
420
                }
421
                parts := strings.Split(strings.TrimSpace(l), ",")
×
422
                if len(parts) != 2 {
×
423
                        continue
×
424
                }
425
                uuid := strings.TrimSpace(parts[0])
×
NEW
426
                externalIDs := strings.Fields(parts[1])
×
NEW
427
                for _, externalID := range externalIDs {
×
428
                        if !strings.Contains(externalID, "iface-id=") {
×
429
                                continue
×
430
                        }
431
                        iface := strings.TrimPrefix(strings.TrimSpace(externalID), "iface-id=")
×
432
                        result[iface] = uuid
×
433
                        break
×
434
                }
435
        }
436
        return result, nil
×
437
}
438

439
func ListQosQueueIDs() (map[string]string, error) {
1✔
440
        args := []string{"--data=bare", "--format=csv", "--no-heading", "--columns=_uuid,queues", "find", "qos", "queues:0!=[]"}
1✔
441
        output, err := Exec(args...)
1✔
442
        if err != nil {
2✔
443
                klog.Errorf("failed to list qos, %v", err)
1✔
444
                return nil, err
1✔
445
        }
1✔
446
        lines := strings.Split(output, "\n")
×
447
        result := make(map[string]string, len(lines))
×
448
        for _, l := range lines {
×
449
                if len(strings.TrimSpace(l)) == 0 {
×
450
                        continue
×
451
                }
452
                parts := strings.Split(strings.TrimSpace(l), ",")
×
453
                if len(parts) != 2 {
×
454
                        continue
×
455
                }
456
                qosID := strings.TrimSpace(parts[0])
×
457
                if !strings.Contains(strings.TrimSpace(parts[1]), "0=") {
×
458
                        continue
×
459
                }
460
                queueID := strings.TrimPrefix(strings.TrimSpace(parts[1]), "0=")
×
461
                result[qosID] = queueID
×
462
        }
463
        return result, nil
×
464
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc