Skip to content

Commit

Permalink
feat: ipam core
Browse files Browse the repository at this point in the history
feat: ipam core
  • Loading branch information
cheina97 committed Nov 27, 2024
1 parent c6c6174 commit ef9e6fd
Show file tree
Hide file tree
Showing 19 changed files with 1,075 additions and 228 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 cmd/ipam/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func main() {
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, "interval", consts.SyncFrequency,
"The interval at which the IPAM will synchronize the IPAM storage.")
cmd.Flags().BoolVar(&options.ServerOpts.GraphvizEnabled, "enable-graphviz", false, "Enable the graphviz output for the IPAM.")

// Leader election flags.
cmd.Flags().BoolVar(&options.EnableLeaderElection, "leader-election", false, "Enable leader election for IPAM. "+
Expand Down Expand Up @@ -132,7 +133,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", "172.16.0.0/12",
}, &options.ServerOpts)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions deployments/liqo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
| ipam.external.enabled | bool | `false` | Use an external IPAM to allocate the IP addresses for the pods. Enabling it will disable the internal IPAM. |
| ipam.external.url | string | `""` | The URL of the external IPAM. |
| ipam.externalCIDR | string | `"10.70.0.0/16"` | The subnet used for the external CIDR. |
| ipam.graphviz | bool | `false` | Enable/Disable the generation of graphviz files inside the ipam. This feature is useful to visualize the status of the ipam. The graphviz files are stored in the /graphviz directory of the ipam pod (a file for each network pool). You can access them using "kubectl cp". |
| ipam.internal.image.name | string | `"ghcr.io/liqotech/ipam"` | Image repository for the IPAM pod. |
| ipam.internal.image.version | string | `""` | Custom version for the IPAM image. If not specified, the global tag is used. |
| ipam.internal.pod.annotations | object | `{}` | Annotations for the IPAM pod. |
Expand All @@ -63,6 +64,7 @@
| ipam.podCIDR | string | `""` | The subnet used by the pods in your cluster, in CIDR notation (e.g., 10.0.0.0/16). |
| ipam.reservedSubnets | list | `[]` | List of IP subnets that do not have to be used by Liqo. Liqo can perform automatic IP address remapping when a remote cluster is peering with you, e.g., in case IP address spaces (e.g., PodCIDR) overlaps. In order to prevent IP conflicting between locally used private subnets in your infrastructure and private subnets belonging to remote clusters you need tell liqo the subnets used in your cluster. E.g if your cluster nodes belong to the 192.168.2.0/24 subnet, then you should add that subnet to the reservedSubnets. PodCIDR and serviceCIDR used in the local cluster are automatically added to the reserved list. |
| ipam.serviceCIDR | string | `""` | The subnet used by the services in you cluster, in CIDR notation (e.g., 172.16.0.0/16). |
| ipam.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. |
| metricAgent.config.timeout | object | `{"read":"30s","write":"30s"}` | Set the timeout for the metrics server. |
| metricAgent.enable | bool | `true` | Enable/Disable the virtual kubelet metric agent. This component aggregates all the kubelet-related metrics (e.g., CPU, RAM, etc) collected on the nodes that are used by a remote cluster peered with you, then exporting the resulting values as a property of the virtual kubelet running on the remote cluster. |
| metricAgent.image.name | string | `"ghcr.io/liqotech/metric-agent"` | Image repository for the metricAgent pod. |
Expand Down
15 changes: 14 additions & 1 deletion 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 Expand Up @@ -51,6 +53,7 @@ spec:
args:
- --pod-name=$(POD_NAME)
- --port=6000
- --interval={{ .Values.ipam.syncInterval }}
{{- if $ha }}
- --leader-election
- --leader-election-namespace=$(POD_NAMESPACE)
Expand All @@ -61,6 +64,7 @@ spec:
{{- if .Values.ipam.internal.pod.extraArgs }}
{{- toYaml .Values.ipam.internal.pod.extraArgs | nindent 12 }}
{{- end }}
- --enable-graphviz={{ .Values.ipam.graphviz }}
env:
- name: POD_NAME
valueFrom:
Expand All @@ -71,6 +75,11 @@ spec:
fieldRef:
fieldPath: metadata.namespace
resources: {{- toYaml .Values.ipam.internal.pod.resources | nindent 12 }}
{{- if .Values.ipam.graphviz }}
volumeMounts:
- mountPath: /graphviz
name: graphviz
{{- end }}
{{- if ((.Values.common).nodeSelector) }}
nodeSelector:
{{- toYaml .Values.common.nodeSelector | nindent 8 }}
Expand All @@ -86,5 +95,9 @@ spec:
{{- if .Values.ipam.internal.pod.priorityClassName }}
priorityClassName: {{ .Values.ipam.internal.pod.priorityClassName }}
{{- end }}

{{- if .Values.ipam.graphviz }}
volumes:
- name: graphviz
emptyDir: {}
{{- end }}
{{- end }}
8 changes: 8 additions & 0 deletions deployments/liqo/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,14 @@ ipam:
# Network pools are used to map a cluster network into another one in order to prevent conflicts.
# Default set of network pools is: [10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12]
additionalPools: []
# -- Enable/Disable the generation of graphviz files inside the ipam.
# This feature is useful to visualize the status of the ipam.
# The graphviz files are stored in the /graphviz directory of the ipam pod (a file for each network pool).
# You can access them using "kubectl cp".
graphviz: false
# -- 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

crdReplicator:
pod:
Expand Down
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
225 changes: 225 additions & 0 deletions pkg/ipam/core/ipam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// 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"
"slices"
)

// 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
}

// 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 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) *netip.Prefix {
for i := range ipam.roots {
if isPrefixChildOf(ipam.roots[i].prefix, prefix) {
if result := networkRelease(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
}

// NetworkIsAvailable checks if the network with the given prefix is allocated.
// It returns true if the network is allocated, false otherwise.
func (ipam *Ipam) NetworkIsAvailable(prefix netip.Prefix) bool {
if node := ipam.search(prefix); node != nil {
return node.acquired
}
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 {
if node := ipam.search(prefix); node != nil {
return node.ipAcquire()
}
return 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 {
if !prefix.Contains(addr) {
return nil
}
if node := ipam.search(prefix); node != nil {
return node.allocateIPWithAddr(addr)
}
return 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) *netip.Addr {
if node := ipam.search(prefix); node != nil {
return node.ipRelease(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 slices.Clone(node.listIPs())
}
return nil
}

// IsAllocatedIP 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 {
if node := ipam.search(prefix); node != nil {
return node.isAllocatedIP(addr)
}
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 {
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.NetworkAcquireWithPrefix(prefixes[i]); prefix == nil {
return fmt.Errorf("prefix %s is not allocated", prefixes[i])
}
}
}
}
return nil
}
Loading

0 comments on commit ef9e6fd

Please sign in to comment.