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

kubeovn / kube-ovn / 19498592422

19 Nov 2025 10:45AM UTC coverage: 22.278% (+0.4%) from 21.909%
19498592422

Pull #5920

github

zbb88888
fix e2e

Signed-off-by: zbb88888 <jmdxjsjgcxy@gmail.com>
Pull Request #5920: IPPool sync to address set

274 of 415 new or added lines in 4 files covered. (66.02%)

3 existing lines in 2 files now uncovered.

11409 of 51212 relevant lines covered (22.28%)

0.26 hits per line

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

97.17
/pkg/util/ippool.go
1
package util
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7
        "math/big"
8
        "net"
9
        "sort"
10
        "strings"
11
)
12

13
// ExpandIPPoolAddresses expands a list of pool entries (IPs, ranges, CIDRs) into canonical CIDR strings without duplicates.
14
// This function provides the same parsing logic as ipam.NewIPRangeListFrom but returns CIDR strings suitable for OVN address sets.
15
//
16
// IMPORTANT: This function does NOT merge overlapping IP ranges. Each input entry is processed independently.
17
// For example, ["10.0.0.1..10.0.0.5", "10.0.0.3..10.0.0.10"] will generate CIDRs covering both ranges
18
// without merging them first, which may result in overlapping CIDRs in the output.
19
//
20
// Alternative: ipam.NewIPRangeListFrom(...).ToCIDRs() merges overlapping ranges before converting to CIDRs,
21
// producing a more compact result. However, it cannot be used here due to circular dependency (ipam -> util).
22
func ExpandIPPoolAddresses(entries []string) ([]string, error) {
1✔
23
        return expandIPPoolAddressesInternal(entries, false)
1✔
24
}
1✔
25

26
// ExpandIPPoolAddressesForOVN expands IP pool entries for OVN address sets.
27
// OVN Limitation: OVN address sets only support either IPv4 or IPv6, not both.
28
// This function will return an error if the input contains mixed IP families.
29
// For simplicity, single IP addresses are returned without /32 or /128 suffix.
30
func ExpandIPPoolAddressesForOVN(entries []string) ([]string, error) {
1✔
31
        addresses, err := expandIPPoolAddressesInternal(entries, true)
1✔
32
        if err != nil {
2✔
33
                return nil, err
1✔
34
        }
1✔
35

36
        // Simplify single IPs by removing /32 and /128 suffixes
37
        for i, addr := range addresses {
2✔
38
                addresses[i] = simplifyOVNAddress(addr)
1✔
39
        }
1✔
40
        return addresses, nil
1✔
41
}
42

43
func expandIPPoolAddressesInternal(entries []string, checkMixedIPFamily bool) ([]string, error) {
1✔
44
        if len(entries) == 0 {
2✔
45
                return nil, nil
1✔
46
        }
1✔
47

48
        seen := make(map[string]struct{})
1✔
49
        hasIPv4 := false
1✔
50
        hasIPv6 := false
1✔
51

1✔
52
        addUnique := func(cidr string) {
2✔
53
                if _, exists := seen[cidr]; !exists {
2✔
54
                        seen[cidr] = struct{}{}
1✔
55
                        // Detect IP family if check is enabled
1✔
56
                        if checkMixedIPFamily {
2✔
57
                                if strings.Contains(cidr, ":") {
2✔
58
                                        hasIPv6 = true
1✔
59
                                } else {
2✔
60
                                        hasIPv4 = true
1✔
61
                                }
1✔
62
                        }
63
                }
64
        }
65

66
        for _, raw := range entries {
2✔
67
                value := strings.TrimSpace(raw)
1✔
68
                if value == "" {
2✔
69
                        continue
1✔
70
                }
71

72
                switch {
1✔
73
                case strings.Contains(value, ".."):
1✔
74
                        cidrs, err := expandIPRange(value)
1✔
75
                        if err != nil {
2✔
76
                                return nil, err
1✔
77
                        }
1✔
78
                        for _, cidr := range cidrs {
2✔
79
                                addUnique(cidr)
1✔
80
                        }
1✔
81
                case strings.Contains(value, "/"):
1✔
82
                        cidr, err := normalizeCIDR(value)
1✔
83
                        if err != nil {
2✔
84
                                return nil, err
1✔
85
                        }
1✔
86
                        addUnique(cidr)
1✔
87
                default:
1✔
88
                        cidr, err := ipToCIDR(value)
1✔
89
                        if err != nil {
2✔
90
                                return nil, err
1✔
91
                        }
1✔
92
                        addUnique(cidr)
1✔
93
                }
94
        }
95

96
        // Check for mixed IP families if enabled (OVN address set limitation)
97
        if checkMixedIPFamily && hasIPv4 && hasIPv6 {
2✔
98
                return nil, errors.New("mixed IPv4 and IPv6 addresses are not supported in OVN address set")
1✔
99
        }
1✔
100

101
        // Convert set to sorted slice
102
        addresses := make([]string, 0, len(seen))
1✔
103
        for cidr := range seen {
2✔
104
                addresses = append(addresses, cidr)
1✔
105
        }
1✔
106
        sort.Strings(addresses)
1✔
107
        return addresses, nil
1✔
108
}
109

110
// expandIPRange expands an IP range (e.g., "10.0.0.1..10.0.0.10") into CIDRs.
111
func expandIPRange(value string) ([]string, error) {
1✔
112
        parts := strings.Split(value, "..")
1✔
113
        if len(parts) != 2 {
2✔
114
                return nil, fmt.Errorf("invalid IP range %q", value)
1✔
115
        }
1✔
116

117
        start, err := NormalizeIP(parts[0])
1✔
118
        if err != nil {
2✔
119
                return nil, fmt.Errorf("invalid range start %q: %w", parts[0], err)
1✔
120
        }
1✔
121
        end, err := NormalizeIP(parts[1])
1✔
122
        if err != nil {
2✔
123
                return nil, fmt.Errorf("invalid range end %q: %w", parts[1], err)
1✔
124
        }
1✔
125
        if (start.To4() != nil) != (end.To4() != nil) {
2✔
126
                return nil, fmt.Errorf("range %q mixes IPv4 and IPv6 addresses", value)
1✔
127
        }
1✔
128
        if compareIP(start, end) > 0 {
2✔
129
                return nil, fmt.Errorf("range %q start is greater than end", value)
1✔
130
        }
1✔
131

132
        cidrs, err := IPRangeToCIDRs(start, end)
1✔
133
        if err != nil {
1✔
NEW
134
                return nil, fmt.Errorf("failed to convert IP range %q to CIDRs: %w", value, err)
×
NEW
135
        }
×
136
        return cidrs, nil
1✔
137
}
138

139
// normalizeCIDR normalizes a CIDR string to canonical form.
140
func normalizeCIDR(value string) (string, error) {
1✔
141
        _, network, err := net.ParseCIDR(value)
1✔
142
        if err != nil {
2✔
143
                return "", fmt.Errorf("invalid CIDR %q: %w", value, err)
1✔
144
        }
1✔
145
        return network.String(), nil
1✔
146
}
147

148
// ipToCIDR converts a single IP to CIDR notation (/32 for IPv4, /128 for IPv6).
149
func ipToCIDR(value string) (string, error) {
1✔
150
        ip, err := NormalizeIP(value)
1✔
151
        if err != nil {
2✔
152
                return "", fmt.Errorf("invalid IP address %q: %w", value, err)
1✔
153
        }
1✔
154
        bits := 32
1✔
155
        if ip.To4() == nil {
2✔
156
                bits = 128
1✔
157
        }
1✔
158
        return fmt.Sprintf("%s/%d", ip.String(), bits), nil
1✔
159
}
160

161
// simplifyOVNAddress removes /32 and /128 suffixes for single IP addresses in OVN address sets.
162
// OVN accepts both "10.0.0.1" and "10.0.0.1/32", but the former is simpler.
163
func simplifyOVNAddress(cidr string) string {
1✔
164
        if strings.HasSuffix(cidr, "/32") {
2✔
165
                return strings.TrimSuffix(cidr, "/32")
1✔
166
        }
1✔
167
        if strings.HasSuffix(cidr, "/128") {
2✔
168
                return strings.TrimSuffix(cidr, "/128")
1✔
169
        }
1✔
170
        return cidr
1✔
171
}
172

173
// CanonicalizeIPPoolEntries returns a set of canonical pool entries for comparison purposes.
174
func CanonicalizeIPPoolEntries(entries []string) (map[string]bool, error) {
1✔
175
        expanded, err := ExpandIPPoolAddresses(entries)
1✔
176
        if err != nil {
2✔
177
                return nil, err
1✔
178
        }
1✔
179

180
        set := make(map[string]bool, len(expanded))
1✔
181
        for _, token := range expanded {
2✔
182
                set[token] = true
1✔
183
        }
1✔
184
        return set, nil
1✔
185
}
186

187
// NormalizeAddressSetEntries normalizes an OVN address set string list into a lookup map.
188
func NormalizeAddressSetEntries(raw string) map[string]bool {
1✔
189
        clean := strings.ReplaceAll(raw, "\"", "")
1✔
190
        tokens := strings.Fields(strings.TrimSpace(clean))
1✔
191
        set := make(map[string]bool, len(tokens))
1✔
192
        for _, token := range tokens {
2✔
193
                set[token] = true
1✔
194
        }
1✔
195
        return set
1✔
196
}
197

198
// IPPoolAddressSetName converts an IPPool name into the OVN address set name.
199
func IPPoolAddressSetName(name string) string {
1✔
200
        return strings.ReplaceAll(name, "-", ".")
1✔
201
}
1✔
202

203
// NormalizeIP parses an IP string and returns the canonical IP.
204
func NormalizeIP(value string) (net.IP, error) {
1✔
205
        ip := net.ParseIP(strings.TrimSpace(value))
1✔
206
        if ip == nil {
2✔
207
                return nil, fmt.Errorf("invalid IP address %q", value)
1✔
208
        }
1✔
209
        if v4 := ip.To4(); v4 != nil {
2✔
210
                return v4, nil
1✔
211
        }
1✔
212
        return ip.To16(), nil
1✔
213
}
214

215
// IPRangeToCIDRs converts an IP range into the minimal set of covering CIDRs.
216
func IPRangeToCIDRs(start, end net.IP) ([]string, error) {
1✔
217
        length := net.IPv4len
1✔
218
        totalBits := 32
1✔
219
        if start.To4() == nil {
2✔
220
                length = net.IPv6len
1✔
221
                totalBits = 128
1✔
222
        }
1✔
223

224
        startInt := ipToBigInt(start)
1✔
225
        endInt := ipToBigInt(end)
1✔
226
        if startInt.Cmp(endInt) > 0 {
2✔
227
                return nil, fmt.Errorf("range %s..%s start is greater than end", start, end)
1✔
228
        }
1✔
229

230
        result := make([]string, 0)
1✔
231
        tmp := new(big.Int)
1✔
232
        for startInt.Cmp(endInt) <= 0 {
2✔
233
                zeros := countTrailingZeros(startInt, totalBits)
1✔
234
                if zeros > totalBits {
1✔
NEW
235
                        return nil, fmt.Errorf("trailing zero count %d exceeds total bits %d", zeros, totalBits)
×
NEW
236
                }
×
237

238
                diff := tmp.Sub(endInt, startInt)
1✔
239
                diff.Add(diff, big.NewInt(1))
1✔
240

1✔
241
                var maxDiff int
1✔
242
                if bits := diff.BitLen(); bits > 0 {
2✔
243
                        maxDiff = bits - 1
1✔
244
                }
1✔
245

246
                size := min(zeros, maxDiff)
1✔
247
                size = min(size, totalBits)
1✔
248
                if size < 0 {
1✔
NEW
249
                        return nil, fmt.Errorf("calculated negative prefix size %d", size)
×
NEW
250
                }
×
251

252
                prefix := totalBits - size
1✔
253
                networkInt := new(big.Int).Set(startInt)
1✔
254
                networkIP := bigIntToIP(networkInt, length)
1✔
255
                network := &net.IPNet{IP: networkIP, Mask: net.CIDRMask(prefix, totalBits)}
1✔
256
                result = append(result, network.String())
1✔
257

1✔
258
                increment := new(big.Int).Lsh(big.NewInt(1), uint(size))
1✔
259
                startInt.Add(startInt, increment)
1✔
260
        }
261

262
        return result, nil
1✔
263
}
264

265
func ipToBigInt(ip net.IP) *big.Int {
1✔
266
        return new(big.Int).SetBytes(ip)
1✔
267
}
1✔
268

269
func bigIntToIP(value *big.Int, length int) net.IP {
1✔
270
        bytes := value.Bytes()
1✔
271
        if len(bytes) < length {
2✔
272
                padded := make([]byte, length)
1✔
273
                copy(padded[length-len(bytes):], bytes)
1✔
274
                bytes = padded
1✔
275
        } else if len(bytes) > length {
3✔
276
                bytes = bytes[len(bytes)-length:]
1✔
277
        }
1✔
278

279
        ip := make(net.IP, length)
1✔
280
        copy(ip, bytes)
1✔
281
        if length == net.IPv4len {
2✔
282
                return ip.To4()
1✔
283
        }
1✔
284
        return ip
1✔
285
}
286

287
func countTrailingZeros(value *big.Int, totalBits int) int {
1✔
288
        if value.Sign() == 0 {
2✔
289
                return totalBits
1✔
290
        }
1✔
291

292
        zeros := 0
1✔
293
        for zeros < totalBits && value.Bit(zeros) == 0 {
2✔
294
                zeros++
1✔
295
        }
1✔
296
        return zeros
1✔
297
}
298

299
func compareIP(a, b net.IP) int {
1✔
300
        return bytes.Compare(a, b)
1✔
301
}
1✔
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