Skip to content

Commit

Permalink
✨ Add IPAM for nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattes83 committed Jun 10, 2024
1 parent 08a6d12 commit 354436f
Show file tree
Hide file tree
Showing 13 changed files with 725 additions and 28 deletions.
20 changes: 17 additions & 3 deletions api/v1alpha1/ionoscloudmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ type IonosCloudMachineSpec struct {
//+optional
AdditionalNetworks Networks `json:"additionalNetworks,omitempty"`

// IPAMConfig allows to obtain IP Addresses from existing IP pools instead of using DHCP.
IPAMConfig `json:",inline"`

// FailoverIP can be set to enable failover for VMs in the same MachineDeployment.
// It can be either set to an already reserved IPv4 address, or it can be set to "AUTO"
// which will automatically reserve an IPv4 address for the Failover Group.
Expand Down Expand Up @@ -183,6 +186,9 @@ type Network struct {
// This LAN will be excluded from the deletion process.
//+kubebuilder:validation:Minimum=1
NetworkID int32 `json:"networkID"`

// IPAMConfig allows to obtain IP Addresses from existing IP pools instead of using DHCP.
IPAMConfig `json:",inline"`
}

// Volume is the physical storage on the VM.
Expand Down Expand Up @@ -228,7 +234,7 @@ type IonosCloudMachineStatus struct {
Ready bool `json:"ready"`

// MachineNetworkInfo contains information about the network configuration of the VM.
// This information is only available after the VM has been provisioned.
//+optional
MachineNetworkInfo *MachineNetworkInfo `json:"machineNetworkInfo,omitempty"`

// FailureReason will be set in the event that there is a terminal problem
Expand Down Expand Up @@ -280,6 +286,8 @@ type IonosCloudMachineStatus struct {
}

// MachineNetworkInfo contains information about the network configuration of the VM.
// Before the provisioning MachineNetworkInfo may contain IP addresses to be used for provisioning.
// After provisioning this information is available completely.
type MachineNetworkInfo struct {
// NICInfo holds information about the NICs, which are attached to the VM.
//+optional
Expand All @@ -289,10 +297,16 @@ type MachineNetworkInfo struct {
// NICInfo provides information about the NIC of the VM.
type NICInfo struct {
// IPv4Addresses contains the IPv4 addresses of the NIC.
IPv4Addresses []string `json:"ipv4Addresses"`
// By default, we enable dual-stack, but as we are storing the IP obtained from AddressClaims here before
// creating the VM this can be temporarily empty, e.g. we use DHCP for IPv4 and fixed IP for IPv6.
//+optional
IPv4Addresses []string `json:"ipv4Addresses,omitempty"`

// IPv6Addresses contains the IPv6 addresses of the NIC.
IPv6Addresses []string `json:"ipv6Addresses"`
// By default, we enable dual-stack, but as we are storing the IP obtained from AddressClaims here before
// creating the VM this can be temporarily empty, e.g. we use DHCP for IPv6 and fixed IP for IPv4.
//+optional
IPv6Addresses []string `json:"ipv6Addresses,omitempty"`

// NetworkID is the ID of the LAN to which the NIC is connected.
NetworkID int32 `json:"networkID"`
Expand Down
51 changes: 51 additions & 0 deletions api/v1alpha1/ionoscloudmachine_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ func defaultMachine() *IonosCloudMachine {
}
}

func setInvalidPoolRef(m *IonosCloudMachine, poolType string, kind, apiGroup, name string) {
ref := &corev1.TypedLocalObjectReference{
APIGroup: ptr.To(apiGroup),
Kind: kind,
Name: name,
}
switch poolType {
case "IPv6":
m.Spec.AdditionalNetworks[0].IPv6PoolRef = ref
case "IPv4":
m.Spec.AdditionalNetworks[0].IPv4PoolRef = ref
}
}

var _ = Describe("IonosCloudMachine Tests", func() {
AfterEach(func() {
m := &IonosCloudMachine{
Expand Down Expand Up @@ -337,6 +351,43 @@ var _ = Describe("IonosCloudMachine Tests", func() {
m.Spec.AdditionalNetworks[0].NetworkID = -1
Expect(k8sClient.Create(context.Background(), m)).ToNot(Succeed())
})
DescribeTable("should allow IPv4PoolRef.Kind GlobalInClusterIPPool and InClusterIPPool", func(kind string) {
m := defaultMachine()
m.Spec.AdditionalNetworks[0].IPv4PoolRef = &corev1.TypedLocalObjectReference{
APIGroup: ptr.To("ipam.cluster.x-k8s.io"),
Kind: kind,
Name: "ipv4-pool",
}
Expect(k8sClient.Create(context.Background(), m)).To(Succeed())
},
Entry("GlobalInClusterIPPool", "GlobalInClusterIPPool"),
Entry("InClusterIPPool", "InClusterIPPool"),
)
DescribeTable("should allow IPv6PoolRef.Kind GlobalInClusterIPPool and InClusterIPPool", func(kind string) {
m := defaultMachine()
m.Spec.AdditionalNetworks[0].IPv6PoolRef = &corev1.TypedLocalObjectReference{
APIGroup: ptr.To("ipam.cluster.x-k8s.io"),
Kind: kind,
Name: "ipv6-pool",
}
Expect(k8sClient.Create(context.Background(), m)).To(Succeed())
},
Entry("GlobalInClusterIPPool", "GlobalInClusterIPPool"),
Entry("InClusterIPPool", "InClusterIPPool"),
)
DescribeTable("must not allow invalid pool references",
func(poolType, kind, apiGroup, name string) {
m := defaultMachine()
setInvalidPoolRef(m, poolType, kind, apiGroup, name)
Expect(k8sClient.Create(context.Background(), m)).ToNot(Succeed())
},
Entry("invalid IPv6PoolRef with invalid kind", "IPv6", "SomeOtherIPPoolKind", "ipam.cluster.x-k8s.io", "ipv6-pool"),
Entry("invalid IPv6PoolRef with invalid apiGroup", "IPv6", "InClusterIPPool", "SomeWrongAPIGroup", "ipv6-pool"),
Entry("invalid IPv6PoolRef with empty name", "IPv6", "InClusterIPPool", "ipam.cluster.x-k8s.io", ""),
Entry("invalid IPv4PoolRef with invalid kind", "IPv4", "SomeOtherIPPoolKind", "ipam.cluster.x-k8s.io", "ipv4-pool"),
Entry("invalid IPv4PoolRef with invalid apiGroup", "IPv4", "InClusterIPPool", "SomeWrongAPIGroup", "ipv4-pool"),
Entry("invalid IPv4PoolRef with empty name", "IPv4", "InClusterIPPool", "ipam.cluster.x-k8s.io", ""),
)
})
})
Context("FailoverIP", func() {
Expand Down
40 changes: 40 additions & 0 deletions api/v1alpha1/ipam_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright 2024 IONOS Cloud.
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 v1alpha1

import (
corev1 "k8s.io/api/core/v1"
)

// IPAMConfig contains the config for ip address management.
type IPAMConfig struct {
// IPv4PoolRef is a reference to an IPAMConfig Pool resource, which exposes IPv4 addresses.
// The nic will use an available IP address from the referenced pool.
// +kubebuilder:validation:XValidation:rule="self.apiGroup == 'ipam.cluster.x-k8s.io'",message="ipv4PoolRef allows only IPAMConfig apiGroup ipam.cluster.x-k8s.io"
// +kubebuilder:validation:XValidation:rule="self.kind == 'InClusterIPPool' || self.kind == 'GlobalInClusterIPPool'",message="ipv4PoolRef allows either InClusterIPPool or GlobalInClusterIPPool"
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="ipv4PoolRef.name is required"
// +optional
IPv4PoolRef *corev1.TypedLocalObjectReference `json:"ipv4PoolRef,omitempty"`

// IPv6PoolRef is a reference to an IPAMConfig pool resource, which exposes IPv6 addresses.
// The nic will use an available IP address from the referenced pool.
// +kubebuilder:validation:XValidation:rule="self.apiGroup == 'ipam.cluster.x-k8s.io'",message="ipv6PoolRef allows only IPAMConfig apiGroup ipam.cluster.x-k8s.io"
// +kubebuilder:validation:XValidation:rule="self.kind == 'InClusterIPPool' || self.kind == 'GlobalInClusterIPPool'",message="ipv6PoolRef allows either InClusterIPPool or GlobalInClusterIPPool"
// +kubebuilder:validation:XValidation:rule="self.name != ''",message="ipv6PoolRef.name is required"
// +optional
IPv6PoolRef *corev1.TypedLocalObjectReference `json:"ipv6PoolRef,omitempty"`
}
2 changes: 2 additions & 0 deletions api/v1alpha1/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"testing"

"k8s.io/apimachinery/pkg/runtime"
ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
Expand Down Expand Up @@ -53,6 +54,7 @@ var _ = BeforeSuite(func() {

scheme := runtime.NewScheme()
Expect(AddToScheme(scheme)).To(Succeed())
Expect(ipamv1.AddToScheme(scheme)).To(Succeed())

cfg, err := testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expand Down
36 changes: 34 additions & 2 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/klog/v2"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1"
"sigs.k8s.io/cluster-api/util/flags"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/controller"
Expand All @@ -53,6 +54,8 @@ func init() {

utilruntime.Must(clusterv1.AddToScheme(scheme))
utilruntime.Must(infrav1.AddToScheme(scheme))
utilruntime.Must(ipamv1.AddToScheme(scheme))

//+kubebuilder:scaffold:scheme
}

Expand Down
Loading

0 comments on commit 354436f

Please sign in to comment.