From 553084c2de8236882973e669d4b8780a495b800d Mon Sep 17 00:00:00 2001 From: Jan Jansen Date: Mon, 6 Nov 2023 15:10:58 +0100 Subject: [PATCH] rework to lan spec Signed-off-by: Jan Jansen --- README.md | 3 +- api/v1alpha1/ionoscloudcluster_types.go | 28 +-- api/v1alpha1/ionoscloudmachine_types.go | 12 ++ api/v1alpha1/zz_generated.deepcopy.go | 6 +- ...e.cluster.x-k8s.io_ionoscloudclusters.yaml | 2 + ...r.x-k8s.io_ionoscloudclustertemplates.yaml | 5 + .../cluster_template_ionoscloudcluster.yaml | 12 ++ ...noscloudmachinetemplate-control-plane.yaml | 5 + ...pha1_ionoscloudmachinetemplate-worker.yaml | 5 + .../ionoscloudcluster_controller.go | 148 ++++------------ .../ionoscloudcluster_controller_test.go | 22 +++ .../ionoscloudmachine_controller.go | 161 ++++++++++-------- .../ionoscloudmachine_controller_test.go | 34 ++++ 13 files changed, 228 insertions(+), 215 deletions(-) diff --git a/README.md b/README.md index 3322b58..c1d04e9 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The API itself is shared across multiple cloud providers allowing for IONOS Clou * Choice of Linux distribution between Ubuntu 22.04 and other cloud init distribution using Server Templates based on raw images from [image builder](image_builder). * Using cloud init for bootstrapping nodes. * Installs only the minimal components to bootstrap a control plane and workers. +* multi ipv4 lan's (private and public lan) # Roadmap @@ -28,7 +29,7 @@ The API itself is shared across multiple cloud providers allowing for IONOS Clou * failuredomains for control planes * failuredomains for machinedeployment * autoscaler integrations example -* multi lan (private and public lan) +* external managed datacenter --- diff --git a/api/v1alpha1/ionoscloudcluster_types.go b/api/v1alpha1/ionoscloudcluster_types.go index dfad53b..b07d5ab 100644 --- a/api/v1alpha1/ionoscloudcluster_types.go +++ b/api/v1alpha1/ionoscloudcluster_types.go @@ -51,26 +51,12 @@ const ( // issues with the creation of the datacenter. DataCenterCreationFailedReason = "DataCenterCreationFailed" - // PublicLanCreatedCondition documents the creation of the Lan - PublicLanCreatedCondition clusterv1.ConditionType = "PublicLanCreated" + // LanCreatedCondition documents the creation of the Lan + LanCreatedCondition clusterv1.ConditionType = "LanCreated" - // PublicLanCreationFailedReason (Severity=Error) documents a controller detecting + // LanCreationFailedReason (Severity=Error) documents a controller detecting // issues with the creation of the Lan. - PublicLanCreationFailedReason = "PublicLanCreationFailed" - - // PrivateLanCreatedCondition documents the creation of the Lan - PrivateLanCreatedCondition clusterv1.ConditionType = "PrivateLanCreated" - - // PrivateLanCreationFailedReason (Severity=Error) documents a controller detecting - // issues with the creation of the Lan. - PrivateLanCreationFailedReason = "PrivateLanCreationFailed" - - // InternetLanCreatedCondition documents the creation of the Lan - InternetLanCreatedCondition clusterv1.ConditionType = "InternetLanCreated" - - // InternetLanCreationFailedReason (Severity=Error) documents a controller detecting - // issues with the creation of the Lan. - InternetLanCreationFailedReason = "InternetLanCreationFailed" + LanCreationFailedReason = "LanCreationFailed" // LoadBalancerForwardingRuleCreatedCondition documents the creation of the ForwardingRule LoadBalancerForwardingRuleCreatedCondition clusterv1.ConditionType = "LoadBalancerForwardingRuleCreated" @@ -102,12 +88,10 @@ type IONOSCloudClusterSpec struct { IdentityName string `json:"identityName"` // +optional ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` - // +optional // +listType=map // +listMapKey=name - Lans []IONOSLanSpec `json:"lans,omitempty"` - // +optional - LoadBalancer *IONOSLoadBalancerSpec `json:"loadBalancer,omitempty"` + Lans []IONOSLanSpec `json:"lans"` + LoadBalancer IONOSLoadBalancerSpec `json:"loadBalancer"` // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="DataCenterID is immutable" DataCenterID string `json:"dataCenterID,omitempty"` diff --git a/api/v1alpha1/ionoscloudmachine_types.go b/api/v1alpha1/ionoscloudmachine_types.go index f7260cf..3902436 100644 --- a/api/v1alpha1/ionoscloudmachine_types.go +++ b/api/v1alpha1/ionoscloudmachine_types.go @@ -138,3 +138,15 @@ func (c *IONOSCloudMachine) EnsureNic(spec IONOSNicSpec) { } c.Spec.Nics = append(c.Spec.Nics, spec) } + +func (c *IONOSCloudMachine) NicByLan(name string) *IONOSNicSpec { + if name == "" { + return nil + } + for i := range c.Spec.Nics { + if c.Spec.Nics[i].LanRef.Name == name { + return &c.Spec.Nics[i] + } + } + return nil +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index dc0d810..f48fb50 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -192,11 +192,7 @@ func (in *IONOSCloudClusterSpec) DeepCopyInto(out *IONOSCloudClusterSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.LoadBalancer != nil { - in, out := &in.LoadBalancer, &out.LoadBalancer - *out = new(IONOSLoadBalancerSpec) - **out = **in - } + out.LoadBalancer = in.LoadBalancer if in.PublicLanID != nil { in, out := &in.PublicLanID, &out.PublicLanID *out = new(int32) diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudclusters.yaml index 8ae8b5b..d9fb1af 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudclusters.yaml @@ -134,6 +134,8 @@ spec: rule: self == oldSelf required: - identityName + - lans + - loadBalancer - location type: object x-kubernetes-validations: diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudclustertemplates.yaml index 07d3887..dc728ae 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_ionoscloudclustertemplates.yaml @@ -86,6 +86,9 @@ spec: - public type: object type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map loadBalancer: properties: id: @@ -140,6 +143,8 @@ spec: rule: self == oldSelf required: - identityName + - lans + - loadBalancer - location type: object x-kubernetes-validations: diff --git a/config/samples/cluster_template_ionoscloudcluster.yaml b/config/samples/cluster_template_ionoscloudcluster.yaml index a0a597a..d3dabf4 100644 --- a/config/samples/cluster_template_ionoscloudcluster.yaml +++ b/config/samples/cluster_template_ionoscloudcluster.yaml @@ -8,3 +8,15 @@ spec: controlPlaneEndpoint: host: "85.215.201.63" port: 6443 + lans: + - name: private + public: false + - name: public + public: true + - name: internet + public: true + loadBalancer: + listenerLanRef: + name: public + targetLanRef: + name: private diff --git a/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate-control-plane.yaml b/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate-control-plane.yaml index a3136ca..67c2781 100644 --- a/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate-control-plane.yaml +++ b/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate-control-plane.yaml @@ -12,3 +12,8 @@ spec: image: "a9152e16-2482-11ee-9eb0-82cf6aa9b8c3" type: "HDD" size: "25" + nics: + - lanRef: + name: internet + - lanRef: + name: private \ No newline at end of file diff --git a/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate-worker.yaml b/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate-worker.yaml index 9afb9eb..3de0c52 100644 --- a/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate-worker.yaml +++ b/config/samples/infrastructure_v1alpha1_ionoscloudmachinetemplate-worker.yaml @@ -12,3 +12,8 @@ spec: image: "a9152e16-2482-11ee-9eb0-82cf6aa9b8c3" type: "HDD" size: "25" + nics: + - lanRef: + name: internet + - lanRef: + name: private diff --git a/internal/controller/ionoscloudcluster_controller.go b/internal/controller/ionoscloudcluster_controller.go index d3b5900..cfc5c40 100644 --- a/internal/controller/ionoscloudcluster_controller.go +++ b/internal/controller/ionoscloudcluster_controller.go @@ -158,13 +158,8 @@ func (r *IONOSCloudClusterReconciler) reconcileNormal(ctx *context.ClusterContex return *result, err } - if result, err := r.reconcilePrivateLan(ctx); err != nil { - conditions.MarkFalse(ctx.IONOSCloudCluster, v1alpha1.PrivateLanCreatedCondition, v1alpha1.PrivateLanCreationFailedReason, clusterv1.ConditionSeverityError, err.Error()) - return *result, err - } - - if result, err := r.reconcilePublicLan(ctx); err != nil { - conditions.MarkFalse(ctx.IONOSCloudCluster, v1alpha1.PublicLanCreatedCondition, v1alpha1.PublicLanCreationFailedReason, clusterv1.ConditionSeverityError, err.Error()) + if result, err := r.reconcileLan(ctx); err != nil { + conditions.MarkFalse(ctx.IONOSCloudCluster, v1alpha1.LanCreatedCondition, v1alpha1.LanCreationFailedReason, clusterv1.ConditionSeverityError, err.Error()) return *result, err } @@ -173,11 +168,6 @@ func (r *IONOSCloudClusterReconciler) reconcileNormal(ctx *context.ClusterContex return *result, err } - if result, err := r.reconcileInternet(ctx); err != nil { - conditions.MarkFalse(ctx.IONOSCloudCluster, v1alpha1.InternetLanCreatedCondition, v1alpha1.InternetLanCreationFailedReason, clusterv1.ConditionSeverityError, err.Error()) - return *result, err - } - ctx.IONOSCloudCluster.Status.Ready = true return reconcile.Result{}, nil @@ -211,107 +201,49 @@ func (r *IONOSCloudClusterReconciler) reconcileDataCenter(ctx *context.ClusterCo return nil, nil } -func (r *IONOSCloudClusterReconciler) reconcilePrivateLan(ctx *context.ClusterContext) (*reconcile.Result, error) { - ctx.Logger.Info("Reconciling private Lan") - if ctx.IONOSCloudCluster.Spec.PrivateLanID == nil { - lanID, err := createLan(ctx, false) - if err != nil { - return &reconcile.Result{}, errors.Wrap(err, "error creating private Lan") +func (r *IONOSCloudClusterReconciler) reconcileLan(ctx *context.ClusterContext) (*reconcile.Result, error) { + for i := range ctx.IONOSCloudCluster.Spec.Lans { + lanSpec := &ctx.IONOSCloudCluster.Spec.Lans[i] + ctx.Logger.Info(fmt.Sprintf("Reconciling %s Lan", lanSpec.Name)) + if lanSpec.LanID == nil { + lanID, err := createLan(ctx, lanSpec.Public) + if err != nil { + return &reconcile.Result{}, err + } + lanSpec.LanID = lanID } - ctx.IONOSCloudCluster.Spec.PrivateLanID = lanID - } - - ctx.IONOSCloudCluster.EnsureLan(v1alpha1.IONOSLanSpec{ - Name: "private", - LanID: ctx.IONOSCloudCluster.Spec.PrivateLanID, - Public: false, - }) - // check status - lanId := fmt.Sprint(*ctx.IONOSCloudCluster.Spec.PrivateLanID) - lan, resp, err := ctx.IONOSClient.GetLan(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lanId) + // check status + lanId := fmt.Sprint(*lanSpec.LanID) + lan, resp, err := ctx.IONOSClient.GetLan(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lanId) - if err != nil && resp.StatusCode != http.StatusNotFound { - return &reconcile.Result{}, errors.Wrap(err, "error getting private Lan") - } - - if resp.StatusCode == http.StatusNotFound || *lan.Metadata.State == STATE_BUSY { - return &reconcile.Result{RequeueAfter: defaultRetryIntervalOnBusy}, errors.New("private Lan not available (yet)") - } - - conditions.MarkTrue(ctx.IONOSCloudCluster, v1alpha1.PrivateLanCreatedCondition) - - return nil, nil -} - -func (r *IONOSCloudClusterReconciler) reconcilePublicLan(ctx *context.ClusterContext) (*reconcile.Result, error) { - ctx.Logger.Info("Reconciling public Lan") - if ctx.IONOSCloudCluster.Spec.PublicLanID == nil { - lanID, err := createLan(ctx, true) - if err != nil { - return &reconcile.Result{}, err + if err != nil && resp.StatusCode != http.StatusNotFound { + return &reconcile.Result{}, errors.Wrap(err, fmt.Sprintf("error getting %s Lan", lanSpec.Name)) } - ctx.IONOSCloudCluster.Spec.PublicLanID = lanID - } - - ctx.IONOSCloudCluster.EnsureLan(v1alpha1.IONOSLanSpec{ - Name: "public", - LanID: ctx.IONOSCloudCluster.Spec.PublicLanID, - Public: true, - }) - // check status - lanId := fmt.Sprint(*ctx.IONOSCloudCluster.Spec.PublicLanID) - lan, resp, err := ctx.IONOSClient.GetLan(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lanId) - - if err != nil && resp.StatusCode != http.StatusNotFound { - return &reconcile.Result{}, errors.Wrap(err, "error getting public Lan") - } - - if resp.StatusCode == http.StatusNotFound || *lan.Metadata.State == STATE_BUSY { - return &reconcile.Result{RequeueAfter: defaultRetryIntervalOnBusy}, errors.New("public Lan not available (yet)") + if resp.StatusCode == http.StatusNotFound || *lan.Metadata.State == STATE_BUSY { + return &reconcile.Result{RequeueAfter: defaultRetryIntervalOnBusy}, errors.New(fmt.Sprintf("%s Lan not available (yet)", lanSpec.Name)) + } } - conditions.MarkTrue(ctx.IONOSCloudCluster, v1alpha1.PublicLanCreatedCondition) + conditions.MarkTrue(ctx.IONOSCloudCluster, v1alpha1.LanCreatedCondition) return nil, nil } -func (r *IONOSCloudClusterReconciler) reconcileInternet(ctx *context.ClusterContext) (*reconcile.Result, error) { - ctx.Logger.Info("Reconciling internet") - if ctx.IONOSCloudCluster.Spec.InternetLanID == nil { - lanID, err := createLan(ctx, true) - if err != nil { - return &reconcile.Result{}, err - } - ctx.IONOSCloudCluster.Spec.InternetLanID = lanID - } - - ctx.IONOSCloudCluster.EnsureLan(v1alpha1.IONOSLanSpec{ - Name: "internet", - LanID: ctx.IONOSCloudCluster.Spec.InternetLanID, - Public: true, - }) - - // check status - lanId := fmt.Sprint(*ctx.IONOSCloudCluster.Spec.InternetLanID) - lan, resp, err := ctx.IONOSClient.GetLan(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lanId) +func (r *IONOSCloudClusterReconciler) reconcileLoadBalancer(ctx *context.ClusterContext) (*reconcile.Result, error) { + ctx.Logger.Info("Reconciling LoadBalancer") + lbSpec := &ctx.IONOSCloudCluster.Spec.LoadBalancer + listenerLan := ctx.IONOSCloudCluster.Lan(lbSpec.ListenerLanRef.Name) + targetLan := ctx.IONOSCloudCluster.Lan(lbSpec.TargetLanRef.Name) - if err != nil && resp.StatusCode != http.StatusNotFound { - return &reconcile.Result{}, errors.New("error getting internet Lan") + if listenerLan == nil { + return &reconcile.Result{RequeueAfter: defaultRetryIntervalOnBusy}, errors.New(fmt.Sprintf("listener lb %s Lan not available (yet)", lbSpec.ListenerLanRef.Name)) } - - if resp.StatusCode == http.StatusNotFound || *lan.Metadata.State == STATE_BUSY { - return &reconcile.Result{RequeueAfter: defaultRetryIntervalOnBusy}, errors.New("internet Lan not available (yet)") + if targetLan == nil { + return &reconcile.Result{RequeueAfter: defaultRetryIntervalOnBusy}, errors.New(fmt.Sprintf("target lb %s Lan not available (yet)", lbSpec.TargetLanRef.Name)) } - conditions.MarkTrue(ctx.IONOSCloudCluster, v1alpha1.InternetLanCreatedCondition) - - return nil, nil -} - -func (r *IONOSCloudClusterReconciler) reconcileLoadBalancer(ctx *context.ClusterContext) (*reconcile.Result, error) { - ctx.Logger.Info("Reconciling LoadBalancer") - if ctx.IONOSCloudCluster.Spec.LoadBalancerID == "" { + if lbSpec.ID == "" { loadBalancerName := fmt.Sprintf("lb-%s", ctx.Cluster.Name) loadBalancer := ionoscloud.NetworkLoadBalancer{ Entities: &ionoscloud.NetworkLoadBalancerEntities{ @@ -330,9 +262,9 @@ func (r *IONOSCloudClusterReconciler) reconcileLoadBalancer(ctx *context.Cluster }, }, Properties: &ionoscloud.NetworkLoadBalancerProperties{ - ListenerLan: ctx.IONOSCloudCluster.Spec.PublicLanID, + ListenerLan: listenerLan.LanID, Name: &loadBalancerName, - TargetLan: ctx.IONOSCloudCluster.Spec.PrivateLanID, + TargetLan: targetLan.LanID, Ips: &[]string{ctx.IONOSCloudCluster.Spec.ControlPlaneEndpoint.Host}, }, } @@ -340,21 +272,11 @@ func (r *IONOSCloudClusterReconciler) reconcileLoadBalancer(ctx *context.Cluster if err != nil { return &reconcile.Result{}, err } - ctx.IONOSCloudCluster.Spec.LoadBalancerID = *loadBalancer.Id - } - - ctx.IONOSCloudCluster.Spec.LoadBalancer = &v1alpha1.IONOSLoadBalancerSpec{ - ID: ctx.IONOSCloudCluster.Spec.LoadBalancerID, - ListenerLanRef: v1alpha1.IONOSLanRefSpec{ - Name: "public", - }, - TargetLanRef: v1alpha1.IONOSLanRefSpec{ - Name: "private", - }, + lbSpec.ID = *loadBalancer.Id } // check status - loadBalancer, resp, err := ctx.IONOSClient.GetLoadBalancer(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, ctx.IONOSCloudCluster.Spec.LoadBalancerID) + loadBalancer, resp, err := ctx.IONOSClient.GetLoadBalancer(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lbSpec.ID) if err != nil && resp.StatusCode != http.StatusNotFound { return &reconcile.Result{}, errors.Wrap(err, "error getting loadbalancer") } diff --git a/internal/controller/ionoscloudcluster_controller_test.go b/internal/controller/ionoscloudcluster_controller_test.go index 505963f..e868968 100644 --- a/internal/controller/ionoscloudcluster_controller_test.go +++ b/internal/controller/ionoscloudcluster_controller_test.go @@ -86,6 +86,28 @@ var _ = Describe("IONOSCloudCluster controller", func() { Port: 6443, }, IdentityName: CAPICClusterIdentityName, + LoadBalancer: v1alpha1.IONOSLoadBalancerSpec{ + ListenerLanRef: v1alpha1.IONOSLanRefSpec{ + Name: "public", + }, + TargetLanRef: v1alpha1.IONOSLanRefSpec{ + Name: "private", + }, + }, + Lans: []v1alpha1.IONOSLanSpec{ + { + Name: "public", + Public: true, + }, + { + Name: "private", + Public: false, + }, + { + Name: "internet", + Public: true, + }, + }, }, } identitySecret = &v1.Secret{ diff --git a/internal/controller/ionoscloudmachine_controller.go b/internal/controller/ionoscloudmachine_controller.go index cd9633e..1600453 100644 --- a/internal/controller/ionoscloudmachine_controller.go +++ b/internal/controller/ionoscloudmachine_controller.go @@ -20,6 +20,10 @@ import ( goctx "context" b64 "encoding/base64" "fmt" + "net/http" + "strings" + "time" + v1alpha1 "github.com/GDATASoftwareAG/cluster-api-provider-ionoscloud/api/v1alpha1" "github.com/GDATASoftwareAG/cluster-api-provider-ionoscloud/internal/context" "github.com/GDATASoftwareAG/cluster-api-provider-ionoscloud/internal/utils" @@ -29,7 +33,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" apitypes "k8s.io/apimachinery/pkg/types" - "net/http" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" clusterutilv1 "sigs.k8s.io/cluster-api/util" @@ -41,8 +44,6 @@ import ( ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "strings" - "time" ) // IONOSCloudMachineReconciler reconciles a IONOSCloudMachine object @@ -183,48 +184,8 @@ func (r *IONOSCloudMachineReconciler) reconcileDelete(ctx *context.MachineContex } } - if ctx.IONOSCloudMachine.Spec.IP != nil { - if rules, _, err := ctx.IONOSClient.GetLoadBalancerForwardingRules(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, ctx.IONOSCloudCluster.Spec.LoadBalancerID); err != nil { - return reconcile.Result{}, err - } else { - targetToDelete := ionoscloud.NetworkLoadBalancerForwardingRuleTarget{ - Ip: ctx.IONOSCloudMachine.Spec.IP, - Port: ionoscloud.PtrInt32(6443), - Weight: ionoscloud.PtrInt32(1), - } - for _, rule := range *rules.Items { - if *rule.Properties.ListenerPort != 6443 { - // we only care about the api server port - continue - } - - for _, target := range *rule.Properties.Targets { - if *target.Ip == *targetToDelete.Ip { - targets := *rule.Properties.Targets - targets = findAndDeleteByIP(targets, targetToDelete) - properties := ionoscloud.NetworkLoadBalancerForwardingRuleProperties{ - Algorithm: rule.Properties.Algorithm, - HealthCheck: rule.Properties.HealthCheck, - ListenerIp: rule.Properties.ListenerIp, - ListenerPort: rule.Properties.ListenerPort, - Name: rule.Properties.Name, - Protocol: rule.Properties.Protocol, - Targets: &targets, - } - if _, _, err = ctx.IONOSClient.PatchLoadBalancerForwardingRule( - ctx, - ctx.IONOSCloudCluster.Spec.DataCenterID, - ctx.IONOSCloudCluster.Spec.LoadBalancerID, - *rule.Id, - properties, - ); err != nil { - return reconcile.Result{}, err - } - - } - } - } - } + if err = r.reconcileDeleteLoadBalancerForwardingRule(ctx); err != nil { + return reconcile.Result{}, err } conditions.MarkFalse(ctx.IONOSCloudCluster, v1alpha1.ServerCreatedCondition, "ServerDeleted", clusterv1.ConditionSeverityInfo, "") @@ -234,6 +195,62 @@ func (r *IONOSCloudMachineReconciler) reconcileDelete(ctx *context.MachineContex return reconcile.Result{}, nil } +func (r *IONOSCloudMachineReconciler) reconcileDeleteLoadBalancerForwardingRule(ctx *context.MachineContext) error { + if !clusterutilv1.IsControlPlaneMachine(ctx.Machine) { + ctx.Logger.Info("Deleting IONOSCloudMachine is not a control plane...no forwarding rule deletion required.") + return nil + } + + lbSpec := ctx.IONOSCloudCluster.Spec.LoadBalancer + nic := ctx.IONOSCloudMachine.NicByLan(lbSpec.TargetLanRef.Name) + + if nic == nil && nic.PrimaryIP != nil { + return nil + } + + if rules, _, err := ctx.IONOSClient.GetLoadBalancerForwardingRules(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lbSpec.ID); err != nil { + return err + } else { + targetToDelete := ionoscloud.NetworkLoadBalancerForwardingRuleTarget{ + Ip: nic.PrimaryIP, + Port: ionoscloud.PtrInt32(6443), + Weight: ionoscloud.PtrInt32(1), + } + for _, rule := range *rules.Items { + if *rule.Properties.ListenerPort != 6443 { + // we only care about the api server port + continue + } + + for _, target := range *rule.Properties.Targets { + if *target.Ip == *targetToDelete.Ip { + targets := *rule.Properties.Targets + targets = findAndDeleteByIP(targets, targetToDelete) + properties := ionoscloud.NetworkLoadBalancerForwardingRuleProperties{ + Algorithm: rule.Properties.Algorithm, + HealthCheck: rule.Properties.HealthCheck, + ListenerIp: rule.Properties.ListenerIp, + ListenerPort: rule.Properties.ListenerPort, + Name: rule.Properties.Name, + Protocol: rule.Properties.Protocol, + Targets: &targets, + } + if _, _, err = ctx.IONOSClient.PatchLoadBalancerForwardingRule( + ctx, + ctx.IONOSCloudCluster.Spec.DataCenterID, + lbSpec.ID, + *rule.Id, + properties, + ); err != nil { + return err + } + } + } + } + } + return nil +} + func (r *IONOSCloudMachineReconciler) reconcileNormal(ctx *context.MachineContext) (reconcile.Result, error) { ctx.Logger.Info("Reconciling IONOSCloudCluster") @@ -317,25 +334,21 @@ func (r *IONOSCloudMachineReconciler) reconcileServer(ctx *context.MachineContex return &reconcile.Result{}, errors.Wrapf(err, "invalid spec.bootvolume.disksize") } + nics := make([]ionoscloud.Nic, 0) + for _, nic := range ctx.IONOSCloudMachine.Spec.Nics { + lanSpec := ctx.IONOSCloudCluster.Lan(nic.LanRef.Name) + nics = append(nics, ionoscloud.Nic{ + Properties: &ionoscloud.NicProperties{ + Dhcp: ionoscloud.ToPtr(true), + Lan: lanSpec.LanID, + Name: ionoscloud.ToPtr(fmt.Sprintf("%s-nic-%s", ctx.IONOSCloudMachine.Name, lanSpec.Name)), + }, + }) + } server := ionoscloud.Server{ Entities: &ionoscloud.ServerEntities{ Nics: &ionoscloud.Nics{ - Items: &[]ionoscloud.Nic{ - { - Properties: &ionoscloud.NicProperties{ - Dhcp: ionoscloud.ToPtr(true), - Lan: ctx.IONOSCloudCluster.Spec.PrivateLanID, - Name: ionoscloud.ToPtr(fmt.Sprintf("%s-nic-lb", ctx.IONOSCloudMachine.Name)), - }, - }, - { - Properties: &ionoscloud.NicProperties{ - Dhcp: ionoscloud.ToPtr(true), - Lan: ctx.IONOSCloudCluster.Spec.InternetLanID, - Name: ionoscloud.ToPtr(fmt.Sprintf("%s-nic-internet", ctx.IONOSCloudMachine.Name)), - }, - }, - }, + Items: &nics, }, Volumes: &ionoscloud.AttachedVolumes{ Items: &[]ionoscloud.Volume{ @@ -356,7 +369,6 @@ func (r *IONOSCloudMachineReconciler) reconcileServer(ctx *context.MachineContex }, }, Properties: &ionoscloud.ServerProperties{ - Cores: ctx.IONOSCloudMachine.Spec.Cores, CpuFamily: ctx.IONOSCloudMachine.Spec.CpuFamily, Name: ionoscloud.ToPtr(ctx.IONOSCloudMachine.Name), @@ -381,17 +393,15 @@ func (r *IONOSCloudMachineReconciler) reconcileServer(ctx *context.MachineContex if resp.StatusCode == http.StatusNotFound || (server.Metadata != nil && *server.Metadata.State == STATE_BUSY) { return &reconcile.Result{RequeueAfter: defaultRetryIntervalOnBusy}, errors.New("server not yet created") } - + ipObtained := false nics := *server.Entities.Nics.Items for _, nic := range nics { ips := *nic.Properties.Ips - if strings.HasSuffix(*nic.Properties.Name, "-nic-lb") { - ctx.IONOSCloudMachine.Spec.IP = &ips[0] - } lan := ctx.IONOSCloudCluster.LanBy(nic.Properties.Lan) - if lan == nil { + if lan == nil || len(ips) == 0 { continue } + ipObtained = true ctx.IONOSCloudMachine.EnsureNic(v1alpha1.IONOSNicSpec{ LanRef: v1alpha1.IONOSLanRefSpec{ Name: lan.Name, @@ -400,7 +410,7 @@ func (r *IONOSCloudMachineReconciler) reconcileServer(ctx *context.MachineContex }) } - if ctx.IONOSCloudMachine.Spec.IP == nil { + if !ipObtained { return &reconcile.Result{RequeueAfter: defaultRetryIntervalOnBusy}, errors.New("server does not have an ip yet") } @@ -413,21 +423,24 @@ func (r *IONOSCloudMachineReconciler) reconcileLoadBalancerForwardingRule(ctx *c ctx.Logger.Info("Reconciling load balancer forwarding rule") if !clusterutilv1.IsControlPlaneMachine(ctx.Machine) { - ctx.Logger.Info("Reconciled IONOSCLoudMachine is not a control plane...no forwarding rule needed.") + ctx.Logger.Info("Reconciled IONOSCloudMachine is not a control plane...no forwarding rule needed.") return nil, nil } - if ctx.IONOSCloudMachine.Spec.IP == nil { - return &reconcile.Result{}, errors.New("server has not obtained an ip yet") + lbSpec := ctx.IONOSCloudCluster.Spec.LoadBalancer + nic := ctx.IONOSCloudMachine.NicByLan(lbSpec.TargetLanRef.Name) + + if nic == nil && nic.PrimaryIP != nil { + return &reconcile.Result{}, errors.New("server has not obtained an target ip yet") } if ctx.IONOSCloudMachine.Spec.ProviderID != "" { - rules, _, err := ctx.IONOSClient.GetLoadBalancerForwardingRules(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, ctx.IONOSCloudCluster.Spec.LoadBalancerID) + rules, _, err := ctx.IONOSClient.GetLoadBalancerForwardingRules(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lbSpec.ID) if err != nil { return &reconcile.Result{}, errors.Wrap(err, "error getting forwarding rules") } desiredTarget := ionoscloud.NetworkLoadBalancerForwardingRuleTarget{ - Ip: ctx.IONOSCloudMachine.Spec.IP, + Ip: nic.PrimaryIP, Port: ionoscloud.PtrInt32(6443), Weight: ionoscloud.PtrInt32(1), } @@ -461,7 +474,7 @@ func (r *IONOSCloudMachineReconciler) reconcileLoadBalancerForwardingRule(ctx *c Targets: &targets, } - if _, _, err = ctx.IONOSClient.PatchLoadBalancerForwardingRule(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, ctx.IONOSCloudCluster.Spec.LoadBalancerID, *rule.Id, properties); err != nil { + if _, _, err = ctx.IONOSClient.PatchLoadBalancerForwardingRule(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lbSpec.ID, *rule.Id, properties); err != nil { return &reconcile.Result{}, errors.Wrap(err, "error updating forwarding rules") } } diff --git a/internal/controller/ionoscloudmachine_controller_test.go b/internal/controller/ionoscloudmachine_controller_test.go index 77fbd1a..53f5427 100644 --- a/internal/controller/ionoscloudmachine_controller_test.go +++ b/internal/controller/ionoscloudmachine_controller_test.go @@ -111,6 +111,28 @@ var _ = Describe("IONOSCloudMachine controller", func() { Port: 6443, }, IdentityName: CAPICClusterIdentityName, + LoadBalancer: v1alpha1.IONOSLoadBalancerSpec{ + ListenerLanRef: v1alpha1.IONOSLanRefSpec{ + Name: "public", + }, + TargetLanRef: v1alpha1.IONOSLanRefSpec{ + Name: "private", + }, + }, + Lans: []v1alpha1.IONOSLanSpec{ + { + Name: "public", + Public: true, + }, + { + Name: "private", + Public: false, + }, + { + Name: "internet", + Public: true, + }, + }, }, } capicMachine = &v1alpha1.IONOSCloudMachine{ @@ -139,6 +161,18 @@ var _ = Describe("IONOSCloudMachine controller", func() { }, IP: nil, ProviderID: "", + Nics: []v1alpha1.IONOSNicSpec{ + { + LanRef: v1alpha1.IONOSLanRefSpec{ + Name: "private", + }, + }, + { + LanRef: v1alpha1.IONOSLanRefSpec{ + Name: "internet", + }, + }, + }, }, } identitySecret = &v1.Secret{