Skip to content

Commit

Permalink
feat: ipam core
Browse files Browse the repository at this point in the history
  • Loading branch information
cheina97 committed Nov 25, 2024
1 parent c765c47 commit 9e4ff21
Show file tree
Hide file tree
Showing 16 changed files with 882 additions and 283 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ docs/_build

# development files
/tmp

/graphviz
/k3s-ansible
5 changes: 4 additions & 1 deletion build/common/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ FROM alpine:3.20.3
RUN apk update && \
apk add --no-cache ca-certificates && \
update-ca-certificates && \
rm -rf /var/cache/apk/*
rm -rf /var/cache/apk/* && \
mkdir -p /app && \
chown 1000:1000 /app && \
chmod 700 /app

ARG COMPONENT
COPY --from=builder /tmp/builder/$COMPONENT /usr/bin/$COMPONENT
Expand Down
4 changes: 3 additions & 1 deletion cmd/ipam/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ func run(cmd *cobra.Command, _ []string) error {
}
}

liqoIPAM, err := ipam.New(ctx, cl, &options.ServerOpts)
liqoIPAM, err := ipam.New(ctx, cl, []string{
"10.0.0.0/8", "192.168.0.0/16",
}, &options.ServerOpts)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions deployments/liqo/templates/liqo-ipam-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ metadata:
labels:
{{- include "liqo.labels" $ipamConfig | nindent 4 }}
spec:
strategy:
type: Recreate
replicas: {{ .Values.ipam.internal.replicas }}
selector:
matchLabels:
Expand Down
17 changes: 17 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"
"net/netip"
)

func main() {
var addr netip.Addr

fmt.Println("aaaa")
fmt.Sprintln(addr)
if !addr.IsValid() {
fmt.Println("invalid")
}
fmt.Println("bbbb")
}
16 changes: 16 additions & 0 deletions pkg/ipam/core/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// 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 provides the core functionality for the IPAM service.
package ipamcore
215 changes: 215 additions & 0 deletions pkg/ipam/core/ipam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// 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"
)

// Ipam represents the IPAM core structure.
type Ipam struct {
roots []node
}

// NewIpam creates a new IPAM instance.
func NewIpam(roots, preallocated []string) (*Ipam, error) {
ipamRootsPrefixes := make([]netip.Prefix, len(roots))
for i, root := range roots {
ipamRootsPrefixes[i] = netip.MustParsePrefix(root)
}

ipamPreallocated := make([]netip.Prefix, len(preallocated))
for i, prefix := range preallocated {
ipamPreallocated[i] = netip.MustParsePrefix(prefix)
}

if err := checkRoots(ipamRootsPrefixes); err != nil {
return nil, err
}

if err := checkPreallocated(ipamRootsPrefixes, ipamPreallocated); err != nil {
return nil, err
}

ipamRoots := make([]node, len(roots))
for i := range ipamRootsPrefixes {
ipamRoots[i] = newNode(ipamRootsPrefixes[i])
}

ipam := &Ipam{
roots: ipamRoots,
}

if err := ipam.preallocateNetwork(ipamRootsPrefixes, ipamPreallocated); err != nil {
return nil, err
}

return ipam, nil
}

// AllocateNetwork allocates a network of the given size.
// It returns the allocated network or nil if no network is available.
func (ipam *Ipam) AllocateNetwork(size int) *netip.Prefix {
for i := range ipam.roots {
if result := allocateNetwork(size, &ipam.roots[i]); result != nil {
return result
}
}
return nil
}

// AllocateNetworkWithPrefix allocates a network with the given prefix.
// It returns the allocated network or nil if the network is not available.
func (ipam *Ipam) AllocateNetworkWithPrefix(prefix netip.Prefix) *netip.Prefix {
for i := range ipam.roots {
if result := allocateNetworkWithPrefix(prefix, &ipam.roots[i]); result != nil {
return result
}
}
return nil
}

// FreeNetwork frees the network with the given prefix.
// It returns the freed network or nil if the network is not found.
func (ipam *Ipam) FreeNetwork(prefix netip.Prefix) *netip.Prefix {
for i := range ipam.roots {
if isPrefixChildOf(ipam.roots[i].prefix, prefix) {
if result := freeNetwork(prefix, &ipam.roots[i]); 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
}

// IsAllocatedNetwork checks if the network with the given prefix is allocated.
// It returns true if the network is allocated, false otherwise.
func (ipam *Ipam) IsAllocatedNetwork(prefix netip.Prefix) bool {
if node := ipam.search(prefix); node != nil {
return node.acquired
}
return false
}

// AllocateIP 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) AllocateIP(prefix netip.Prefix) *netip.Addr {
if node := ipam.search(prefix); node != nil {
return node.allocateIP()
}
return nil
}

// AllocateIPWithAddr 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) AllocateIPWithAddr(prefix netip.Prefix, addr netip.Addr) *netip.Addr {
if !prefix.Contains(addr) {
return nil
}
if node := ipam.search(prefix); node != nil {
return node.allocateIPWithAddr(addr)
}
return nil
}

// FreeIP 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) FreeIP(prefix netip.Prefix, addr netip.Addr) *netip.Addr {
if node := ipam.search(prefix); node != nil {
return node.freeIP(addr)
}
return nil
}

// ListIPs returns the list of allocated IP addresses from the given prefix.
func (ipam *Ipam) ListIPs(prefix netip.Prefix) []netip.Addr {
if node := ipam.search(prefix); node != nil {
return node.listIPs()
}
return nil
}

// 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("/app"); err != nil {
return fmt.Errorf("failed to generate Graphviz representation: %w", err)
}
}
return nil
}

func (ipam *Ipam) search(prefix netip.Prefix) *node {
for i := range ipam.roots {
if node := search(prefix, &ipam.roots[i]); node != nil {
return node
}
}
return nil
}

func checkRoots(roots []netip.Prefix) error {
for i := range roots {
if err := checkHostBitsZero(roots[i]); err != nil {
return err
}
}
return nil
}

func checkPreallocated(roots, preallocated []netip.Prefix) error {
var err error
for i := range preallocated {
if err = checkHostBitsZero(preallocated[i]); err != nil {
return err
}
isChild := false
for j := range roots {
if isPrefixChildOf(roots[j], preallocated[i]) {
isChild = true
break
}
isChild = false
}
if !isChild {
return fmt.Errorf("prefix %s is not a child of any root cidr", preallocated[i])
}
}
return nil
}

func (ipam *Ipam) preallocateNetwork(roots, prefixes []netip.Prefix) error {
for i := range prefixes {
for j := range roots {
if isPrefixChildOf(roots[j], prefixes[i]) {
if prefix := ipam.AllocateNetworkWithPrefix(prefixes[i]); prefix == nil {
return fmt.Errorf("prefix %s is not allocated", prefixes[i])
}
}
}
}
return nil
}
81 changes: 81 additions & 0 deletions pkg/ipam/core/net.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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
}

func splitNetworkPrefix(prefix netip.Prefix) (left, right netip.Prefix) {
if err := checkHostBitsZero(prefix); err != nil {
panic("Host bits must be zero")
}

bin, err := prefix.MarshalBinary()
if err != nil {
panic(err)
}

maskLen := bin[len(bin)-1]

left = netip.MustParsePrefix(
fmt.Sprintf("%s/%d", convertByteSliceToString(bin[:4]), maskLen+1),
)

byteIndex := maskLen / 8
bitIndex := maskLen % 8

bin[byteIndex] = setBit(bin[byteIndex], bitIndex)

right = netip.MustParsePrefix(
fmt.Sprintf("%s/%d", convertByteSliceToString(bin[:4]), maskLen+1),
)

return left, right
}

func isPrefixChildOf(parent, child netip.Prefix) bool {
if parent.Bits() <= child.Bits() && parent.Overlaps(child) {
return true
}
return false
}
Loading

0 comments on commit 9e4ff21

Please sign in to comment.