Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for IP ranges exclusion for IPPool CRD #46

Merged
merged 1 commit into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,9 @@ spec:
subnet: 192.168.0.0/16
perNodeBlockSize: 100
gateway: 192.168.0.1
exclusions: # optional
- startIP: 192.168.0.10
endIP: 192.168.0.20
nodeSelector:
nodeSelectorTerms:
- matchExpressions:
Expand Down Expand Up @@ -374,6 +377,11 @@ spec:
* `subnet`: IP Subnet of the pool.
* `gateway` (optional): Gateway IP of the subnet.
* `perNodeBlockSize`: the number of IPs of IP Blocks allocated to Nodes.
* `exclusions` (optional, list): contains reserved IP addresses that should not be allocated by nv-ipam node component.

* `startIP`: start IP of the exclude range (inclusive).
* `endIP`: end IP of the exclude range (inclusive).

* `nodeSelector` (optional): A list of node selector terms. The terms are ORed. Each term can have a list of matchExpressions that are ANDed. Only the nodes that match the provided labels will get assigned IP Blocks for the defined pool.

> __Notes:__
Expand Down
26 changes: 24 additions & 2 deletions api/v1alpha1/ippool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ var _ = Describe("Validate", func() {
Spec: v1alpha1.IPPoolSpec{
Subnet: "192.168.0.0/16",
PerNodeBlockSize: 128,
Gateway: "192.168.0.1",
Exclusions: []v1alpha1.ExcludeRange{
{StartIP: "192.168.0.100", EndIP: "192.168.0.110"},
},
Gateway: "192.168.0.1",
NodeSelector: &corev1.NodeSelector{
NodeSelectorTerms: []corev1.NodeSelectorTerm{{
MatchExpressions: []corev1.NodeSelectorRequirement{{
Expand All @@ -49,7 +52,10 @@ var _ = Describe("Validate", func() {
Spec: v1alpha1.IPPoolSpec{
Subnet: "2001:db8:3333:4444::0/64",
PerNodeBlockSize: 1000,
Gateway: "2001:db8:3333:4444::1",
Exclusions: []v1alpha1.ExcludeRange{
{StartIP: "2001:db8:3333:4444::3", EndIP: "2001:db8:3333:4444::4"},
},
Gateway: "2001:db8:3333:4444::1",
},
}
Expect(ipPool.Validate()).To(BeEmpty())
Expand Down Expand Up @@ -98,6 +104,22 @@ var _ = Describe("Validate", func() {
ContainSubstring("spec.perNodeBlockSize"),
)
})
It("Invalid - exclusions not part of the subnet", func() {
ipPool := v1alpha1.IPPool{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Spec: v1alpha1.IPPoolSpec{
Subnet: "192.168.0.0/24",
Exclusions: []v1alpha1.ExcludeRange{
{StartIP: "10.10.10.10", EndIP: "10.10.10.20"},
},
PerNodeBlockSize: 10,
},
}
Expect(ipPool.Validate().ToAggregate().Error()).
To(
ContainSubstring("spec.exclusions"),
)
})
It("Invalid - gateway outside of the subnet", func() {
ipPool := v1alpha1.IPPool{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha1/ippool_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type IPPoolSpec struct {
// amount of IPs to allocate for each node,
// must be less than amount of available IPs in the subnet
PerNodeBlockSize int `json:"perNodeBlockSize"`
// contains reserved IP addresses that should not be allocated by nv-ipam
Exclusions []ExcludeRange `json:"exclusions,omitempty"`
// gateway for the pool
Gateway string `json:"gateway,omitempty"`
// selector for nodes, if empty match all nodes
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/ippool_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func (r *IPPool) Validate() field.ErrorList {
"is larger then amount of IPs available in the subnet"))
}
}
if network != nil {
errList = append(errList, validateExclusions(network, r.Spec.Exclusions, field.NewPath("spec"))...)
}
var parsedGW net.IP
if r.Spec.Gateway != "" {
parsedGW = net.ParseIP(r.Spec.Gateway)
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions deploy/crds/nv-ipam.nvidia.com_ippools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ spec:
spec:
description: IPPoolSpec contains configuration for IP pool
properties:
exclusions:
description: contains reserved IP addresses that should not be allocated
by nv-ipam
items:
description: ExcludeRange contains range of IP addresses to exclude
from allocation startIP and endIP are part of the ExcludeRange
properties:
endIP:
type: string
startIP:
type: string
required:
- endIP
- startIP
type: object
type: array
gateway:
description: gateway for the pool
type: string
Expand Down
3 changes: 3 additions & 0 deletions examples/ippool-1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ metadata:
spec:
subnet: 192.168.0.0/16
perNodeBlockSize: 128
exclusions: # optional
- startIP: 192.168.0.10
endIP: 192.168.0.20
gateway: 192.168.0.1
nodeSelector:
nodeSelectorTerms:
Expand Down
16 changes: 10 additions & 6 deletions pkg/ipam-node/controllers/ippool/ippool.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,19 @@ func (r *IPPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
}
reqLog.Info("Notification on IPPool", "name", ipPool.Name)
found := false

for _, alloc := range ipPool.Status.Allocations {
if alloc.NodeName == r.NodeName {
exclusions := make([]pool.ExclusionRange, 0, len(ipPool.Spec.Exclusions))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we don't need additional logic here. The node's allocator implementation will reject configurations where exclusions are outside of the node's subnet. In the case of IPPool, all nodes use the same subnet, so any exclusions that match the pool's subnet are valid for all nodes. However, with CIDRPool, each node has a unique subnet, so we need to filter the pool's exclusions and only pass valid exclusions to the node's allocator.

for _, e := range ipPool.Spec.Exclusions {
exclusions = append(exclusions, pool.ExclusionRange{StartIP: e.StartIP, EndIP: e.EndIP})
}
ipPool := &pool.Pool{
Name: ipPool.Name,
Subnet: ipPool.Spec.Subnet,
Gateway: ipPool.Spec.Gateway,
StartIP: alloc.StartIP,
EndIP: alloc.EndIP,
Name: ipPool.Name,
Subnet: ipPool.Spec.Subnet,
Gateway: ipPool.Spec.Gateway,
StartIP: alloc.StartIP,
EndIP: alloc.EndIP,
Exclusions: exclusions,
}
r.PoolManager.UpdatePool(poolKey, ipPool)
found = true
Expand Down
Loading