Skip to content

Commit

Permalink
feat: ipam timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
cheina97 committed Dec 5, 2024
1 parent f7c2de7 commit ff1d03e
Show file tree
Hide file tree
Showing 14 changed files with 279 additions and 165 deletions.
4 changes: 3 additions & 1 deletion cmd/ipam/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ func main() {

// Server options.
cmd.Flags().IntVar(&options.ServerOpts.Port, "port", consts.IpamPort, "The port on which to listen for incoming gRPC requests.")
cmd.Flags().DurationVar(&options.ServerOpts.SyncFrequency, "sync-interval", consts.SyncInterval,
cmd.Flags().DurationVar(&options.ServerOpts.SyncInterval, "sync-interval", consts.SyncInterval,
"The interval at which the IPAM will synchronize the IPAM storage.")
cmd.Flags().DurationVar(&options.ServerOpts.SyncGracePeriod, "sync-graceperiod", consts.SyncGracePeriod,
"The grace period the sync routine wait before releasing an ip or a network.")
cmd.Flags().BoolVar(&options.ServerOpts.GraphvizEnabled, "enable-graphviz", false, "Enable the graphviz output for the IPAM.")
cmd.Flags().StringSliceVar(&options.ServerOpts.Pools, "pools",
[]string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"},
Expand Down
1 change: 1 addition & 0 deletions deployments/liqo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
| ipam.internal.pod.priorityClassName | string | `""` | PriorityClassName (https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#pod-priority) for the IPAM pod. |
| ipam.internal.pod.resources | object | `{"limits":{},"requests":{}}` | Resource requests and limits (https://kubernetes.io/docs/user-guide/compute-resources/) for the IPAM pod. |
| ipam.internal.replicas | int | `1` | The number of IPAM instances to run, which can be increased for active/passive high availability. |
| ipam.internal.syncGracePeriod | string | `"30s"` | |
| ipam.internal.syncInterval | string | `"2m"` | Set the interval at which the IPAM pod will synchronize it's in-memory status with the local cluster. If you want to disable the synchronization, set the interval to 0. |
| ipam.internalCIDR | string | `"10.80.0.0/16"` | The subnet used for the internal CIDR. These IPs are assigned to the Liqo internal-network interfaces. |
| ipam.podCIDR | string | `""` | The subnet used by the pods in your cluster, in CIDR notation (e.g., 10.0.0.0/16). |
Expand Down
1 change: 1 addition & 0 deletions deployments/liqo/templates/liqo-ipam-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ spec:
- --pod-name=$(POD_NAME)
- --port=6000
- --sync-interval={{ .Values.ipam.internal.syncInterval }}
- --sync-graceperiod={{ .Values.ipam.internal.syncGracePeriod }}
{{- if $ha }}
- --leader-election
- --leader-election-namespace=$(POD_NAMESPACE)
Expand Down
2 changes: 2 additions & 0 deletions deployments/liqo/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ ipam:
# -- Set the interval at which the IPAM pod will synchronize it's in-memory status with the local cluster.
# If you want to disable the synchronization, set the interval to 0.
syncInterval: 2m
## -- Set the grace period the sync routine will wait before deleting an ip or a network.
syncGracePeriod: 30s
# -- The subnet used by the pods in your cluster, in CIDR notation (e.g., 10.0.0.0/16).
podCIDR: ""
# -- The subnet used by the services in you cluster, in CIDR notation (e.g., 172.16.0.0/16).
Expand Down
3 changes: 2 additions & 1 deletion pkg/consts/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const (
IpamPort = 6000
// SyncInterval is the frequency at which the IPAM should periodically sync its status.
SyncInterval = 2 * time.Minute

// SyncGracePeriod is the time the IPAM sync routine should wait before performing a deletion.
SyncGracePeriod = 30 * time.Second
// NetworkNotRemappedLabelKey is the label key used to mark a Network that does not need CIDR remapping.
NetworkNotRemappedLabelKey = "ipam.liqo.io/network-not-remapped"
// NetworkNotRemappedLabelValue is the label value used to mark a Network that does not need CIDR remapping.
Expand Down
54 changes: 46 additions & 8 deletions pkg/ipam/core/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package ipamcore
import (
"fmt"
"net/netip"
"slices"
"time"
)

// Ipam represents the IPAM core structure.
Expand Down Expand Up @@ -74,10 +74,10 @@ func (ipam *Ipam) NetworkAcquireWithPrefix(prefix netip.Prefix) *netip.Prefix {

// 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) *netip.Prefix {
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]); result != nil {
if result := networkRelease(prefix, &ipam.roots[i], gracePeriod); result != nil {
return result
}
}
Expand Down Expand Up @@ -137,13 +137,13 @@ func (ipam *Ipam) IPAcquireWithAddr(prefix netip.Prefix, addr netip.Addr) (*neti

// 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) (*netip.Addr, error) {
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), nil
return node.ipRelease(addr, gracePeriod), nil
}
return nil, nil
}
Expand All @@ -155,14 +155,18 @@ func (ipam *Ipam) ListIPs(prefix netip.Prefix) ([]netip.Addr, error) {
return nil, err
}
if node != nil {
return slices.Clone(node.ips), 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
}

// IsAllocatedIP checks if the IP address is allocated from the given prefix.
// 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) IsAllocatedIP(prefix netip.Prefix, addr netip.Addr) (bool, error) {
func (ipam *Ipam) IPIsAllocated(prefix netip.Prefix, addr netip.Addr) (bool, error) {
node, err := ipam.search(prefix)
if err != nil {
return false, err
Expand Down Expand Up @@ -216,3 +220,37 @@ func checkRoots(roots []netip.Prefix) error {
}
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
}
87 changes: 60 additions & 27 deletions pkg/ipam/core/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,25 @@ import (
"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 []netip.Addr
ips []nodeIP
lastip netip.Addr
}

Expand All @@ -39,10 +48,12 @@ type nodeDirection string
const (
leftDirection nodeDirection = "left"
rightDirection nodeDirection = "right"

graphvizFolder = "./graphviz"
)

func newNode(prefix netip.Prefix) node {
return node{prefix: prefix}
return node{prefix: prefix, lastUpdateTimestamp: time.Now()}
}

func allocateNetwork(size int, node *node) *netip.Prefix {
Expand All @@ -52,6 +63,7 @@ func allocateNetwork(size int, node *node) *netip.Prefix {
if node.prefix.Bits() == size {
if !node.isSplitted() {
node.acquired = true
node.lastUpdateTimestamp = time.Now()
return &node.prefix
}
return nil
Expand All @@ -76,6 +88,7 @@ func allocateNetworkWithPrefix(prefix netip.Prefix, node *node) *netip.Prefix {
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
Expand All @@ -95,29 +108,31 @@ func allocateNetworkWithPrefix(prefix netip.Prefix, node *node) *netip.Prefix {
return nil
}

func networkRelease(prefix netip.Prefix, node *node) *netip.Prefix {
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() {
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 && node.left.prefix.Overlaps(prefix) {
result = networkRelease(prefix, node.left)
result = networkRelease(prefix, node.left, gracePeriod)
}
if node.right != nil && node.right.prefix.Overlaps(prefix) {
result = networkRelease(prefix, node.right)
result = networkRelease(prefix, node.right, gracePeriod)
}

node.merge()
node.merge(gracePeriod)
return result
}

Expand Down Expand Up @@ -170,7 +185,7 @@ func listNetworks(node *node) []netip.Prefix {

func (n *node) isAllocatedIP(ip netip.Addr) bool {
for i := range n.ips {
if n.ips[i].Compare(ip) == 0 {
if n.ips[i].addr.Compare(ip) == 0 {
return true
}
}
Expand Down Expand Up @@ -202,8 +217,9 @@ func (n *node) ipAcquire() *netip.Addr {
addr = n.prefix.Addr()
}
if !n.isAllocatedIP(addr) {
n.ips = append(n.ips, addr)
n.ips = append(n.ips, nodeIP{addr: addr, creationTimestamp: time.Now()})
n.lastip = addr
n.lastUpdateTimestamp = time.Now()
return &addr
}
addr = addr.Next()
Expand All @@ -221,25 +237,30 @@ func (n *node) allocateIPWithAddr(addr netip.Addr) *netip.Addr {
}

for i := range n.ips {
if n.ips[i].Compare(addr) == 0 {
if n.ips[i].addr.Compare(addr) == 0 {
return nil
}
}

n.ips = append(n.ips, addr)
n.ips = append(n.ips, nodeIP{addr: addr, creationTimestamp: time.Now()})
n.lastUpdateTimestamp = time.Now()

return &n.ips[len(n.ips)-1]
return &n.ips[len(n.ips)-1].addr
}

func (n *node) ipRelease(ip netip.Addr) *netip.Addr {
func (n *node) ipRelease(ip netip.Addr, gracePeriod time.Duration) *netip.Addr {
if !n.acquired {
return nil
}

for i, addr := range n.ips {
if addr.Compare(ip) == 0 {
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:]...)
return &addr
n.lastUpdateTimestamp = time.Now()
return &nodeIP.addr
}
}
return nil
Expand Down Expand Up @@ -273,21 +294,33 @@ func (n *node) split() {
n.insert(rightDirection, right)
}

func (n *node) merge() {
if n.left.isLeaf() && n.right.isLeaf() && !n.left.acquired && !n.right.acquired {
n.left = nil
n.right = nil
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 := &node{prefix: prefix}
newNode := newNode(prefix)
switch nd {
case leftDirection:
n.left = newNode
n.left = &newNode
return
case rightDirection:
n.right = newNode
n.right = &newNode
return
default:
return
Expand Down Expand Up @@ -335,13 +368,13 @@ func (n *node) toGraphviz() error {
n.toGraphvizRecursive(&sb)
sb.WriteString("}\n")

if _, err := os.Stat("./graphviz"); os.IsNotExist(err) {
if err := os.Mkdir("./graphviz", 0o700); err != nil {
if _, err := os.Stat(graphvizFolder + ""); os.IsNotExist(err) {
if err := os.Mkdir(graphvizFolder+"", 0o700); err != nil {
return err
}
}

filePath := filepath.Clean("./graphviz/" + strings.NewReplacer("/", "_", ".", "_").Replace(n.prefix.String()) + ".dot")
filePath := filepath.Clean(graphvizFolder + "/" + strings.NewReplacer("/", "_", ".", "_").Replace(n.prefix.String()) + ".dot")
file, err := os.Create(filePath)
if err != nil {
return err
Expand All @@ -360,7 +393,7 @@ func (n *node) toGraphvizRecursive(sb *strings.Builder) {
if len(n.ips) > 0 {
ipsString := []string{}
for i := range n.ips {
ipsString = append(ipsString, n.ips[i].String())
ipsString = append(ipsString, n.ips[i].addr.String())
}
label += "\\n" + strings.Join(ipsString, "\\n")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/ipam/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (lipam *LiqoIPAM) initializeNetworks(ctx context.Context) error {
}
for i := 0; i < int(netdetails.preallocated); i++ {
if _, err := lipam.ipAcquire(net); err != nil {
return errors.Join(err, lipam.networkRelease(net))
return errors.Join(err, lipam.networkRelease(net, 0))
}
}
}
Expand Down
Loading

0 comments on commit ff1d03e

Please sign in to comment.