Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Adds integration for NLB #216

Merged
merged 15 commits into from
Sep 20, 2024
Merged
120 changes: 109 additions & 11 deletions api/v1alpha1/ionoscloudloadbalancer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package v1alpha1

import (
"strconv"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
)
Expand Down Expand Up @@ -54,10 +56,6 @@ type LoadBalancerSource struct {
// NLB is used for setting up a network load balancer.
//+optional
NLB *NLBSpec `json:"nlb,omitempty"`

// KubeVIP is used for setting up a highly available control plane.
//+optional
KubeVIP *KubeVIPSpec `json:"kubeVIP,omitempty"`
}

// NLBSpec defines the spec for a network load balancer.
Expand All @@ -67,14 +65,18 @@ type NLBSpec struct {
//+kubebuilder:validation:Format=uuid
//+required
DatacenterID string `json:"datacenterID"`
}

// KubeVIPSpec defines the spec for a high availability load balancer.
type KubeVIPSpec struct {
// Image is the container image to use for the KubeVIP static pod.
// If not provided, the default image will be used.
// Algorithm is the load balancing algorithm.
//+kubebuilder:validation:Enum=ROUND_ROBIN;LEAST_CONNECTION;RANDOM;SOURCE_IP
//+kubebuilder:default=ROUND_ROBIN
//+optional
Image string `json:"image,omitempty"`
Algorithm string `json:"algorithm,omitempty"`

// Protocol is the load balancing protocol.
//+kubebuilder:validation:Enum=TCP;HTTP
//+kubebuilder:default=TCP
//+optional
Protocol string `json:"protocol,omitempty"`
}

// IonosCloudLoadBalancerStatus defines the observed state of IonosCloudLoadBalancer.
Expand All @@ -91,6 +93,22 @@ type IonosCloudLoadBalancerStatus struct {
// cloud resource that is being provisioned.
//+optional
CurrentRequest *ProvisioningRequest `json:"currentRequest,omitempty"`

// NLBStatus defines the status for a network load balancer.
//+optional
NLBStatus *NLBStatus `json:"nlbStatus,omitempty"`
}

// NLBStatus holds information about the NLB configuration of the load balancer.
type NLBStatus struct {
// ID is the ID of the network load balancer.
ID string `json:"id,omitempty"`

// PublicLANID is the ID of the LAN used for incoming traffic.
PublicLANID int32 `json:"publicLANID,omitempty"`

// PrivateLANID is the ID of the LAN used for outgoing traffic.
PrivateLANID int32 `json:"privateLANID,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down Expand Up @@ -125,6 +143,86 @@ func (l *IonosCloudLoadBalancer) SetConditions(conditions clusterv1.Conditions)
l.Status.Conditions = conditions
}

// SetCurrentRequest sets the current provisioning request.
func (l *IonosCloudLoadBalancer) SetCurrentRequest(method, status, requestPath string) {
l.Status.CurrentRequest = &ProvisioningRequest{
Method: method,
State: status,
RequestPath: requestPath,
}
}

// DeleteCurrentRequest deletes the current provisioning request.
func (l *IonosCloudLoadBalancer) DeleteCurrentRequest() {
l.Status.CurrentRequest = nil
}

// GetNLBID returns the NLB ID from the status.
func (l *IonosCloudLoadBalancer) GetNLBID() string {
if l.Status.NLBStatus == nil {
return ""
}

return l.Status.NLBStatus.ID
}

// SetNLBID sets the NLB ID in the status.
func (l *IonosCloudLoadBalancer) SetNLBID(nlbID string) {
if l.Status.NLBStatus == nil {
l.Status.NLBStatus = &NLBStatus{}
}

l.Status.NLBStatus.ID = nlbID
}

// SetPublicLANID sets the public LAN ID in the status.
func (l *IonosCloudLoadBalancer) SetPublicLANID(id string) error {
if l.Status.NLBStatus == nil {
l.Status.NLBStatus = &NLBStatus{}
}
lanID, err := strconv.ParseInt(id, 10, 32)
if err != nil {
return err
}

l.Status.NLBStatus.PublicLANID = int32(lanID)
return nil
}

// GetPublicLANID returns the public LAN ID from the status.
func (l *IonosCloudLoadBalancer) GetPublicLANID() string {
if l.Status.NLBStatus == nil {
return ""
}

return strconv.Itoa(int(l.Status.NLBStatus.PublicLANID))
}

// SetPrivateLANID sets the private LAN ID in the status.
func (l *IonosCloudLoadBalancer) SetPrivateLANID(id string) error {
if l.Status.NLBStatus == nil {
l.Status.NLBStatus = &NLBStatus{}
}

lanID, err := strconv.ParseInt(id, 10, 32)
if err != nil {
return err
}

l.Status.NLBStatus.PrivateLANID = int32(lanID)

return nil
}

// GetPrivateLANID returns the private LAN ID from the status.
func (l *IonosCloudLoadBalancer) GetPrivateLANID() string {
if l.Status.NLBStatus == nil {
return ""
}

return strconv.Itoa(int(l.Status.NLBStatus.PrivateLANID))
}

func init() {
objectTypes = append(objectTypes, &IonosCloudLoadBalancer{})
objectTypes = append(objectTypes, &IonosCloudLoadBalancer{}, &IonosCloudLoadBalancerList{})
}
38 changes: 18 additions & 20 deletions api/v1alpha1/ionoscloudloadbalancer_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,6 @@ var _ = Describe("IonosCloudLoadBalancer", func() {
})

Context("Create", func() {
When("Using a KubeVIP load balancer", func() {
It("Should succeed when no image is provided", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{KubeVIP: &KubeVIPSpec{}})
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed())
})
It("Should succeed with an endpoint and a port", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{KubeVIP: &KubeVIPSpec{}})
dlb.Spec.LoadBalancerEndpoint = exampleEndpoint
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed())
})
})
When("Using an NLB", func() {
It("Should fail when not providing a datacenter ID", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{}})
Expand All @@ -80,6 +69,24 @@ var _ = Describe("IonosCloudLoadBalancer", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID}})
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed())
})
It("Should have ROUND_ROBIN as the default algorithm", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID}})
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed())
Expect(dlb.Spec.NLB.Algorithm).To(Equal("ROUND_ROBIN"))
})
It("Should fail when providing an invalid algorithm", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID, Algorithm: "INVALID"}})
Expect(k8sClient.Create(context.Background(), dlb)).NotTo(Succeed())
})
It("Should have TCP as the default protocol", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID}})
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed())
Expect(dlb.Spec.NLB.Protocol).To(Equal("TCP"))
})
It("Should fail when providing an invalid protocol", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID, Protocol: "INVALID"}})
Expect(k8sClient.Create(context.Background(), dlb)).NotTo(Succeed())
})
It("Should succeed providing an endpoint and a port", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID}})
dlb.Spec.LoadBalancerEndpoint = exampleEndpoint
Expand All @@ -92,15 +99,6 @@ var _ = Describe("IonosCloudLoadBalancer", func() {
})
})
Context("Update", func() {
When("Using a KubeVIP load balancer", func() {
It("Should succeed creating a KubeVIP load balancer with an empty endpoint and updating it", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{KubeVIP: &KubeVIPSpec{}})
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed())

dlb.Spec.LoadBalancerEndpoint = exampleEndpoint
Expect(k8sClient.Update(context.Background(), dlb)).To(Succeed())
})
})
When("Using an NLB", func() {
It("Should fail when attempting to update the datacenter ID", func() {
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID}})
Expand Down
40 changes: 20 additions & 20 deletions api/v1alpha1/zz_generated.deepcopy.go

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

52 changes: 30 additions & 22 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ limitations under the License.
package main

import (
"context"
"flag"
"os"

Expand Down Expand Up @@ -100,47 +101,54 @@ func main() {

ctx := ctrl.SetupSignalHandler()

//+kubebuilder:scaffold:builder
if err := setupControllers(ctx, mgr); err != nil {
os.Exit(1)
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}

setupLog.Info("Starting manager")
if err := mgr.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}

func setupControllers(ctx context.Context, mgr ctrl.Manager) error {
const errMsg = "unable to create controller"
if err = iccontroller.NewIonosCloudClusterReconciler(mgr).SetupWithManager(
if err := iccontroller.NewIonosCloudClusterReconciler(mgr).SetupWithManager(
ctx,
mgr,
controller.Options{MaxConcurrentReconciles: icClusterConcurrency},
); err != nil {
setupLog.Error(err, errMsg, "controller", "IonosCloudCluster")
os.Exit(1)
return err
}
if err = iccontroller.NewIonosCloudMachineReconciler(mgr).SetupWithManager(
if err := iccontroller.NewIonosCloudMachineReconciler(mgr).SetupWithManager(
mgr,
controller.Options{MaxConcurrentReconciles: icMachineConcurrency},
); err != nil {
setupLog.Error(err, errMsg, "controller", "IonosCloudMachine")
os.Exit(1)
return err
}
if err = iccontroller.NewIonosCloudLoadBalancerReconciler(mgr).SetupWithManager(
if err := iccontroller.NewIonosCloudLoadBalancerReconciler(mgr).SetupWithManager(
ctx,
mgr,
controller.Options{MaxConcurrentReconciles: icLoadBalancerConcurrency},
); err != nil {
setupLog.Error(err, errMsg, "controller", "IonosCloudLoadBalancer")
os.Exit(1)
return err
}

//+kubebuilder:scaffold:builder

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}

setupLog.Info("Starting manager")
if err := mgr.Start(ctx); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
return nil
}

// initFlags parses the command line flags.
Expand Down
Loading
Loading