diff --git a/pkg/ipam/core/core_suite_test.go b/pkg/ipam/core/core_suite_test.go
new file mode 100644
index 0000000000..75ba3b46d0
--- /dev/null
+++ b/pkg/ipam/core/core_suite_test.go
@@ -0,0 +1,27 @@
+// Copyright 2019-2024 The Liqo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ipamcore_test
+
+import (
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+func TestCore(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "Core Suite")
+}
diff --git a/pkg/ipam/core/coverage/coverage.html b/pkg/ipam/core/coverage/coverage.html
new file mode 100644
index 0000000000..0fd30ad780
--- /dev/null
+++ b/pkg/ipam/core/coverage/coverage.html
@@ -0,0 +1,866 @@
+
+
+
+
+
+
+
// Copyright 2019-2024 The Liqo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ipamcore
+
+import (
+ "fmt"
+ "net/netip"
+ "time"
+)
+
+// Ipam represents the IPAM core structure.
+type Ipam struct {
+ roots []node
+}
+
+// NewIpam creates a new IPAM instance.
+func NewIpam(pools []netip.Prefix) (*Ipam, error) {
+ if err := checkRoots(pools); err != nil {
+ return nil, err
+ }
+
+ ipamRoots := make([]node, len(pools))
+ for i := range pools {
+ ipamRoots[i] = newNode(pools[i])
+ }
+
+ ipam := &Ipam{
+ roots: ipamRoots,
+ }
+
+ return ipam, nil
+}
+
+// NetworkAcquire allocates a network of the given size.
+// It returns the allocated network or nil if no network is available.
+func (ipam *Ipam) NetworkAcquire(size int) *netip.Prefix {
+ for i := range ipam.roots {
+ if result := allocateNetwork(size, &ipam.roots[i]); result != nil {
+ return result
+ }
+ }
+ return nil
+}
+
+// NetworkAcquireWithPrefix allocates a network with the given prefix.
+// It returns the allocated network or nil if the network is not available.
+func (ipam *Ipam) NetworkAcquireWithPrefix(prefix netip.Prefix) *netip.Prefix {
+ for i := range ipam.roots {
+ if isPrefixChildOf(ipam.roots[i].prefix, prefix) {
+ if result := allocateNetworkWithPrefix(prefix, &ipam.roots[i]); result != nil {
+ return result
+ }
+ }
+ }
+ return nil
+}
+
+// NetworkRelease frees the network with the given prefix.
+// It returns the freed network or nil if the network is not found.
+func (ipam *Ipam) NetworkRelease(prefix netip.Prefix, gracePeriod time.Duration) *netip.Prefix {
+ for i := range ipam.roots {
+ if isPrefixChildOf(ipam.roots[i].prefix, prefix) {
+ if result := networkRelease(prefix, &ipam.roots[i], gracePeriod); result != nil {
+ return result
+ }
+ }
+ }
+ return nil
+}
+
+// ListNetworks returns the list of allocated networks.
+func (ipam *Ipam) ListNetworks() []netip.Prefix {
+ var networks []netip.Prefix
+ for i := range ipam.roots {
+ networks = append(networks, listNetworks(&ipam.roots[i])...)
+ }
+ return networks
+}
+
+// NetworkIsAvailable checks if the network with the given prefix is allocated.
+// It returns false if the network is allocated or there is no suitable pool, true otherwise.
+func (ipam *Ipam) NetworkIsAvailable(prefix netip.Prefix) bool {
+ for i := range ipam.roots {
+ if isPrefixChildOf(ipam.roots[i].prefix, prefix) {
+ return networkIsAvailable(prefix, &ipam.roots[i])
+ }
+ }
+ return false
+}
+
+// IPAcquire allocates an IP address from the given prefix.
+// It returns the allocated IP address or nil if the IP address is not available.
+func (ipam *Ipam) IPAcquire(prefix netip.Prefix) (*netip.Addr, error) {
+ node, err := ipam.search(prefix)
+ if err != nil {
+ return nil, err
+ }
+ if node != nil {
+ return node.ipAcquire(), nil
+ }
+
+ return nil, nil
+}
+
+// IPAcquireWithAddr allocates the IP address from the given prefix.
+// It returns the allocated IP address or nil if the IP address is not available.
+func (ipam *Ipam) IPAcquireWithAddr(prefix netip.Prefix, addr netip.Addr) (*netip.Addr, error) {
+ if !prefix.Contains(addr) {
+ return nil, fmt.Errorf("address %s is not contained in prefix %s", addr, prefix)
+ }
+ node, err := ipam.search(prefix)
+ if err != nil {
+ return nil, err
+ }
+ if node != nil {
+ return node.allocateIPWithAddr(addr), nil
+ }
+ return nil, nil
+}
+
+// IPRelease frees the IP address from the given prefix.
+// It returns the freed IP address or nil if the IP address is not found.
+func (ipam *Ipam) IPRelease(prefix netip.Prefix, addr netip.Addr, gracePeriod time.Duration) (*netip.Addr, error) {
+ node, err := ipam.search(prefix)
+ if err != nil {
+ return nil, err
+ }
+ if node != nil {
+ return node.ipRelease(addr, gracePeriod), nil
+ }
+ return nil, nil
+}
+
+// ListIPs returns the list of allocated IP addresses from the given prefix.
+func (ipam *Ipam) ListIPs(prefix netip.Prefix) ([]netip.Addr, error) {
+ node, err := ipam.search(prefix)
+ if err != nil {
+ return nil, err
+ }
+ if node != nil {
+ addrs := make([]netip.Addr, len(node.ips))
+ for i := range node.ips {
+ addrs[i] = node.ips[i].addr
+ }
+ return addrs, nil
+ }
+ return nil, nil
+}
+
+// IPIsAllocated checks if the IP address is allocated from the given prefix.
+// It returns true if the IP address is allocated, false otherwise.
+func (ipam *Ipam) IPIsAllocated(prefix netip.Prefix, addr netip.Addr) (bool, error) {
+ node, err := ipam.search(prefix)
+ if err != nil {
+ return false, err
+ }
+ if node != nil {
+ return node.isAllocatedIP(addr), nil
+ }
+ return false, nil
+}
+
+// IsPrefixInRoots checks if the given prefix is contained in the roots.
+// It returns true if the prefix is contained, false otherwise.
+func (ipam *Ipam) IsPrefixInRoots(prefix netip.Prefix) bool {
+ for i := range ipam.roots {
+ if isPrefixChildOf(ipam.roots[i].prefix, prefix) {
+ return true
+ }
+ }
+ return false
+}
+
+// ToGraphviz generates the Graphviz representation of the IPAM structure.
+func (ipam *Ipam) ToGraphviz() error {
+ for i := range ipam.roots {
+ _ = i
+ if err := ipam.roots[i].toGraphviz(); err != nil {
+ return fmt.Errorf("failed to generate Graphviz representation: %w", err)
+ }
+ }
+ return nil
+}
+
+func (ipam *Ipam) search(prefix netip.Prefix) (*node, error) {
+ for i := range ipam.roots {
+ if !isPrefixChildOf(ipam.roots[i].prefix, prefix) {
+ continue
+ }
+ if node := search(prefix, &ipam.roots[i]); node != nil {
+ return node, nil
+ }
+ return nil, nil
+ }
+ return nil, fmt.Errorf("prefix %s not contained in roots", prefix)
+}
+
+func checkRoots(roots []netip.Prefix) error {
+ for i := range roots {
+ if err := checkHostBitsZero(roots[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// NetworkSetLastUpdateTimestamp sets the last update time of the network with the given prefix.
+// This function is for testing purposes only.
+func (ipam *Ipam) NetworkSetLastUpdateTimestamp(prefix netip.Prefix, lastUpdateTimestamp time.Time) error {
+ node, err := ipam.search(prefix)
+ if err != nil {
+ return err
+ }
+ if node == nil {
+ return fmt.Errorf("prefix %s not found", prefix)
+ }
+ node.lastUpdateTimestamp = lastUpdateTimestamp
+ return nil
+}
+
+// IPSetCreationTimestamp sets the creation timestamp of the IP address with the given address.
+// This function is for testing purposes only.
+func (ipam *Ipam) IPSetCreationTimestamp(addr netip.Addr, prefix netip.Prefix, creationTimestamp time.Time) error {
+ node, err := ipam.search(prefix)
+ if err != nil {
+ return err
+ }
+ if node == nil {
+ return fmt.Errorf("prefix %s not found", prefix)
+ }
+
+ for i := range node.ips {
+ if node.ips[i].addr.Compare(addr) == 0 {
+ node.ips[i].creationTimestamp = creationTimestamp
+ return nil
+ }
+ }
+ return nil
+}
+
+
+
// Copyright 2019-2024 The Liqo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ipamcore
+
+import (
+ "fmt"
+ "net/netip"
+ "strings"
+)
+
+// convertByteSliceToString converts a slice of bytes to a comma-separated string.
+func convertByteSliceToString(byteSlice []byte) string {
+ strSlice := make([]string, len(byteSlice))
+ for i, b := range byteSlice {
+ strSlice[i] = fmt.Sprintf("%d", b)
+ }
+ return strings.Join(strSlice, ".")
+}
+
+// setBit sets the bit at the given position to 1.
+func setBit(b, position byte) byte {
+ if position > 7 {
+ fmt.Println("Bit position out of range")
+ return b
+ }
+ return b | (1 << (7 - position))
+}
+
+func checkHostBitsZero(prefix netip.Prefix) error {
+ if prefix.Masked().Addr().Compare(prefix.Addr()) != 0 {
+ return fmt.Errorf("%s :host bits must be zero", prefix)
+ }
+ return nil
+}
+
+// splitNetworkPrefix splits a network prefix into two subnets.
+// It increases the prefix length by one and sets the bit at
+// the new position to 0 or 1 to retrieve the two subnets.
+func splitNetworkPrefix(prefix netip.Prefix) (left, right netip.Prefix) {
+ // We neer to check that the host bits are zero.
+ if err := checkHostBitsZero(prefix); err != nil {
+ panic("Host bits must be zero")
+ }
+
+ // We need to convert the prefix to a byte slice to manipulate it.
+ bin, err := prefix.MarshalBinary()
+ if err != nil {
+ panic(err)
+ }
+
+ // We need to get the mask length to know where to split the prefix.
+ maskLen := bin[len(bin)-1]
+
+ // Since the prefix host bits are zero, we just need to shift
+ // the mask length by one to get the first splitted prefix.
+ left = netip.MustParsePrefix(
+ fmt.Sprintf("%s/%d", convertByteSliceToString(bin[:4]), maskLen+1),
+ )
+
+ // We need to set the bit at the mask length position to 1 to get the second splitted prefix.
+ // Since the IP is expressed like a slice of bytes, we need to get the byte index and the bit index to set the bit.
+ byteIndex := maskLen / 8
+ bitIndex := maskLen % 8
+
+ // We set the bit at the mask length position to 1.
+ bin[byteIndex] = setBit(bin[byteIndex], bitIndex)
+
+ // We forge and return the second splitted prefix.
+ right = netip.MustParsePrefix(
+ fmt.Sprintf("%s/%d", convertByteSliceToString(bin[:4]), maskLen+1),
+ )
+
+ return left, right
+}
+
+// isPrefixChildOf checks if the child prefix is a child of the parent prefix.
+func isPrefixChildOf(parent, child netip.Prefix) bool {
+ if parent.Bits() <= child.Bits() && parent.Overlaps(child) {
+ return true
+ }
+ return false
+}
+
+
+
// Copyright 2019-2024 The Liqo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ipamcore
+
+import (
+ "fmt"
+ "math"
+ "net/netip"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+// nodeIP represents an IP address acquired by a node.
+type nodeIP struct {
+ addr netip.Addr
+ creationTimestamp time.Time
+}
+
+// node represents a node in the binary tree.
+type node struct {
+ lastUpdateTimestamp time.Time
+
+ prefix netip.Prefix
+ acquired bool
+ left *node
+ right *node
+
+ ips []nodeIP
+ lastip netip.Addr
+}
+
+type nodeDirection string
+
+const (
+ leftDirection nodeDirection = "left"
+ rightDirection nodeDirection = "right"
+
+ graphvizFolder = "./graphviz"
+)
+
+func newNode(prefix netip.Prefix) node {
+ return node{prefix: prefix, lastUpdateTimestamp: time.Now()}
+}
+
+func allocateNetwork(size int, node *node) *netip.Prefix {
+ if node.acquired || node.prefix.Bits() > size {
+ return nil
+ }
+ if node.prefix.Bits() == size {
+ if !node.isSplitted() {
+ node.acquired = true
+ node.lastUpdateTimestamp = time.Now()
+ return &node.prefix
+ }
+ return nil
+ }
+
+ if !node.isSplitted() {
+ node.split()
+ }
+
+ first, second := node.bestDirection()
+
+ if prefix := allocateNetwork(size, node.next(first)); prefix != nil {
+ return prefix
+ }
+ return allocateNetwork(size, node.next(second))
+}
+
+func allocateNetworkWithPrefix(prefix netip.Prefix, node *node) *netip.Prefix {
+ if node.acquired || !isPrefixChildOf(node.prefix, prefix) {
+ return nil
+ }
+ if node.prefix.Addr().Compare(prefix.Addr()) == 0 && node.prefix.Bits() == prefix.Bits() {
+ if !node.acquired && node.left == nil && node.right == nil {
+ node.acquired = true
+ node.lastUpdateTimestamp = time.Now()
+ return &node.prefix
+ }
+ return nil
+ }
+
+ if !node.isSplitted() {
+ node.split()
+ }
+
+ if node.left != nil && node.left.prefix.Overlaps(prefix) {
+ return allocateNetworkWithPrefix(prefix, node.left)
+ }
+ if node.right != nil && node.right.prefix.Overlaps(prefix) {
+ return allocateNetworkWithPrefix(prefix, node.right)
+ }
+
+ return nil
+}
+
+func networkRelease(prefix netip.Prefix, node *node, gracePeriod time.Duration) *netip.Prefix {
+ var result *netip.Prefix
+
+ if node == nil {
+ return nil
+ }
+
+ if node.prefix.Addr().Compare(prefix.Addr()) == 0 && node.prefix.Bits() == prefix.Bits() &&
+ node.lastUpdateTimestamp.Add(gracePeriod).Before(time.Now()) {
+ if node.acquired {
+ node.acquired = false
+ node.lastUpdateTimestamp = time.Now()
+ return &node.prefix
+ }
+ return nil
+ }
+ if node.left != nil && isPrefixChildOf(node.left.prefix, prefix) {
+ result = networkRelease(prefix, node.left, gracePeriod)
+ }
+ if node.right != nil && isPrefixChildOf(node.right.prefix, prefix) {
+ result = networkRelease(prefix, node.right, gracePeriod)
+ }
+
+ node.merge(gracePeriod)
+ return result
+}
+
+func networkIsAvailable(prefix netip.Prefix, node *node) bool {
+ if node.prefix.Addr().Compare(prefix.Addr()) == 0 && node.prefix.Bits() == prefix.Bits() {
+ if node.left != nil && (node.left.isSplitted() || node.left.acquired) {
+ return false
+ }
+ if node.right != nil && (node.right.isSplitted() || node.right.acquired) {
+ return false
+ }
+
+ // If node children are not splitted and node is not acquired, then network is available
+ return !node.acquired
+ }
+
+ if node.left == nil && node.right == nil {
+ return true
+ }
+
+ if node.left != nil && isPrefixChildOf(node.left.prefix, prefix) && !node.left.acquired {
+ return networkIsAvailable(prefix, node.left)
+ }
+ if node.right != nil && isPrefixChildOf(node.right.prefix, prefix) && !node.right.acquired {
+ return networkIsAvailable(prefix, node.right)
+ }
+
+ return false
+}
+
+func listNetworks(node *node) []netip.Prefix {
+ if node == nil {
+ return nil
+ }
+
+ if node.acquired {
+ return []netip.Prefix{node.prefix}
+ }
+
+ var networks []netip.Prefix
+ if node.left != nil {
+ networks = append(networks, listNetworks(node.left)...)
+ }
+ if node.right != nil {
+ networks = append(networks, listNetworks(node.right)...)
+ }
+
+ return networks
+}
+
+func (n *node) isAllocatedIP(ip netip.Addr) bool {
+ for i := range n.ips {
+ if n.ips[i].addr.Compare(ip) == 0 {
+ return true
+ }
+ }
+ return false
+}
+
+func (n *node) ipAcquire() *netip.Addr {
+ if !n.acquired {
+ return nil
+ }
+
+ size := int(math.Pow(2, float64(n.prefix.Addr().BitLen()-n.prefix.Bits())))
+
+ // If the lastip is not initialized, set it to the first address of the prefix.
+ if !n.lastip.IsValid() {
+ n.lastip = n.prefix.Addr()
+ }
+
+ addr := n.lastip
+
+ if n.lastip.Compare(n.prefix.Addr()) != 0 {
+ addr = addr.Next()
+ }
+
+ for i := 0; i < size; i++ {
+ // we need to check if the address is contained in the prefix.
+ // If it is not, we need to set it to the first address of the prefix to prevent overflow.
+ if !n.prefix.Contains(addr) {
+ addr = n.prefix.Addr()
+ }
+ if !n.isAllocatedIP(addr) {
+ n.ips = append(n.ips, nodeIP{addr: addr, creationTimestamp: time.Now()})
+ n.lastip = addr
+ n.lastUpdateTimestamp = time.Now()
+ return &addr
+ }
+ addr = addr.Next()
+ }
+ return nil
+}
+
+func (n *node) allocateIPWithAddr(addr netip.Addr) *netip.Addr {
+ if !n.acquired {
+ return nil
+ }
+
+ if !n.prefix.Contains(addr) {
+ return nil
+ }
+
+ for i := range n.ips {
+ if n.ips[i].addr.Compare(addr) == 0 {
+ return nil
+ }
+ }
+
+ n.ips = append(n.ips, nodeIP{addr: addr, creationTimestamp: time.Now()})
+ n.lastUpdateTimestamp = time.Now()
+
+ return &n.ips[len(n.ips)-1].addr
+}
+
+func (n *node) ipRelease(ip netip.Addr, gracePeriod time.Duration) *netip.Addr {
+ if !n.acquired {
+ return nil
+ }
+
+ for i, nodeIP := range n.ips {
+ if !nodeIP.creationTimestamp.Add(gracePeriod).Before(time.Now()) {
+ continue
+ }
+ if nodeIP.addr.Compare(ip) == 0 {
+ n.ips = append(n.ips[:i], n.ips[i+1:]...)
+ n.lastUpdateTimestamp = time.Now()
+ return &nodeIP.addr
+ }
+ }
+ return nil
+}
+
+func search(prefix netip.Prefix, node *node) *node {
+ if node == nil {
+ return nil
+ }
+
+ if node.prefix.Addr().Compare(prefix.Addr()) == 0 && node.prefix.Bits() == prefix.Bits() {
+ return node
+ }
+
+ if node.left != nil && isPrefixChildOf(node.left.prefix, prefix) {
+ return search(prefix, node.left)
+ }
+ if node.right != nil && isPrefixChildOf(node.right.prefix, prefix) {
+ return search(prefix, node.right)
+ }
+
+ return nil
+}
+
+func (n *node) split() {
+ if n.isSplitted() {
+ return
+ }
+ left, right := splitNetworkPrefix(n.prefix)
+ n.insert(leftDirection, left)
+ n.insert(rightDirection, right)
+}
+
+func (n *node) merge(gracePeriod time.Duration) {
+ if n.left == nil || n.right == nil {
+ return
+ }
+ if !n.left.lastUpdateTimestamp.Add(gracePeriod).Before(time.Now()) || !n.right.lastUpdateTimestamp.Add(gracePeriod).Before(time.Now()) {
+ return // grace period not expired
+ }
+ if !n.left.isLeaf() || !n.right.isLeaf() {
+ return
+ }
+ if n.left.acquired || n.right.acquired {
+ return
+ }
+
+ n.left = nil
+ n.right = nil
+ n.lastUpdateTimestamp = time.Now()
+}
+
+func (n *node) insert(nd nodeDirection, prefix netip.Prefix) {
+ newNode := newNode(prefix)
+ switch nd {
+ case leftDirection:
+ n.left = &newNode
+ return
+ case rightDirection:
+ n.right = &newNode
+ return
+ default:
+ return
+ }
+}
+
+func (n *node) bestDirection() (first, second nodeDirection) {
+ if n.left.isSplitted() {
+ return leftDirection, rightDirection
+ }
+ if n.right.isSplitted() {
+ return rightDirection, leftDirection
+ }
+ return leftDirection, rightDirection
+}
+
+func (n *node) isSplitted() bool {
+ if n.left != nil && n.right != nil {
+ return true
+ }
+ return false
+}
+
+func (n *node) isLeaf() bool {
+ if n.left == nil && n.right == nil {
+ return true
+ }
+ return false
+}
+
+func (n *node) next(direction nodeDirection) *node {
+ switch direction {
+ case leftDirection:
+ return n.left
+ case rightDirection:
+ return n.right
+ default:
+ return nil
+ }
+}
+
+func (n *node) toGraphviz() error {
+ var sb strings.Builder
+ sb.WriteString("digraph G {\n")
+ n.toGraphvizRecursive(&sb)
+ sb.WriteString("}\n")
+
+ if _, err := os.Stat(graphvizFolder + ""); os.IsNotExist(err) {
+ if err := os.Mkdir(graphvizFolder+"", 0o700); err != nil {
+ return err
+ }
+ }
+
+ filePath := filepath.Clean(graphvizFolder + "/" + strings.NewReplacer("/", "_", ".", "_").Replace(n.prefix.String()) + ".dot")
+ file, err := os.Create(filePath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ _, err = file.WriteString(sb.String())
+ return err
+}
+
+func (n *node) toGraphvizRecursive(sb *strings.Builder) {
+ if n == nil {
+ return
+ }
+ label := n.prefix.String()
+ if len(n.ips) > 0 {
+ ipsString := []string{}
+ for i := range n.ips {
+ ipsString = append(ipsString, n.ips[i].addr.String())
+ }
+ label += "\\n" + strings.Join(ipsString, "\\n")
+ }
+ if n.acquired {
+ fmt.Fprintf(sb, " %q [label=\"%s\", style=filled, color=\"#57cc99\"];\n", n.prefix, label)
+ }
+ if n.left != nil {
+ fmt.Fprintf(sb, " %q -> %q;\n", n.prefix, n.left.prefix)
+ n.left.toGraphvizRecursive(sb)
+ }
+ if n.right != nil {
+ fmt.Fprintf(sb, " %q -> %q;\n", n.prefix, n.right.prefix)
+ n.right.toGraphvizRecursive(sb)
+ }
+}
+
+
+
+
+
+
diff --git a/pkg/ipam/core/ipam.go b/pkg/ipam/core/ipam.go
index e2adb94203..8e75201054 100644
--- a/pkg/ipam/core/ipam.go
+++ b/pkg/ipam/core/ipam.go
@@ -26,19 +26,14 @@ type Ipam struct {
}
// NewIpam creates a new IPAM instance.
-func NewIpam(pools []string) (*Ipam, error) {
- ipamRootsPrefixes := make([]netip.Prefix, len(pools))
- for i, root := range pools {
- ipamRootsPrefixes[i] = netip.MustParsePrefix(root)
- }
-
- if err := checkRoots(ipamRootsPrefixes); err != nil {
+func NewIpam(pools []netip.Prefix) (*Ipam, error) {
+ if err := checkRoots(pools); err != nil {
return nil, err
}
ipamRoots := make([]node, len(pools))
- for i := range ipamRootsPrefixes {
- ipamRoots[i] = newNode(ipamRootsPrefixes[i])
+ for i := range pools {
+ ipamRoots[i] = newNode(pools[i])
}
ipam := &Ipam{
diff --git a/pkg/ipam/core/ipam_test.go b/pkg/ipam/core/ipam_test.go
new file mode 100644
index 0000000000..5d98926cfb
--- /dev/null
+++ b/pkg/ipam/core/ipam_test.go
@@ -0,0 +1,459 @@
+// Copyright 2019-2024 The Liqo Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ipamcore
+
+import (
+ "fmt"
+ "math"
+ "net/netip"
+ "os"
+ "time"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Ipam", func() {
+ var (
+ err error
+ ipam *Ipam
+ validPools = []netip.Prefix{
+ netip.MustParsePrefix("10.0.0.0/8"),
+ netip.MustParsePrefix("192.168.0.0/16"),
+ netip.MustParsePrefix("172.16.0.0/12"),
+ }
+ invalidPools = []netip.Prefix{
+ netip.MustParsePrefix("10.0.1.0/8"),
+ netip.MustParsePrefix("192.168.1.0/16"),
+ netip.MustParsePrefix("172.16.0.5/12"),
+ }
+ prefixOutOfPools = netip.MustParsePrefix("11.0.0.0/8")
+ )
+
+ Context("Ipam Creation", func() {
+ When("Using valid pools", func() {
+ It("should create an Ipam object", func() {
+ ipam, err = NewIpam(validPools)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(ipam).NotTo(BeNil())
+ })
+ })
+ When("Using invalid pools", func() {
+ It("should return an error", func() {
+ _, err := NewIpam(invalidPools)
+ Expect(err).To(HaveOccurred())
+ })
+ })
+ })
+
+ Context("Ipam Utilities", func() {
+ When("checking if a prefix is child of another one", func() {
+ It("should return true", func() {
+ parentPrefix := netip.MustParsePrefix("10.0.0.0/16")
+ childPrefixes := []netip.Prefix{
+ netip.MustParsePrefix("10.0.0.0/24"),
+ netip.MustParsePrefix("10.0.1.0/24"),
+ netip.MustParsePrefix("10.0.1.6/24"),
+ netip.MustParsePrefix("10.0.0.0/16"),
+ }
+
+ for _, childPrefix := range childPrefixes {
+ Expect(isPrefixChildOf(parentPrefix, childPrefix)).To(BeTrue())
+ }
+ })
+ It("should return false", func() {
+ parentPrefix := netip.MustParsePrefix("10.0.0.0/16")
+ childPrefixes := []netip.Prefix{
+ netip.MustParsePrefix("0.0.0.0/0"),
+ netip.MustParsePrefix("10.0.0.0/15"),
+ netip.MustParsePrefix("10.0.0.0/8"),
+ netip.MustParsePrefix("10.0.1.0/8"),
+ }
+
+ for _, childPrefix := range childPrefixes {
+ Expect(isPrefixChildOf(parentPrefix, childPrefix)).To(BeFalse())
+ }
+ })
+ })
+ })
+
+ Context("Ipam networks", func() {
+ BeforeEach(func() {
+ var err error
+ ipam, err = NewIpam(validPools)
+ Expect(err).NotTo(HaveOccurred())
+ })
+
+ When("creating an Ipam object", func() {
+ It("should succeed", func() {
+ Expect(ipam).NotTo(BeNil())
+ })
+ })
+
+ When("listing networks", func() {
+ It("should succeed", func() {
+ networks := ipam.ListNetworks()
+ Expect(networks).Should(HaveLen(0))
+
+ acquiredNetworks := []netip.Prefix{}
+ acquiredNetworks = append(acquiredNetworks, *ipam.NetworkAcquire(24))
+ acquiredNetworks = append(acquiredNetworks, *ipam.NetworkAcquire(25))
+ acquiredNetworks = append(acquiredNetworks, *ipam.NetworkAcquire(26))
+ acquiredNetworks = append(acquiredNetworks, *ipam.NetworkAcquire(27))
+ acquiredNetworks = append(acquiredNetworks, *ipam.NetworkAcquire(28))
+
+ networks = ipam.ListNetworks()
+ Expect(networks).Should(HaveLen(5))
+ for i := range acquiredNetworks {
+ Expect(networks).Should(ContainElement(acquiredNetworks[i]))
+ }
+ })
+ })
+
+ When("acquiring networks", func() {
+ It("should succeed", func() {
+ network := ipam.NetworkAcquire(24)
+ Expect(network).ShouldNot(BeNil())
+ Expect(ipam.NetworkIsAvailable(*network)).To(BeFalse())
+ })
+ It("should not succeed", func() {
+ network := ipam.NetworkAcquire(4)
+ Expect(network).Should(BeNil())
+ })
+ })
+
+ When("releasing networks", func() {
+ It("should succeed", func() {
+ network := ipam.NetworkAcquire(16)
+ Expect(network).ShouldNot(BeNil())
+ Expect(ipam.NetworkIsAvailable(*network)).To(BeFalse())
+ Expect(ipam.NetworkRelease(*network, 0).String()).To(Equal(network.String()))
+ Expect(ipam.NetworkIsAvailable(*network)).To(BeTrue())
+ })
+ It("should succeed (with grace period)", func() {
+ network := ipam.NetworkAcquire(16)
+ Expect(network).ShouldNot(BeNil())
+ Expect(ipam.NetworkIsAvailable(*network)).To(BeFalse())
+ Expect(ipam.NetworkRelease(*network, time.Second*0).String()).To(Equal(network.String()))
+ Expect(ipam.NetworkIsAvailable(*network)).To(BeTrue())
+ })
+
+ It("should not succeed", func() {
+ networks := []netip.Prefix{
+ netip.MustParsePrefix("10.0.1.0/24"),
+ netip.MustParsePrefix("10.0.2.0/24"),
+ netip.MustParsePrefix("10.1.0.0/16"),
+ netip.MustParsePrefix("10.2.0.0/16"),
+ netip.MustParsePrefix("10.3.0.0/30"),
+ netip.MustParsePrefix("10.4.0.0/27"),
+ }
+ for _, network := range networks {
+ Expect(ipam.NetworkIsAvailable(network)).To(BeTrue())
+ Expect(ipam.NetworkRelease(network, 0)).To(BeNil())
+ Expect(ipam.NetworkRelease(network, time.Second*5)).To(BeNil())
+ }
+ })
+ })
+
+ When("acquiring networks with prefix", func() {
+ It("should succeed", func() {
+ networks := []netip.Prefix{
+ netip.MustParsePrefix("10.0.1.0/24"),
+ netip.MustParsePrefix("10.0.2.0/24"),
+ netip.MustParsePrefix("10.1.0.0/16"),
+ netip.MustParsePrefix("10.2.0.0/16"),
+ netip.MustParsePrefix("10.3.8.0/21"),
+ netip.MustParsePrefix("10.4.16.0/20"),
+ netip.MustParsePrefix("10.128.0.0/20"),
+ netip.MustParsePrefix("10.130.64.0/18"),
+ netip.MustParsePrefix("10.4.2.0/27"),
+ netip.MustParsePrefix("10.3.0.0/30"),
+ netip.MustParsePrefix("10.4.0.0/27"),
+ netip.MustParsePrefix("10.5.0.0/16"),
+ netip.MustParsePrefix("10.4.2.128/25"),
+ netip.MustParsePrefix("10.4.3.0/27"),
+ netip.MustParsePrefix("10.3.0.24/29"),
+ }
+
+ for _, network := range networks {
+ Expect(ipam.NetworkIsAvailable(network)).To(BeTrue())
+ }
+
+ for _, network := range networks {
+ networkAcquired := ipam.NetworkAcquireWithPrefix(network)
+ Expect(networkAcquired).NotTo(BeNil())
+ Expect(networkAcquired.String()).To(Equal(network.String()))
+ }
+
+ for _, network := range networks {
+ Expect(ipam.NetworkIsAvailable(network)).To(BeFalse())
+ }
+
+ for _, network := range networks {
+ Expect(ipam.NetworkRelease(network, 0).String()).To(Equal(network.String()))
+ }
+
+ for _, network := range networks {
+ Expect(ipam.NetworkIsAvailable(network)).To(BeTrue())
+ }
+ })
+
+ It("should not succeed", func() {
+ networks := []netip.Prefix{
+ netip.MustParsePrefix("10.0.1.0/8"),
+ netip.MustParsePrefix("11.0.2.0/24"),
+ netip.MustParsePrefix("11.1.0.0/16"),
+ netip.MustParsePrefix("11.2.0.0/16"),
+ netip.MustParsePrefix("12.0.0.0/8"),
+ netip.MustParsePrefix("13.4.0.0/8"),
+ }
+ for _, network := range networks {
+ Expect(ipam.NetworkAcquireWithPrefix(network)).To(BeNil())
+ }
+
+ })
+ })
+
+ When("acquiring a network", func() {
+ BeforeEach(func() {
+ prefix := netip.MustParsePrefix("10.0.0.0/16")
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).NotTo(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("10.5.0.0/16")
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).NotTo(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ })
+ It("parent networks should not be available", func() {
+ prefix := netip.MustParsePrefix("10.0.0.0/14")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("10.0.0.0/15")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("10.4.0.0/15")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("10.4.0.0/14")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("10.0.0.0/13")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ })
+
+ It("child networks should not be available", func() {
+ prefix := netip.MustParsePrefix("10.0.0.0/17")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("10.0.0.0/18")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("10.5.0.0/20")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("11.0.0.0/16")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+
+ prefix = netip.MustParsePrefix("0.0.0.0/0")
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ Expect(ipam.NetworkAcquireWithPrefix(prefix)).To(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefix)).To(BeFalse())
+ })
+ })
+
+ When("generating graphviz", func() {
+ BeforeEach(func() {
+ sizes := []int{21, 26, 27, 22, 30, 25, 28, 24, 16, 10, 29}
+ for _, size := range sizes {
+ for i := 0; i < 3; i++ {
+ network := ipam.NetworkAcquire(size)
+ Expect(network).ShouldNot(BeNil())
+ }
+ }
+ })
+ It("it should succeed", func() {
+ Expect(ipam.ToGraphviz()).Should(Succeed())
+ })
+ AfterEach(func() {
+ _, err := os.Stat(graphvizFolder)
+ Expect(err).ShouldNot(HaveOccurred())
+ Expect(os.RemoveAll(graphvizFolder)).ShouldNot(HaveOccurred())
+ })
+ })
+ })
+
+ Context("Ipam IPs", func() {
+ var (
+ availableIPs = 8
+ prefixAcquired = netip.MustParsePrefix(fmt.Sprintf("10.0.0.0/%d", int(32-math.Sqrt(8))))
+ prefixNotAcquired = netip.MustParsePrefix(fmt.Sprintf("10.1.0.0/%d", int(32-math.Sqrt(8))))
+ )
+ BeforeEach(func() {
+ var err error
+ ipam, err = NewIpam(validPools)
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(ipam.NetworkIsAvailable(prefixAcquired)).To(BeTrue())
+ Expect(ipam.NetworkAcquireWithPrefix(prefixAcquired)).NotTo(BeNil())
+ Expect(ipam.NetworkIsAvailable(prefixAcquired)).To(BeFalse())
+ })
+
+ When("acquiring an IP from not existing network", func() {
+ It("should not succeed (out of pools)", func() {
+ addr, err := ipam.IPAcquire(prefixOutOfPools)
+ Expect(err).To(HaveOccurred())
+ Expect(addr).To(BeNil())
+ })
+ It("should not succeed (prefix not acquired)", func() {
+ addr, err := ipam.IPAcquire(prefixNotAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(addr).To(BeNil())
+ })
+ })
+ When("acquiring an IP from existing network", func() {
+ It("should succeed", func() {
+ addr, err := ipam.IPAcquire(prefixAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(addr).NotTo(BeNil())
+ Expect(ipam.IPIsAllocated(prefixAcquired, *addr)).To(BeTrue())
+ })
+
+ It("should succeed, with specific IP", func() {
+ addr, err := ipam.IPAcquireWithAddr(prefixAcquired, prefixAcquired.Addr())
+ Expect(err).NotTo(HaveOccurred())
+ Expect(addr).NotTo(BeNil())
+ Expect(addr.String()).To(Equal(prefixAcquired.Addr().String()))
+ Expect(ipam.IPIsAllocated(prefixAcquired, *addr)).To(BeTrue())
+ })
+
+ It("should not overflow available IPs)", func() {
+ for i := 0; i < availableIPs; i++ {
+ addr, err := ipam.IPAcquire(prefixAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(addr).NotTo(BeNil())
+ Expect(ipam.IPIsAllocated(prefixAcquired, *addr)).To(BeTrue())
+ }
+ for i := 0; i < availableIPs; i++ {
+ addr, err := ipam.IPAcquire(prefixAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(addr).To(BeNil())
+ }
+ })
+
+ It("should not overflow available IPs, with specific IPs)", func() {
+ addr := prefixAcquired.Addr()
+ for i := 0; i < availableIPs; i++ {
+ result, err := ipam.IPAcquire(prefixAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(result).NotTo(BeNil())
+ Expect(ipam.IPIsAllocated(prefixAcquired, *result)).To(BeTrue())
+ addr = addr.Next()
+ }
+ for i := 0; i < availableIPs; i++ {
+ result, err := ipam.IPAcquire(prefixAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(result).To(BeNil())
+ addr = addr.Next()
+ }
+ })
+
+ It("should succeed (after an ip has been released)", func() {
+ addrs := []netip.Addr{}
+ for i := 0; i < availableIPs; i++ {
+ addr, err := ipam.IPAcquire(prefixAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(addr).NotTo(BeNil())
+ addrs = append(addrs, *addr)
+ }
+
+ addr, err := ipam.IPRelease(prefixAcquired, addrs[availableIPs/2], 0)
+ Expect(err).To(BeNil())
+ Expect(addr).NotTo(BeNil())
+ Expect(addr.String()).To(Equal(addrs[availableIPs/2].String()))
+ Expect(ipam.IPIsAllocated(prefixAcquired, *addr)).To(BeFalse())
+
+ addr, err = ipam.IPAcquire(prefixAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(addr).NotTo(BeNil())
+ Expect(ipam.IPIsAllocated(prefixAcquired, *addr)).To(BeTrue())
+ })
+ })
+ When("releasing an IP from not existing network", func() {
+ It("should not succeed", func() {
+ addr := prefixAcquired.Addr()
+ for i := 0; i < availableIPs*2; i++ {
+ result, err := ipam.IPRelease(prefixNotAcquired, addr, 0)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(result).To(BeNil())
+ addr = addr.Next()
+ }
+ })
+ It("should not succeed (out of pools)", func() {
+ addr := prefixAcquired.Addr()
+ for i := 0; i < availableIPs*2; i++ {
+ result, err := ipam.IPRelease(prefixOutOfPools, addr, 0)
+ Expect(err).To(HaveOccurred())
+ Expect(result).To(BeNil())
+ addr = addr.Next()
+ }
+ })
+ })
+ When("releasing an IP from existing network", func() {
+ It("should not succeed", func() {
+ addr := prefixAcquired.Addr()
+ for i := 0; i < availableIPs*2; i++ {
+ result, err := ipam.IPRelease(prefixAcquired, addr, 0)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(result).To(BeNil())
+ addr = addr.Next()
+ }
+ })
+ It("should succeed", func() {
+ addrs := []netip.Addr{}
+ for i := 0; i < availableIPs; i++ {
+ addr, err := ipam.IPAcquire(prefixAcquired)
+ Expect(err).NotTo(HaveOccurred())
+ Expect(addr).NotTo(BeNil())
+ addrs = append(addrs, *addr)
+ }
+ for i := range addrs {
+ addr, err := ipam.IPRelease(prefixAcquired, addrs[i], 0)
+ Expect(err).To(BeNil())
+ Expect(addr).NotTo(BeNil())
+ Expect(addr.String()).To(Equal(addrs[i].String()))
+ }
+ })
+
+ })
+ })
+})
diff --git a/pkg/ipam/core/node.go b/pkg/ipam/core/node.go
index 4b1058eee9..f9a55e67da 100644
--- a/pkg/ipam/core/node.go
+++ b/pkg/ipam/core/node.go
@@ -82,7 +82,7 @@ func allocateNetwork(size int, node *node) *netip.Prefix {
}
func allocateNetworkWithPrefix(prefix netip.Prefix, node *node) *netip.Prefix {
- if node.acquired || !node.prefix.Overlaps(prefix) {
+ if node.acquired || !isPrefixChildOf(node.prefix, prefix) {
return nil
}
if node.prefix.Addr().Compare(prefix.Addr()) == 0 && node.prefix.Bits() == prefix.Bits() {
@@ -124,11 +124,10 @@ func networkRelease(prefix netip.Prefix, node *node, gracePeriod time.Duration)
}
return nil
}
-
- if node.left != nil && node.left.prefix.Overlaps(prefix) {
+ if node.left != nil && isPrefixChildOf(node.left.prefix, prefix) {
result = networkRelease(prefix, node.left, gracePeriod)
}
- if node.right != nil && node.right.prefix.Overlaps(prefix) {
+ if node.right != nil && isPrefixChildOf(node.right.prefix, prefix) {
result = networkRelease(prefix, node.right, gracePeriod)
}
@@ -138,10 +137,10 @@ func networkRelease(prefix netip.Prefix, node *node, gracePeriod time.Duration)
func networkIsAvailable(prefix netip.Prefix, node *node) bool {
if node.prefix.Addr().Compare(prefix.Addr()) == 0 && node.prefix.Bits() == prefix.Bits() {
- if node.left != nil && node.left.left.isSplitted() {
+ if node.left != nil && (node.left.isSplitted() || node.left.acquired) {
return false
}
- if node.right != nil && node.right.isSplitted() {
+ if node.right != nil && (node.right.isSplitted() || node.right.acquired) {
return false
}
@@ -153,10 +152,10 @@ func networkIsAvailable(prefix netip.Prefix, node *node) bool {
return true
}
- if node.left != nil && node.left.prefix.Overlaps(prefix) && !node.left.acquired {
+ if node.left != nil && isPrefixChildOf(node.left.prefix, prefix) && !node.left.acquired {
return networkIsAvailable(prefix, node.left)
}
- if node.right != nil && node.right.prefix.Overlaps(prefix) && !node.right.acquired {
+ if node.right != nil && isPrefixChildOf(node.right.prefix, prefix) && !node.right.acquired {
return networkIsAvailable(prefix, node.right)
}
@@ -275,10 +274,10 @@ func search(prefix netip.Prefix, node *node) *node {
return node
}
- if node.left != nil && node.left.prefix.Overlaps(prefix) {
+ if node.left != nil && isPrefixChildOf(node.left.prefix, prefix) {
return search(prefix, node.left)
}
- if node.right != nil && node.right.prefix.Overlaps(prefix) {
+ if node.right != nil && isPrefixChildOf(node.right.prefix, prefix) {
return search(prefix, node.right)
}
diff --git a/pkg/ipam/initialize_test.go b/pkg/ipam/initialize_test.go
index 75d19a63d6..8baed358f9 100644
--- a/pkg/ipam/initialize_test.go
+++ b/pkg/ipam/initialize_test.go
@@ -64,7 +64,11 @@ var _ = Describe("Initialize routine tests", func() {
testutil.FakeNetwork("net6", testNamespace, "172.16.1.0/24", nil),
).Build()
- ipamCore, err := ipamcore.NewIpam([]string{"10.0.0.0/8", "192.168.0.0/16", "172.16.1.0/24"})
+ ipamCore, err := ipamcore.NewIpam([]netip.Prefix{
+ netip.MustParsePrefix("10.0.0.0/8"),
+ netip.MustParsePrefix("192.168.0.0/16"),
+ netip.MustParsePrefix("172.16.1.0/24"),
+ })
Expect(err).To(BeNil())
// Init ipam server
diff --git a/pkg/ipam/ipam.go b/pkg/ipam/ipam.go
index 7c0e71f6d2..93bea650d6 100644
--- a/pkg/ipam/ipam.go
+++ b/pkg/ipam/ipam.go
@@ -56,7 +56,16 @@ func New(ctx context.Context, cl client.Client, roots []string, opts *ServerOpti
hs := health.NewServer()
hs.SetServingStatus(IPAM_ServiceDesc.ServiceName, grpc_health_v1.HealthCheckResponse_NOT_SERVING)
- ipam, err := ipamcore.NewIpam(roots)
+ prefixRoots := make([]netip.Prefix, len(roots))
+ for i, r := range roots {
+ p, err := netip.ParsePrefix(r)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse prefix %q: %w", r, err)
+ }
+ prefixRoots[i] = p
+ }
+
+ ipam, err := ipamcore.NewIpam(prefixRoots)
if err != nil {
return nil, err
}
diff --git a/pkg/ipam/sync_test.go b/pkg/ipam/sync_test.go
index 4c601c9238..1f36e20cb7 100644
--- a/pkg/ipam/sync_test.go
+++ b/pkg/ipam/sync_test.go
@@ -72,7 +72,7 @@ var _ = Describe("Sync routine tests", func() {
testutil.FakeNetwork("net4", testNamespace, "10.4.0.0/16", nil),
).Build()
- ipamCore, err := ipamcore.NewIpam([]string{"10.0.0.0/8"})
+ ipamCore, err := ipamcore.NewIpam([]netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")})
Expect(err).To(BeNil())
// Populate the cache
@@ -156,7 +156,7 @@ var _ = Describe("Sync routine tests", func() {
testutil.FakeIP("ip3", testNamespace, "10.0.0.2", "10.0.0.0/24", nil, nil, false),
).Build()
- ipamCore, err := ipamcore.NewIpam([]string{"10.0.0.0/8"})
+ ipamCore, err := ipamcore.NewIpam([]netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")})
Expect(err).To(BeNil())
// Populate the cache