-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Provide new controller for handling generic load balancer provision…
…ing (#178) **What is the purpose of this pull request/Why do we need it?** This PR introduces a new controller to handle the setup of load balancing for the control plane. This PR does not provide the actual implementations as those will be done in separate PRs. As this is intended to provide an interface to work on multiple implementations at the same time, this branch will not be merged into main to not have an intermediate state. **Checklist:** - [x] Unit Tests added - [x] Includes [emojis](https://github.com/kubernetes-sigs/kubebuilder-release-tools?tab=readme-ov-file#kubebuilder-project-versioning)
- Loading branch information
Showing
22 changed files
with
1,305 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
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 ( | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" | ||
) | ||
|
||
const ( | ||
// LoadBalancerFinalizer allows cleanup of resources, which are | ||
// associated with the IonosCloudLoadBalancer before removing it from the API server. | ||
LoadBalancerFinalizer = "ionoscloudloadbalancer.infrastructure.cluster.x-k8s.io" | ||
|
||
// LoadBalancerReadyCondition is the condition for the IonosCloudLoadBalancer, which indicates that the load balancer is ready. | ||
LoadBalancerReadyCondition clusterv1.ConditionType = "LoadBalancerReady" | ||
|
||
// InvalidEndpointConfigurationReason indicates that the endpoints for IonosCloudCluster and IonosCloudLoadBalancer | ||
// have not been properly configured. | ||
InvalidEndpointConfigurationReason = "InvalidEndpointConfiguration" | ||
) | ||
|
||
// IonosCloudLoadBalancerSpec defines the desired state of IonosCloudLoadBalancer. | ||
type IonosCloudLoadBalancerSpec struct { | ||
// LoadBalancerEndpoint represents the endpoint of the load balanced control plane. | ||
// If the endpoint isn't provided, the controller will reserve a new public IP address. | ||
// The port is optional and defaults to 6443. | ||
// | ||
// For external load balancers, the endpoint and port must be provided. | ||
//+kubebuilder:validation:XValidation:rule="self.host == oldSelf.host || oldSelf.host == ''",message="control plane endpoint host cannot be updated" | ||
//+kubebuilder:validation:XValidation:rule="self.port == oldSelf.port || oldSelf.port == 0",message="control plane endpoint port cannot be updated" | ||
LoadBalancerEndpoint clusterv1.APIEndpoint `json:"loadBalancerEndpoint,omitempty"` | ||
|
||
// LoadBalancerSource is the actual load balancer definition. | ||
LoadBalancerSource `json:",inline"` | ||
} | ||
|
||
// LoadBalancerSource defines the source of the load balancer. | ||
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. | ||
type NLBSpec struct { | ||
// DatacenterID is the ID of the datacenter where the load balancer should be created. | ||
//+kubebuilder:validation:XValidation:rule="self == oldSelf",message="datacenterID is immutable" | ||
//+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. | ||
//+optional | ||
Image string `json:"image,omitempty"` | ||
} | ||
|
||
// IonosCloudLoadBalancerStatus defines the observed state of IonosCloudLoadBalancer. | ||
type IonosCloudLoadBalancerStatus struct { | ||
// Ready indicates that the load balancer is ready. | ||
//+optional | ||
Ready bool `json:"ready,omitempty"` | ||
|
||
// Conditions defines current service state of the IonosCloudLoadBalancer. | ||
//+optional | ||
Conditions clusterv1.Conditions `json:"conditions,omitempty"` | ||
|
||
// CurrentRequest shows the current provisioning request for any | ||
// cloud resource that is being provisioned. | ||
//+optional | ||
CurrentRequest *ProvisioningRequest `json:"currentRequest,omitempty"` | ||
} | ||
|
||
// +kubebuilder:object:root=true | ||
// +kubebuilder:subresource:status | ||
|
||
// IonosCloudLoadBalancer is the Schema for the ionoscloudloadbalancers API | ||
// +kubebuilder:resource:path=ionoscloudloadbalancers,scope=Namespaced,categories=cluster-api;ionoscloud,shortName=iclb | ||
type IonosCloudLoadBalancer struct { | ||
metav1.TypeMeta `json:",inline"` | ||
metav1.ObjectMeta `json:"metadata,omitempty"` | ||
|
||
Spec IonosCloudLoadBalancerSpec `json:"spec,omitempty"` | ||
Status IonosCloudLoadBalancerStatus `json:"status,omitempty"` | ||
} | ||
|
||
// +kubebuilder:object:root=true | ||
|
||
// IonosCloudLoadBalancerList contains a list of IonosCloudLoadBalancer. | ||
type IonosCloudLoadBalancerList struct { | ||
metav1.TypeMeta `json:",inline"` | ||
metav1.ListMeta `json:"metadata,omitempty"` | ||
Items []IonosCloudLoadBalancer `json:"items"` | ||
} | ||
|
||
// GetConditions returns the conditions from the status. | ||
func (l *IonosCloudLoadBalancer) GetConditions() clusterv1.Conditions { | ||
return l.Status.Conditions | ||
} | ||
|
||
// SetConditions sets the conditions in the status. | ||
func (l *IonosCloudLoadBalancer) SetConditions(conditions clusterv1.Conditions) { | ||
l.Status.Conditions = conditions | ||
} | ||
|
||
func init() { | ||
objectTypes = append(objectTypes, &IonosCloudLoadBalancer{}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
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 ( | ||
"context" | ||
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
) | ||
|
||
const ( | ||
exampleDatacenterID = "fe3b4e3d-3b0e-4e6c-9e3e-4f3c9e3e4f3c" | ||
exampleSecondaryDatacenterID = "fe3b4e3d-3b0e-4e6c-9e3e-4f3c9e3e4f3d" | ||
) | ||
|
||
var exampleEndpoint = clusterv1.APIEndpoint{ | ||
Host: "example.com", | ||
Port: 6443, | ||
} | ||
|
||
func defaultLoadBalancer(source LoadBalancerSource) *IonosCloudLoadBalancer { | ||
return &IonosCloudLoadBalancer{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "test-loadbalancer", | ||
Namespace: metav1.NamespaceDefault, | ||
}, | ||
Spec: IonosCloudLoadBalancerSpec{ | ||
LoadBalancerSource: source, | ||
}, | ||
} | ||
} | ||
|
||
var _ = Describe("IonosCloudLoadBalancer", func() { | ||
AfterEach(func() { | ||
err := k8sClient.Delete(context.Background(), defaultLoadBalancer(LoadBalancerSource{})) | ||
Expect(client.IgnoreNotFound(err)).To(Succeed()) | ||
}) | ||
|
||
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{}}) | ||
Expect(k8sClient.Create(context.Background(), dlb)).NotTo(Succeed()) | ||
}) | ||
It("Should fail when not providing a uuid for the datacenter ID", func() { | ||
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: "something-invalid"}}) | ||
Expect(k8sClient.Create(context.Background(), dlb)).NotTo(Succeed()) | ||
}) | ||
It("Should succeed when providing a datacenter ID", func() { | ||
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID}}) | ||
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed()) | ||
}) | ||
It("Should succeed providing an endpoint and a port", func() { | ||
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{DatacenterID: exampleDatacenterID}}) | ||
dlb.Spec.LoadBalancerEndpoint = exampleEndpoint | ||
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed()) | ||
}) | ||
It("Should fail when providing a host and a port without a datacenter ID", func() { | ||
dlb := defaultLoadBalancer(LoadBalancerSource{NLB: &NLBSpec{}}) | ||
dlb.Spec.LoadBalancerEndpoint = exampleEndpoint | ||
Expect(k8sClient.Create(context.Background(), dlb)).NotTo(Succeed()) | ||
}) | ||
}) | ||
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}}) | ||
Expect(k8sClient.Create(context.Background(), dlb)).To(Succeed()) | ||
|
||
dlb.Spec.NLB.DatacenterID = exampleSecondaryDatacenterID | ||
Expect(k8sClient.Update(context.Background(), dlb)).NotTo(Succeed()) | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.