Skip to content

Commit cb16980

Browse files
author
origin-release-container
committed
Merge remote-tracking branch 'upstream/master' into d/s-merge-08-21-2025
2 parents cb5375b + 7c04728 commit cb16980

File tree

63 files changed

+2916
-454
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2916
-454
lines changed

contrib/kind-common

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ if_error_exit() {
3535
set_common_default_params() {
3636
KIND_IMAGE=${KIND_IMAGE:-kindest/node}
3737
K8S_VERSION=${K8S_VERSION:-v1.33.1}
38+
KIND_SETTLE_DURATION=${KIND_SETTLE_DURATION:-30}
3839
}
3940

4041
run_kubectl() {
@@ -369,8 +370,8 @@ calculate_timeout() {
369370
}
370371

371372
sleep_until_pods_settle() {
372-
echo "Pods are all up, allowing things settle for 30 seconds..."
373-
sleep 30
373+
echo "Pods are all up, allowing things settle for ${KIND_SETTLE_DURATION} seconds..."
374+
sleep ${KIND_SETTLE_DURATION}
374375
}
375376

376377
is_nested_virt_enabled() {

dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,48 @@ spec:
9494
layer2:
9595
description: Layer2 is the Layer2 topology configuration.
9696
properties:
97+
defaultGatewayIPs:
98+
description: |-
99+
defaultGatewayIPs specifies the default gateway IP used in the internal OVN topology.
100+
101+
Dual-stack clusters may set 2 IPs (one for each IP family), otherwise only 1 IP is allowed.
102+
This field is only allowed for "Primary" network.
103+
It is not recommended to set this field without explicit need and understanding of the OVN network topology.
104+
When omitted, an IP from the subnets field is used.
105+
items:
106+
type: string
107+
x-kubernetes-validations:
108+
- message: IP is invalid
109+
rule: isIP(self)
110+
maxItems: 2
111+
minItems: 1
112+
type: array
113+
x-kubernetes-validations:
114+
- message: When 2 IPs are set, they must be from different
115+
IP families
116+
rule: size(self) != 2 || !isIP(self[0]) || !isIP(self[1])
117+
|| ip(self[0]).family() != ip(self[1]).family()
118+
infrastructureSubnets:
119+
description: |-
120+
infrastructureSubnets specifies a list of internal CIDR ranges that OVN-Kubernetes will reserve for internal network infrastructure.
121+
Any IP addresses within these ranges cannot be assigned to workloads.
122+
When omitted, OVN-Kubernetes will automatically allocate IP addresses from `subnets` for its infrastructure needs.
123+
When there are not enough available IPs in the provided infrastructureSubnets, OVN-Kubernetes will automatically allocate IP addresses from subnets for its infrastructure needs.
124+
When `reservedSubnets` is also specified the CIDRs cannot overlap.
125+
When `defaultGatewayIPs` is also specified, the default gateway IPs must belong to one of the infrastructure subnet CIDRs.
126+
Each item should be in range of the specified CIDR(s) in `subnets`.
127+
The maximum number of entries allowed is 4.
128+
The format should match standard CIDR notation (for example, "10.128.0.0/16").
129+
This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
130+
items:
131+
maxLength: 43
132+
type: string
133+
x-kubernetes-validations:
134+
- message: CIDR is invalid
135+
rule: isCIDR(self)
136+
maxItems: 4
137+
minItems: 1
138+
type: array
97139
ipam:
98140
description: IPAM section contains IPAM-related configuration
99141
for the network.
@@ -160,6 +202,24 @@ spec:
160202
maximum: 65536
161203
minimum: 576
162204
type: integer
205+
reservedSubnets:
206+
description: |-
207+
reservedSubnets specifies a list of CIDRs reserved for static IP assignment, excluded from automatic allocation.
208+
reservedSubnets is optional. When omitted, all IP addresses in `subnets` are available for automatic assignment.
209+
IPs from these ranges can still be requested through static IP assignment.
210+
Each item should be in range of the specified CIDR(s) in `subnets`.
211+
The maximum number of entries allowed is 25.
212+
The format should match standard CIDR notation (for example, "10.128.0.0/16").
213+
This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
214+
items:
215+
maxLength: 43
216+
type: string
217+
x-kubernetes-validations:
218+
- message: CIDR is invalid
219+
rule: isCIDR(self)
220+
maxItems: 25
221+
minItems: 1
222+
type: array
163223
role:
164224
description: |-
165225
Role describes the network role in the pod.
@@ -212,6 +272,47 @@ spec:
212272
subnet is used
213273
rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
214274
isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280'
275+
- message: defaultGatewayIPs is only supported for Primary network
276+
rule: '!has(self.defaultGatewayIPs) || has(self.role) && self.role
277+
== ''Primary'''
278+
- message: defaultGatewayIPs must belong to one of the subnets
279+
specified in the subnets field
280+
rule: '!has(self.defaultGatewayIPs) || self.defaultGatewayIPs.all(ip,
281+
self.subnets.exists(subnet, cidr(subnet).containsIP(ip)))'
282+
- message: defaultGatewayIPs must be specified for all IP families
283+
rule: '!has(self.defaultGatewayIPs) || size(self.defaultGatewayIPs)
284+
== size(self.subnets)'
285+
- message: reservedSubnets must be unset when subnets is unset
286+
rule: '!has(self.reservedSubnets) || has(self.subnets)'
287+
- message: reservedSubnets is only supported for Primary network
288+
rule: '!has(self.reservedSubnets) || has(self.role) && self.role
289+
== ''Primary'''
290+
- message: infrastructureSubnets must be unset when subnets is
291+
unset
292+
rule: '!has(self.infrastructureSubnets) || has(self.subnets)'
293+
- message: infrastructureSubnets is only supported for Primary
294+
network
295+
rule: '!has(self.infrastructureSubnets) || has(self.role) &&
296+
self.role == ''Primary'''
297+
- message: defaultGatewayIPs have to belong to infrastructureSubnets
298+
rule: '!has(self.infrastructureSubnets) || !has(self.defaultGatewayIPs)
299+
|| self.defaultGatewayIPs.all(ip, self.infrastructureSubnets.exists(subnet,
300+
cidr(subnet).containsIP(ip)))'
301+
- fieldPath: .reservedSubnets
302+
message: reservedSubnets must be subnetworks of the networks
303+
specified in the subnets field
304+
rule: '!has(self.reservedSubnets) || self.reservedSubnets.all(e,
305+
self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))'
306+
- fieldPath: .infrastructureSubnets
307+
message: infrastructureSubnets must be subnetworks of the networks
308+
specified in the subnets field
309+
rule: '!has(self.infrastructureSubnets) || self.infrastructureSubnets.all(e,
310+
self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))'
311+
- message: infrastructureSubnets and reservedSubnets must not
312+
overlap
313+
rule: '!has(self.infrastructureSubnets) || !has(self.reservedSubnets)
314+
|| self.infrastructureSubnets.all(infra, !self.reservedSubnets.exists(reserved,
315+
cidr(infra).containsCIDR(reserved) || cidr(reserved).containsCIDR(infra)))'
215316
layer3:
216317
description: Layer3 is the Layer3 topology configuration.
217318
properties:

dist/templates/k8s.ovn.org_userdefinednetworks.yaml.j2

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,48 @@ spec:
4242
layer2:
4343
description: Layer2 is the Layer2 topology configuration.
4444
properties:
45+
defaultGatewayIPs:
46+
description: |-
47+
defaultGatewayIPs specifies the default gateway IP used in the internal OVN topology.
48+
49+
Dual-stack clusters may set 2 IPs (one for each IP family), otherwise only 1 IP is allowed.
50+
This field is only allowed for "Primary" network.
51+
It is not recommended to set this field without explicit need and understanding of the OVN network topology.
52+
When omitted, an IP from the subnets field is used.
53+
items:
54+
type: string
55+
x-kubernetes-validations:
56+
- message: IP is invalid
57+
rule: isIP(self)
58+
maxItems: 2
59+
minItems: 1
60+
type: array
61+
x-kubernetes-validations:
62+
- message: When 2 IPs are set, they must be from different IP
63+
families
64+
rule: size(self) != 2 || !isIP(self[0]) || !isIP(self[1]) ||
65+
ip(self[0]).family() != ip(self[1]).family()
66+
infrastructureSubnets:
67+
description: |-
68+
infrastructureSubnets specifies a list of internal CIDR ranges that OVN-Kubernetes will reserve for internal network infrastructure.
69+
Any IP addresses within these ranges cannot be assigned to workloads.
70+
When omitted, OVN-Kubernetes will automatically allocate IP addresses from `subnets` for its infrastructure needs.
71+
When there are not enough available IPs in the provided infrastructureSubnets, OVN-Kubernetes will automatically allocate IP addresses from subnets for its infrastructure needs.
72+
When `reservedSubnets` is also specified the CIDRs cannot overlap.
73+
When `defaultGatewayIPs` is also specified, the default gateway IPs must belong to one of the infrastructure subnet CIDRs.
74+
Each item should be in range of the specified CIDR(s) in `subnets`.
75+
The maximum number of entries allowed is 4.
76+
The format should match standard CIDR notation (for example, "10.128.0.0/16").
77+
This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
78+
items:
79+
maxLength: 43
80+
type: string
81+
x-kubernetes-validations:
82+
- message: CIDR is invalid
83+
rule: isCIDR(self)
84+
maxItems: 4
85+
minItems: 1
86+
type: array
4587
ipam:
4688
description: IPAM section contains IPAM-related configuration
4789
for the network.
@@ -108,6 +150,24 @@ spec:
108150
maximum: 65536
109151
minimum: 576
110152
type: integer
153+
reservedSubnets:
154+
description: |-
155+
reservedSubnets specifies a list of CIDRs reserved for static IP assignment, excluded from automatic allocation.
156+
reservedSubnets is optional. When omitted, all IP addresses in `subnets` are available for automatic assignment.
157+
IPs from these ranges can still be requested through static IP assignment.
158+
Each item should be in range of the specified CIDR(s) in `subnets`.
159+
The maximum number of entries allowed is 25.
160+
The format should match standard CIDR notation (for example, "10.128.0.0/16").
161+
This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
162+
items:
163+
maxLength: 43
164+
type: string
165+
x-kubernetes-validations:
166+
- message: CIDR is invalid
167+
rule: isCIDR(self)
168+
maxItems: 25
169+
minItems: 1
170+
type: array
111171
role:
112172
description: |-
113173
Role describes the network role in the pod.
@@ -159,6 +219,44 @@ spec:
159219
is used
160220
rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
161221
isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280'
222+
- message: defaultGatewayIPs is only supported for Primary network
223+
rule: '!has(self.defaultGatewayIPs) || has(self.role) && self.role
224+
== ''Primary'''
225+
- message: defaultGatewayIPs must belong to one of the subnets specified
226+
in the subnets field
227+
rule: '!has(self.defaultGatewayIPs) || self.defaultGatewayIPs.all(ip,
228+
self.subnets.exists(subnet, cidr(subnet).containsIP(ip)))'
229+
- message: defaultGatewayIPs must be specified for all IP families
230+
rule: '!has(self.defaultGatewayIPs) || size(self.defaultGatewayIPs)
231+
== size(self.subnets)'
232+
- message: reservedSubnets must be unset when subnets is unset
233+
rule: '!has(self.reservedSubnets) || has(self.subnets)'
234+
- message: reservedSubnets is only supported for Primary network
235+
rule: '!has(self.reservedSubnets) || has(self.role) && self.role
236+
== ''Primary'''
237+
- message: infrastructureSubnets must be unset when subnets is unset
238+
rule: '!has(self.infrastructureSubnets) || has(self.subnets)'
239+
- message: infrastructureSubnets is only supported for Primary network
240+
rule: '!has(self.infrastructureSubnets) || has(self.role) && self.role
241+
== ''Primary'''
242+
- message: defaultGatewayIPs have to belong to infrastructureSubnets
243+
rule: '!has(self.infrastructureSubnets) || !has(self.defaultGatewayIPs)
244+
|| self.defaultGatewayIPs.all(ip, self.infrastructureSubnets.exists(subnet,
245+
cidr(subnet).containsIP(ip)))'
246+
- fieldPath: .reservedSubnets
247+
message: reservedSubnets must be subnetworks of the networks specified
248+
in the subnets field
249+
rule: '!has(self.reservedSubnets) || self.reservedSubnets.all(e,
250+
self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))'
251+
- fieldPath: .infrastructureSubnets
252+
message: infrastructureSubnets must be subnetworks of the networks
253+
specified in the subnets field
254+
rule: '!has(self.infrastructureSubnets) || self.infrastructureSubnets.all(e,
255+
self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))'
256+
- message: infrastructureSubnets and reservedSubnets must not overlap
257+
rule: '!has(self.infrastructureSubnets) || !has(self.reservedSubnets)
258+
|| self.infrastructureSubnets.all(infra, !self.reservedSubnets.exists(reserved,
259+
cidr(infra).containsCIDR(reserved) || cidr(reserved).containsCIDR(infra)))'
162260
layer3:
163261
description: Layer3 is the Layer3 topology configuration.
164262
properties:

go-controller/pkg/allocator/ip/allocator.go

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,24 @@ import (
2727
allocator "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/allocator/bitmap"
2828
)
2929

30-
// Interface manages the allocation of IP addresses out of a range. Interface
31-
// should be threadsafe.
32-
type Interface interface {
30+
// StaticAllocator provides IP allocation functionality for explicit/static allocations only.
31+
type StaticAllocator interface {
3332
Allocate(net.IP) error
34-
AllocateNext() (net.IP, error)
3533
Release(net.IP)
3634
ForEach(func(net.IP))
3735
CIDR() net.IPNet
3836
Has(ip net.IP) bool
3937
Reserved(ip net.IP) bool
4038
}
4139

40+
// ContinuousAllocator extends StaticAllocator with next-available allocation support.
41+
// This is the primary interface for IP allocation that supports both explicit
42+
// allocation and continuous allocation.
43+
type ContinuousAllocator interface {
44+
StaticAllocator
45+
AllocateNext() (net.IP, error)
46+
}
47+
4248
var (
4349
ErrFull = errors.New("subnet address pool exhausted")
4450
ErrAllocated = errors.New("provided IP is already allocated")
@@ -84,7 +90,30 @@ type Range struct {
8490
}
8591

8692
// NewAllocatorCIDRRange creates a Range over a net.IPNet, calling allocatorFactory to construct the backing store.
93+
// It excludes the network address (.0) and broadcast address (IPv4 only) from allocation.
8794
func NewAllocatorCIDRRange(cidr *net.IPNet, allocatorFactory allocator.AllocatorFactory) (*Range, error) {
95+
r, err := NewAllocatorFullCIDRRange(cidr, allocatorFactory)
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
if utilnet.IsIPv4CIDR(cidr) {
101+
// Don't use the IPv4 network's broadcast address.
102+
r.max--
103+
}
104+
// Don't use the network's ".0" address.
105+
r.base.Add(r.base, big.NewInt(1))
106+
r.max--
107+
108+
r.max = maximum(0, r.max)
109+
// Reconfigure the allocator to use the new max value
110+
r.alloc, err = allocatorFactory(r.max, r.net.String())
111+
return r, err
112+
}
113+
114+
// NewAllocatorFullCIDRRange creates a Range over a net.IPNet without excluding any IPs,
115+
// calling allocatorFactory to construct the backing store.
116+
func NewAllocatorFullCIDRRange(cidr *net.IPNet, allocatorFactory allocator.AllocatorFactory) (*Range, error) {
88117
max := utilnet.RangeSize(cidr)
89118
base := utilnet.BigForIP(cidr.IP)
90119
rangeSpec := cidr.String()
@@ -94,19 +123,11 @@ func NewAllocatorCIDRRange(cidr *net.IPNet, allocatorFactory allocator.Allocator
94123
if max > 65536 {
95124
max = 65536
96125
}
97-
} else {
98-
// Don't use the IPv4 network's broadcast address.
99-
max--
100126
}
101-
102-
// Don't use the network's ".0" address.
103-
base.Add(base, big.NewInt(1))
104-
max--
105-
106127
r := Range{
107128
net: cidr,
108129
base: base,
109-
max: maximum(0, int(max)),
130+
max: int(max),
110131
}
111132
var err error
112133
r.alloc, err = allocatorFactory(r.max, rangeSpec)
@@ -207,14 +228,37 @@ func (r *Range) Has(ip net.IP) bool {
207228
}
208229

209230
// Reserved returns true if the provided IP can't be allocated. This is *only*
210-
// true for the network and broadcast addresses.
231+
// true for the network and broadcast addresses of the original CIDR.
211232
func (r *Range) Reserved(ip net.IP) bool {
212233
if !r.net.Contains(ip) {
213234
return false
214235
}
215236

216-
offset := calculateIPOffset(r.base, ip)
217-
return offset == -1 || offset == r.max
237+
// For IPv4, reserve network (.0) and broadcast addresses
238+
if utilnet.IsIPv4CIDR(r.net) {
239+
// Network address is the base IP of the original CIDR
240+
networkAddr := r.net.IP
241+
if ip.Equal(networkAddr) {
242+
return true
243+
}
244+
245+
// Broadcast address is the last IP in the original CIDR
246+
rangeSize := utilnet.RangeSize(r.net)
247+
broadcastAddr, _ := utilnet.GetIndexedIP(r.net, int(rangeSize)-1)
248+
if ip.Equal(broadcastAddr) {
249+
return true
250+
}
251+
}
252+
253+
// For IPv6, only reserve the network address (no broadcast concept)
254+
if utilnet.IsIPv6CIDR(r.net) {
255+
networkAddr := r.net.IP
256+
if ip.Equal(networkAddr) {
257+
return true
258+
}
259+
}
260+
261+
return false
218262
}
219263

220264
// contains returns true and the offset if the ip is in the range, and false

0 commit comments

Comments
 (0)