Skip to content

Commit

Permalink
Add example CIDRPool CR
Browse files Browse the repository at this point in the history
Signed-off-by: Yury Kulazhenkov <ykulazhenkov@nvidia.com>
  • Loading branch information
ykulazhenkov committed Jun 11, 2024
1 parent 012a125 commit cc6ed19
Show file tree
Hide file tree
Showing 9 changed files with 407 additions and 460 deletions.
700 changes: 290 additions & 410 deletions api/v1alpha1/cidrpool_test.go

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions api/v1alpha1/cidrpool_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ type CIDRPool struct {

// CIDRPoolSpec contains configuration for CIDR pool
type CIDRPoolSpec struct {
// pool CIDR block which will be split to smaller prefixes(size is define in perNodePrefixSize)
// pool CIDR block which will be split to smaller prefixes(size is define in perNodeNetworkPrefix)
// and distributed between matching nodes
CIDR string `json:"cidr"`
// 0 - no gateway, non zero value - automatically use IP with this index from the host prefix as a gateway
GatewayIndex uint `json:"gatewayIndex,omitempty"`
// size of the network prefix for each host, the network defined in "cidr" field will be slitted to multiple networks
// use IP with this index from the host prefix as a gateway, skip gateway configuration if the value not set
GatewayIndex *uint `json:"gatewayIndex,omitempty"`
// size of the network prefix for each host, the network defined in "cidr" field will be split to multiple networks
// with this size.
PerNodeNetworkPrefix uint `json:"perNodeNetworkPrefix"`
// contains reserved IP addresses that should not be allocated by nv-ipam
Expand Down
13 changes: 8 additions & 5 deletions api/v1alpha1/cidrpool_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ func (r *CIDRPool) validateCIDR() field.ErrorList {
r.Spec.PerNodeNetworkPrefix < uint(setBits) {
return field.ErrorList{field.Invalid(
field.NewPath("spec", "perNodeNetworkPrefix"),
r.Spec.PerNodeNetworkPrefix, "must be less or equal then network prefix size in the \"cidr\" field")}
r.Spec.PerNodeNetworkPrefix, "must be less or equal than network prefix size in the \"cidr\" field")}
}

errList := field.ErrorList{}
firstNodePrefix := &net.IPNet{IP: network.IP, Mask: net.CIDRMask(int(r.Spec.PerNodeNetworkPrefix), bitsTotal)}
if r.Spec.GatewayIndex != 0 && GetGatewayForSubnet(firstNodePrefix, r.Spec.GatewayIndex) == "" {
if r.Spec.GatewayIndex != nil && GetGatewayForSubnet(firstNodePrefix, *r.Spec.GatewayIndex) == "" {
errList = append(errList, field.Invalid(
field.NewPath("spec", "gatewayIndex"),
r.Spec.GatewayIndex, "gateway index is larger then amount of IPs available in the node prefix"))
r.Spec.GatewayIndex, "gateway index is outside of the node prefix"))
}
errList = append(errList, validateExclusions(network, r.Spec.Exclusions, field.NewPath("spec"))...)
errList = append(errList, r.validateStaticAllocations(network)...)
Expand Down Expand Up @@ -115,7 +115,7 @@ func (r *CIDRPool) validateStaticAllocations(cidr *net.IPNet) field.ErrorList {
if parentCIDRTotalBits != nodePrefixTotalBits {
errList = append(errList, field.Invalid(
field.NewPath("spec", "staticAllocations").Index(i).Child("prefix"), alloc.Prefix,
"ip family is not match with the pool cidr"))
"ip family doesn't match the pool cidr"))
continue
}
if nodePrefixOnes != int(r.Spec.PerNodeNetworkPrefix) {
Expand Down Expand Up @@ -185,7 +185,10 @@ func (a *CIDRPoolAllocation) Validate(pool *CIDRPool) field.ErrorList {
return field.ErrorList{field.Invalid(field.NewPath("prefix"), a.Prefix, "network prefix has host bits set")}
}

computedGW := GetGatewayForSubnet(prefixNetwork, pool.Spec.GatewayIndex)
computedGW := ""
if pool.Spec.GatewayIndex != nil {
computedGW = GetGatewayForSubnet(prefixNetwork, *pool.Spec.GatewayIndex)
}

// check static allocations first
for _, staticAlloc := range pool.Spec.StaticAllocations {
Expand Down
46 changes: 28 additions & 18 deletions api/v1alpha1/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,51 @@ import (

// GetGatewayForSubnet returns computed gateway for subnet as string, index 0 means no gw
// Examples:
// - subnet 192.168.0.0/24, index 1, gateway = 192.168.1.1 (skip network IP)
// - subnet 192.168.1.2/31, index 1, gateway = 192.168.1.2 (use network IPs for point to point prefixes /31, /127)
// - subnet 192.168.0.0/24, index = 0, gateway = "" (invalid config, can't use network address as gateway )
// - subnet 192.168.0.0/24, index 1, gateway = 192.168.1.1
// - subnet 192.168.0.0/24, index 10, gateway = 102.168.1.10
// - subnet 192.168.1.2/31, index 0, gateway = 192.168.1.2 (point to point network can use network IP as a gateway)
// - subnet 192.168.33.0/24, index 900, gateway = "" (invalid config, index too large)
// - subnet 192.168.33.0/24, index 0, gateway = "" (no gw)
func GetGatewayForSubnet(subnet *net.IPNet, index uint) string {
if index == 0 {
setBits, bitsTotal := subnet.Mask.Size()
maxIndex := GetPossibleIPCount(subnet)

if setBits >= bitsTotal-1 {
// point to point or single IP
maxIndex.Sub(maxIndex, big.NewInt(1))
} else if index == 0 {
// index 0 can be used only for point to point or single IP networks
return ""
}
if getPossibleIPCount(subnet).Cmp(big.NewInt(int64(index))) < 0 {
if maxIndex.Cmp(big.NewInt(int64(index))) < 0 {
// index too large
return ""
}
setBits, bitsTotal := subnet.Mask.Size()
if setBits == bitsTotal-1 {
// point to point prefix
index--
}
gwIP := ip.NextIPWithOffset(subnet.IP, int64(index))
if gwIP == nil {
return ""
}
return gwIP.String()
}

func getPossibleIPCount(subnet *net.IPNet) *big.Int {
// GetPossibleIPCount returns count of IP addresses for hosts in the provided subnet.
// for IPv4: returns amount of IPs - 2 (subnet address and broadcast address)
// for IPv6: returns amount of IPs - 1 (subnet address)
// for point to point subnets(/31 and /127) returns 2
func GetPossibleIPCount(subnet *net.IPNet) *big.Int {
setBits, bitsTotal := subnet.Mask.Size()
isPointToPoint := setBits == bitsTotal-1
if setBits == bitsTotal {
return big.NewInt(1)
}
if setBits == bitsTotal-1 {
// point to point
return big.NewInt(2)
}
ipCount := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(int64(bitsTotal-setBits)), nil)
if !isPointToPoint {
if subnet.IP.To4() != nil {
ipCount.Sub(ipCount, big.NewInt(2))
} else {
ipCount.Sub(ipCount, big.NewInt(1))
}
if subnet.IP.To4() != nil {
ipCount.Sub(ipCount, big.NewInt(2))
} else {
ipCount.Sub(ipCount, big.NewInt(1))
}
return ipCount
}
63 changes: 45 additions & 18 deletions api/v1alpha1/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,49 @@ import (
)

var _ = Describe("Helpers", func() {
Context("GetGatewayForSubnet", func() {
It("Valid IPv4", func() {
_, network, _ := net.ParseCIDR("192.168.1.0/24")
Expect(v1alpha1.GetGatewayForSubnet(network, 33)).To(Equal("192.168.1.33"))
})
It("Valid IPv6", func() {
_, network, _ := net.ParseCIDR("2001:db8:3333:4444::0/64")
Expect(v1alpha1.GetGatewayForSubnet(network, 10)).To(Equal("2001:db8:3333:4444::a"))
})
It("no gateway", func() {
_, network, _ := net.ParseCIDR("192.168.1.0/24")
Expect(v1alpha1.GetGatewayForSubnet(network, 0)).To(BeEmpty())
})
It("out of network range", func() {
_, network, _ := net.ParseCIDR("192.168.1.0/24")
Expect(v1alpha1.GetGatewayForSubnet(network, 300)).To(BeEmpty())
})
})
DescribeTable("GetGatewayForSubnet",
func(subnet string, index int, gw string) {
_, network, err := net.ParseCIDR(subnet)
Expect(err).NotTo(HaveOccurred())
ret := v1alpha1.GetGatewayForSubnet(network, uint(index))
Expect(ret).To(Equal(gw))
},
Entry("ipv4 - start range", "192.168.1.0/24", 1, "192.168.1.1"),
Entry("ipv4 - mid range", "192.168.1.0/24", 33, "192.168.1.33"),
Entry("ipv4 - end range", "192.168.1.0/24", 254, "192.168.1.254"),
Entry("ipv4 - out of range, can't use index 0", "192.168.1.0/24", 0, ""),
Entry("ipv4 - out of range, too big", "192.168.1.0/24", 255, ""),
Entry("ipv4 - single IP", "192.168.1.100/32", 0, "192.168.1.100"),
Entry("ipv4 - single IP, out of range", "192.168.1.100/32", 1, ""),
Entry("ipv4 - point to point, can use index 0", "192.168.1.0/31", 0, "192.168.1.0"),
Entry("ipv4 - point to point, can use index 1", "192.168.1.0/31", 1, "192.168.1.1"),
Entry("ipv4 - point to point, out of range", "192.168.1.0/31", 2, ""),
Entry("ipv6 - start range", "2001:db8:3333:4444::0/120", 1, "2001:db8:3333:4444::1"),
Entry("ipv6 - mid range", "2001:db8:3333:4444::0/120", 10, "2001:db8:3333:4444::a"),
Entry("ipv6 - end range", "2001:db8:3333:4444::0/120", 255, "2001:db8:3333:4444::ff"),
Entry("ipv6 - out of range, can't use index 0", "2001:db8:3333:4444::0/120", 0, ""),
Entry("ipv6 - out of range, too big", "2001:db8:3333:4444::0/120", 256, ""),
Entry("ipv6 - point to point, can use index 0", "2001:db8:3333:4444::0/127", 0, "2001:db8:3333:4444::"),
Entry("ipv6 - point to point, can use index 1", "2001:db8:3333:4444::0/127", 1, "2001:db8:3333:4444::1"),
Entry("ipv6 - point to point, out of range", "2001:db8:3333:4444::0/127", 2, ""),
)
DescribeTable("GetPossibleIPCount",
func(subnet string, count int) {
_, network, err := net.ParseCIDR(subnet)
Expect(err).NotTo(HaveOccurred())
ret := v1alpha1.GetPossibleIPCount(network)
Expect(ret).NotTo(BeNil())
Expect(ret.IsUint64()).To(BeTrue())
Expect(ret.Uint64()).To(Equal(uint64(count)))
},
Entry("ipv4 /24", "192.168.0.0/24", 254),
Entry("ipv4 /25", "192.168.0.0/25", 126),
Entry("ipv4 /16", "192.168.0.0/16", 65534),
Entry("ipv4 /31", "192.20.20.0/31", 2),
Entry("ipv4 /32", "10.10.10.10/32", 1),
Entry("ipv6 /124", "2001:db8:85a3::8a2e:370:7334/120", 255),
Entry("ipv6 /96", "2001:db8:85a3::8a2e:370:7334/96", 4294967295),
Entry("ipv6 /127", "2001:db8:85a3::8a2e:370:7334/127", 2),
Entry("ipv6 /128", "2001:db8:85a3::8a2e:370:7334/128", 1),
)
})
2 changes: 1 addition & 1 deletion api/v1alpha1/ippool_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (r *IPPool) Validate() field.ErrorList {
}

if network != nil && r.Spec.PerNodeBlockSize >= 2 {
if getPossibleIPCount(network).Cmp(big.NewInt(int64(r.Spec.PerNodeBlockSize))) < 0 {
if GetPossibleIPCount(network).Cmp(big.NewInt(int64(r.Spec.PerNodeBlockSize))) < 0 {
// config is not valid even if only one node exist in the cluster
errList = append(errList, field.Invalid(
field.NewPath("spec", "perNodeBlockSize"), r.Spec.PerNodeBlockSize,
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.

8 changes: 4 additions & 4 deletions deploy/crds/nv-ipam.nvidia.com_cidrpools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ spec:
properties:
cidr:
description: pool CIDR block which will be split to smaller prefixes(size
is define in perNodePrefixSize) and distributed between matching
is define in perNodeNetworkPrefix) and distributed between matching
nodes
type: string
exclusions:
Expand All @@ -66,8 +66,8 @@ spec:
type: object
type: array
gatewayIndex:
description: 0 - no gateway, non zero value - automatically use IP
with this index from the host prefix as a gateway
description: use IP with this index from the host prefix as a gateway,
skip gateway configuration if the value not set
type: integer
nodeSelector:
description: selector for nodes, if empty match all nodes
Expand Down Expand Up @@ -155,7 +155,7 @@ spec:
x-kubernetes-map-type: atomic
perNodeNetworkPrefix:
description: size of the network prefix for each host, the network
defined in "cidr" field will be slitted to multiple networks with
defined in "cidr" field will be split to multiple networks with
this size.
type: integer
staticAllocations:
Expand Down
22 changes: 22 additions & 0 deletions examples/cidrpool-1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: nv-ipam.nvidia.com/v1alpha1
kind: CIDRPool
metadata:
name: pool1
namespace: kube-system
spec:
cidr: 192.168.0.0/16
gatewayIndex: 1
perNodeNetworkPrefix: 24
exclusions: # optional
- startIP: 192.168.0.10
endIP: 192.168.0.20
staticAllocations: # optional
- nodeName: node-33
prefix: 192.168.33.0/24
gateway: 192.168.33.10
- prefix: 192.168.1.0/24
nodeSelector: # optional
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/worker
operator: Exists

0 comments on commit cc6ed19

Please sign in to comment.