Skip to content

Commit a0935de

Browse files
committed
feat: ipam core
1 parent 3473545 commit a0935de

File tree

19 files changed

+1091
-256
lines changed

19 files changed

+1091
-256
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,5 @@ docs/_build
4848

4949
# development files
5050
/tmp
51-
51+
/graphviz
5252
/k3s-ansible

cmd/ipam/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func main() {
7272
cmd.Flags().IntVar(&options.ServerOpts.Port, "port", consts.IpamPort, "The port on which to listen for incoming gRPC requests.")
7373
cmd.Flags().DurationVar(&options.ServerOpts.SyncFrequency, "interval", consts.SyncFrequency,
7474
"The interval at which the IPAM will synchronize the IPAM storage.")
75+
cmd.Flags().BoolVar(&options.ServerOpts.GraphvizEnabled, "enable-graphviz", false, "Enable the graphviz output for the IPAM.")
7576

7677
// Leader election flags.
7778
cmd.Flags().BoolVar(&options.EnableLeaderElection, "leader-election", false, "Enable leader election for IPAM. "+
@@ -132,7 +133,9 @@ func run(cmd *cobra.Command, _ []string) error {
132133
}
133134
}
134135

135-
liqoIPAM, err := ipam.New(ctx, cl, &options.ServerOpts)
136+
liqoIPAM, err := ipam.New(ctx, cl, []string{
137+
"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12",
138+
}, &options.ServerOpts)
136139
if err != nil {
137140
return err
138141
}

deployments/liqo/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
| ipam.external.enabled | bool | `false` | Use an external IPAM to allocate the IP addresses for the pods. Enabling it will disable the internal IPAM. |
5252
| ipam.external.url | string | `""` | The URL of the external IPAM. |
5353
| ipam.externalCIDR | string | `"10.70.0.0/16"` | The subnet used for the external CIDR. |
54+
| ipam.internal.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". |
5455
| ipam.internal.image.name | string | `"ghcr.io/liqotech/ipam"` | Image repository for the IPAM pod. |
5556
| ipam.internal.image.version | string | `""` | Custom version for the IPAM image. If not specified, the global tag is used. |
5657
| ipam.internal.pod.annotations | object | `{}` | Annotations for the IPAM pod. |
@@ -59,6 +60,7 @@
5960
| ipam.internal.pod.priorityClassName | string | `""` | PriorityClassName (https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#pod-priority) for the IPAM pod. |
6061
| ipam.internal.pod.resources | object | `{"limits":{},"requests":{}}` | Resource requests and limits (https://kubernetes.io/docs/user-guide/compute-resources/) for the IPAM pod. |
6162
| ipam.internal.replicas | int | `1` | The number of IPAM instances to run, which can be increased for active/passive high availability. |
63+
| 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. |
6264
| 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. |
6365
| ipam.podCIDR | string | `""` | The subnet used by the pods in your cluster, in CIDR notation (e.g., 10.0.0.0/16). |
6466
| 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. |

deployments/liqo/templates/liqo-ipam-deployment.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ spec:
5151
args:
5252
- --pod-name=$(POD_NAME)
5353
- --port=6000
54+
- --interval={{ .Values.ipam.internal.syncInterval }}
5455
{{- if $ha }}
5556
- --leader-election
5657
- --leader-election-namespace=$(POD_NAMESPACE)
@@ -61,6 +62,7 @@ spec:
6162
{{- if .Values.ipam.internal.pod.extraArgs }}
6263
{{- toYaml .Values.ipam.internal.pod.extraArgs | nindent 12 }}
6364
{{- end }}
65+
- --enable-graphviz={{ .Values.ipam.internal.graphviz }}
6466
env:
6567
- name: POD_NAME
6668
valueFrom:
@@ -71,6 +73,11 @@ spec:
7173
fieldRef:
7274
fieldPath: metadata.namespace
7375
resources: {{- toYaml .Values.ipam.internal.pod.resources | nindent 12 }}
76+
{{- if .Values.ipam.internal.graphviz }}
77+
volumeMounts:
78+
- mountPath: /graphviz
79+
name: graphviz
80+
{{- end }}
7481
{{- if ((.Values.common).nodeSelector) }}
7582
nodeSelector:
7683
{{- toYaml .Values.common.nodeSelector | nindent 8 }}
@@ -86,5 +93,9 @@ spec:
8693
{{- if .Values.ipam.internal.pod.priorityClassName }}
8794
priorityClassName: {{ .Values.ipam.internal.pod.priorityClassName }}
8895
{{- end }}
89-
96+
{{- if .Values.ipam.internal.graphviz }}
97+
volumes:
98+
- name: graphviz
99+
emptyDir: {}
100+
{{- end }}
90101
{{- end }}

deployments/liqo/values.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,14 @@ ipam:
441441
requests: {}
442442
# -- PriorityClassName (https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#pod-priority) for the IPAM pod.
443443
priorityClassName: ""
444+
# -- Enable/Disable the generation of graphviz files inside the ipam.
445+
# This feature is useful to visualize the status of the ipam.
446+
# The graphviz files are stored in the /graphviz directory of the ipam pod (a file for each network pool).
447+
# You can access them using "kubectl cp".
448+
graphviz: false
449+
# -- Set the interval at which the IPAM pod will synchronize it's in-memory status with the local cluster.
450+
# If you want to disable the synchronization, set the interval to 0.
451+
syncInterval: 2m
444452
# -- The subnet used by the pods in your cluster, in CIDR notation (e.g., 10.0.0.0/16).
445453
podCIDR: ""
446454
# -- The subnet used by the services in you cluster, in CIDR notation (e.g., 172.16.0.0/16).

pkg/ipam/core/doc.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2019-2024 The Liqo Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package ipamcore provides the core functionality for the IPAM service.
16+
package ipamcore

pkg/ipam/core/ipam.go

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// Copyright 2019-2024 The Liqo Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ipamcore
16+
17+
import (
18+
"fmt"
19+
"net/netip"
20+
"slices"
21+
)
22+
23+
// Ipam represents the IPAM core structure.
24+
type Ipam struct {
25+
roots []node
26+
}
27+
28+
// NewIpam creates a new IPAM instance.
29+
func NewIpam(roots []string) (*Ipam, error) {
30+
ipamRootsPrefixes := make([]netip.Prefix, len(roots))
31+
for i, root := range roots {
32+
ipamRootsPrefixes[i] = netip.MustParsePrefix(root)
33+
}
34+
35+
if err := checkRoots(ipamRootsPrefixes); err != nil {
36+
return nil, err
37+
}
38+
39+
ipamRoots := make([]node, len(roots))
40+
for i := range ipamRootsPrefixes {
41+
ipamRoots[i] = newNode(ipamRootsPrefixes[i])
42+
}
43+
44+
ipam := &Ipam{
45+
roots: ipamRoots,
46+
}
47+
48+
return ipam, nil
49+
}
50+
51+
// NetworkAcquire allocates a network of the given size.
52+
// It returns the allocated network or nil if no network is available.
53+
func (ipam *Ipam) NetworkAcquire(size int) *netip.Prefix {
54+
for i := range ipam.roots {
55+
if result := allocateNetwork(size, &ipam.roots[i]); result != nil {
56+
return result
57+
}
58+
}
59+
return nil
60+
}
61+
62+
// NetworkAcquireWithPrefix allocates a network with the given prefix.
63+
// It returns the allocated network or nil if the network is not available.
64+
func (ipam *Ipam) NetworkAcquireWithPrefix(prefix netip.Prefix) *netip.Prefix {
65+
for i := range ipam.roots {
66+
if result := allocateNetworkWithPrefix(prefix, &ipam.roots[i]); result != nil {
67+
return result
68+
}
69+
}
70+
return nil
71+
}
72+
73+
// NetworkRelease frees the network with the given prefix.
74+
// It returns the freed network or nil if the network is not found.
75+
func (ipam *Ipam) NetworkRelease(prefix netip.Prefix) *netip.Prefix {
76+
for i := range ipam.roots {
77+
if isPrefixChildOf(ipam.roots[i].prefix, prefix) {
78+
if result := networkRelease(prefix, &ipam.roots[i]); result != nil {
79+
return result
80+
}
81+
}
82+
}
83+
return nil
84+
}
85+
86+
// ListNetworks returns the list of allocated networks.
87+
func (ipam *Ipam) ListNetworks() []netip.Prefix {
88+
var networks []netip.Prefix
89+
for i := range ipam.roots {
90+
networks = append(networks, listNetworks(&ipam.roots[i])...)
91+
}
92+
return networks
93+
}
94+
95+
// NetworkIsAvailable checks if the network with the given prefix is allocated.
96+
// It returns false if the network is allocated or there is no suitable pool, true otherwise.
97+
func (ipam *Ipam) NetworkIsAvailable(prefix netip.Prefix) bool {
98+
node, err := ipam.search(prefix)
99+
if err != nil {
100+
return false
101+
}
102+
if node == nil {
103+
return true
104+
}
105+
return !node.acquired
106+
}
107+
108+
// IPAcquire allocates an IP address from the given prefix.
109+
// It returns the allocated IP address or nil if the IP address is not available.
110+
func (ipam *Ipam) IPAcquire(prefix netip.Prefix) (*netip.Addr, error) {
111+
node, err := ipam.search(prefix)
112+
if err != nil {
113+
return nil, err
114+
}
115+
if node != nil {
116+
return node.ipAcquire(), nil
117+
}
118+
119+
return nil, nil
120+
}
121+
122+
// IPAcquireWithAddr allocates the IP address from the given prefix.
123+
// It returns the allocated IP address or nil if the IP address is not available.
124+
func (ipam *Ipam) IPAcquireWithAddr(prefix netip.Prefix, addr netip.Addr) (*netip.Addr, error) {
125+
if !prefix.Contains(addr) {
126+
return nil, fmt.Errorf("address %s is not contained in prefix %s", addr, prefix)
127+
}
128+
node, err := ipam.search(prefix)
129+
if err != nil {
130+
return nil, err
131+
}
132+
if node != nil {
133+
return node.allocateIPWithAddr(addr), nil
134+
}
135+
return nil, nil
136+
}
137+
138+
// IPRelease frees the IP address from the given prefix.
139+
// It returns the freed IP address or nil if the IP address is not found.
140+
func (ipam *Ipam) IPRelease(prefix netip.Prefix, addr netip.Addr) (*netip.Addr, error) {
141+
node, err := ipam.search(prefix)
142+
if err != nil {
143+
return nil, err
144+
}
145+
if node != nil {
146+
return node.ipRelease(addr), nil
147+
}
148+
return nil, nil
149+
}
150+
151+
// ListIPs returns the list of allocated IP addresses from the given prefix.
152+
func (ipam *Ipam) ListIPs(prefix netip.Prefix) ([]netip.Addr, error) {
153+
node, err := ipam.search(prefix)
154+
if err != nil {
155+
return nil, err
156+
}
157+
if node != nil {
158+
return slices.Clone(node.ips), nil
159+
}
160+
return nil, nil
161+
}
162+
163+
// IsAllocatedIP checks if the IP address is allocated from the given prefix.
164+
// It returns true if the IP address is allocated, false otherwise.
165+
func (ipam *Ipam) IsAllocatedIP(prefix netip.Prefix, addr netip.Addr) (bool, error) {
166+
node, err := ipam.search(prefix)
167+
if err != nil {
168+
return false, err
169+
}
170+
if node != nil {
171+
return node.isAllocatedIP(addr), nil
172+
}
173+
return false, nil
174+
}
175+
176+
// ToGraphviz generates the Graphviz representation of the IPAM structure.
177+
func (ipam *Ipam) ToGraphviz() error {
178+
for i := range ipam.roots {
179+
_ = i
180+
if err := ipam.roots[i].toGraphviz(); err != nil {
181+
return fmt.Errorf("failed to generate Graphviz representation: %w", err)
182+
}
183+
}
184+
return nil
185+
}
186+
187+
func (ipam *Ipam) search(prefix netip.Prefix) (*node, error) {
188+
for i := range ipam.roots {
189+
if !isPrefixChildOf(ipam.roots[i].prefix, prefix) {
190+
continue
191+
}
192+
if node := search(prefix, &ipam.roots[i]); node != nil {
193+
return node, nil
194+
}
195+
return nil, nil
196+
}
197+
return nil, fmt.Errorf("prefix %s not contained in roots", prefix)
198+
}
199+
200+
func checkRoots(roots []netip.Prefix) error {
201+
for i := range roots {
202+
if err := checkHostBitsZero(roots[i]); err != nil {
203+
return err
204+
}
205+
}
206+
return nil
207+
}

0 commit comments

Comments
 (0)