diff --git a/apinetlet/api/v1alpha1/common_types.go b/apinetlet/api/v1alpha1/common_types.go index 8f0a9749..febed732 100644 --- a/apinetlet/api/v1alpha1/common_types.go +++ b/apinetlet/api/v1alpha1/common_types.go @@ -27,5 +27,9 @@ const ( NATGatewayNamespaceLabel = "apinetlet.api.onmetal.de/natgateway-namespace" NATGatewayNameLabel = "apinetlet.api.onmetal.de/natgateway-name" + LoadBalancerUIDLabel = "apinetlet.api.onmetal.de/loadbalancer-uid" + LoadBalancerNamespaceLabel = "apinetlet.api.onmetal.de/loadbalancer-namespace" + LoadBalancerNameLabel = "apinetlet.api.onmetal.de/loadbalancer-name" + FieldOwner = "apinetlet.api.onmetal.de/field-owner" ) diff --git a/apinetlet/controllers/controllers_suite_test.go b/apinetlet/controllers/controllers_suite_test.go index a3d3b681..f39d681a 100644 --- a/apinetlet/controllers/controllers_suite_test.go +++ b/apinetlet/controllers/controllers_suite_test.go @@ -156,7 +156,13 @@ func SetupTest(ctx context.Context) *corev1.Namespace { APINetNamespace: ns.Name, }).SetupWithManager(k8sManager, k8sManager)).To(Succeed()) - Expect((&NatGatewayReconciler{ + Expect((&NATGatewayReconciler{ + Client: k8sManager.GetClient(), + APINetClient: k8sManager.GetClient(), + APINetNamespace: ns.Name, + }).SetupWithManager(k8sManager, k8sManager)).To(Succeed()) + + Expect((&LoadBalancerReconciler{ Client: k8sManager.GetClient(), APINetClient: k8sManager.GetClient(), APINetNamespace: ns.Name, diff --git a/apinetlet/controllers/loadbalancer_controller.go b/apinetlet/controllers/loadbalancer_controller.go new file mode 100644 index 00000000..7fa645d1 --- /dev/null +++ b/apinetlet/controllers/loadbalancer_controller.go @@ -0,0 +1,270 @@ +// Copyright 2022 OnMetal authors +// +// 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 controllers + +import ( + "context" + "fmt" + "net/netip" + "strings" + + "github.com/go-logr/logr" + "github.com/onmetal/controller-utils/clientutils" + onmetalapinetv1alpha1 "github.com/onmetal/onmetal-api-net/api/v1alpha1" + apinetletv1alpha1 "github.com/onmetal/onmetal-api-net/apinetlet/api/v1alpha1" + commonv1alpha1 "github.com/onmetal/onmetal-api/api/common/v1alpha1" + networkingv1alpha1 "github.com/onmetal/onmetal-api/api/networking/v1alpha1" + "github.com/onmetal/onmetal-api/apiutils/predicates" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/cluster" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +const ( + loadBalancerFinalizer = "apinet.api.onmetal.de/loadbalancer" +) + +type LoadBalancerReconciler struct { + client.Client + APINetClient client.Client + + APINetNamespace string + + WatchFilterValue string +} + +//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=networking.api.onmetal.de,resources=loadbalancers,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=networking.api.onmetal.de,resources=loadbalancers/finalizers,verbs=update;patch +//+kubebuilder:rbac:groups=networking.api.onmetal.de,resources=loadbalancers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=apinet.api.onmetal.de,resources=publicips,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=apinet.api.onmetal.de,resources=publicips/status,verbs=get + +func (r *LoadBalancerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx) + loadBalancer := &networkingv1alpha1.LoadBalancer{} + if err := r.Get(ctx, req.NamespacedName, loadBalancer); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("error getting load balancer %s: %w", req.NamespacedName, err) + } + + return r.deleteGone(ctx, log, req.NamespacedName) + } + + return r.reconcileExists(ctx, log, loadBalancer) +} + +func (r *LoadBalancerReconciler) deleteGone(ctx context.Context, log logr.Logger, virtualIPKey client.ObjectKey) (ctrl.Result, error) { + log.V(1).Info("Delete gone") + + log.V(1).Info("Deleting any matching apinet public ips") + if err := r.APINetClient.DeleteAllOf(ctx, &onmetalapinetv1alpha1.PublicIP{}, + client.InNamespace(r.APINetNamespace), + client.MatchingLabels{ + apinetletv1alpha1.LoadBalancerNamespaceLabel: virtualIPKey.Namespace, + apinetletv1alpha1.LoadBalancerNameLabel: virtualIPKey.Name, + }, + ); err != nil { + return ctrl.Result{}, fmt.Errorf("error deleting apinet public ips: %w", err) + } + + log.V(1).Info("Issued delete for any leftover apinet public ip") + return ctrl.Result{}, nil +} + +func (r *LoadBalancerReconciler) reconcileExists(ctx context.Context, log logr.Logger, loadBalancer *networkingv1alpha1.LoadBalancer) (ctrl.Result, error) { + log = log.WithValues("UID", loadBalancer.UID) + if !loadBalancer.DeletionTimestamp.IsZero() { + return r.delete(ctx, log, loadBalancer) + } + return r.reconcile(ctx, log, loadBalancer) +} + +func (r *LoadBalancerReconciler) delete(ctx context.Context, log logr.Logger, loadBalancer *networkingv1alpha1.LoadBalancer) (ctrl.Result, error) { + log.V(1).Info("Delete") + + if !controllerutil.ContainsFinalizer(loadBalancer, loadBalancerFinalizer) { + log.V(1).Info("No finalizer present, nothing to do") + return ctrl.Result{}, nil + } + + var count int + for _, ipFamily := range loadBalancer.Spec.IPFamilies { + if err := r.APINetClient.Delete(ctx, &onmetalapinetv1alpha1.PublicIP{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: r.APINetNamespace, + Name: fmt.Sprintf("%s-%s", loadBalancer.UID, strings.ToLower(string(ipFamily))), + }, + }); err != nil { + if !apierrors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("error deleting target public ip: %w", err) + } + count++ + + } + } + + if count < len(loadBalancer.Spec.IPFamilies) { + log.V(1).Info("Target public ip is not yet gone, requeueing") + return ctrl.Result{Requeue: true}, nil + } + + log.V(1).Info("Target public ip is gone, removing finalizer") + if err := clientutils.PatchRemoveFinalizer(ctx, r.Client, loadBalancer, loadBalancerFinalizer); err != nil { + return ctrl.Result{}, fmt.Errorf("error removing finalizer: %w", err) + } + log.V(1).Info("Removed finalizer") + return ctrl.Result{}, nil +} + +func (r *LoadBalancerReconciler) reconcile(ctx context.Context, log logr.Logger, loadBalancer *networkingv1alpha1.LoadBalancer) (ctrl.Result, error) { + log.V(1).Info("Reconcile") + + log.V(1).Info("Ensuring finalizer") + modified, err := clientutils.PatchEnsureFinalizer(ctx, r.Client, loadBalancer, loadBalancerFinalizer) + if err != nil { + return ctrl.Result{}, fmt.Errorf("error ensuring finalizer: %w", err) + } + if modified { + log.V(1).Info("Added finalizer, requeueing") + return ctrl.Result{Requeue: true}, nil + } + + ips, err := r.applyPublicIPs(ctx, log, loadBalancer) + if err != nil { + return ctrl.Result{}, fmt.Errorf("error getting / applying public ip: %w", err) + } + + if err := r.patchStatus(ctx, log, loadBalancer, ips); err != nil { + return ctrl.Result{}, fmt.Errorf("error patching load balancer status") + } + + log.V(1).Info("Patched load balancer status") + return ctrl.Result{}, nil +} + +func (r *LoadBalancerReconciler) applyPublicIPs(ctx context.Context, log logr.Logger, loadBalancer *networkingv1alpha1.LoadBalancer) ([]netip.Addr, error) { + var ips []netip.Addr + for _, ipFamily := range loadBalancer.Spec.IPFamilies { + apiNetPublicIP, err := r.applyPublicIP(ctx, log, loadBalancer, ipFamily) + if err != nil { + return nil, err + } + + ips = append(ips, apiNetPublicIP) + } + return ips, nil +} + +func (r *LoadBalancerReconciler) applyPublicIP(ctx context.Context, log logr.Logger, loadBalancer *networkingv1alpha1.LoadBalancer, ipFamily corev1.IPFamily) (netip.Addr, error) { + apiNetPublicIP := &onmetalapinetv1alpha1.PublicIP{ + TypeMeta: metav1.TypeMeta{ + APIVersion: onmetalapinetv1alpha1.GroupVersion.String(), + Kind: "PublicIP", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: r.APINetNamespace, + Name: fmt.Sprintf("%s-%s", loadBalancer.UID, strings.ToLower(string(ipFamily))), + Labels: map[string]string{ + apinetletv1alpha1.LoadBalancerNamespaceLabel: loadBalancer.Namespace, + apinetletv1alpha1.LoadBalancerNameLabel: loadBalancer.Name, + apinetletv1alpha1.LoadBalancerUIDLabel: string(loadBalancer.UID), + }, + }, + Spec: onmetalapinetv1alpha1.PublicIPSpec{ + IPFamily: ipFamily, + }, + } + + log.V(1).Info("Applying apinet public ip", "ipFamily", ipFamily) + if err := r.APINetClient.Patch(ctx, apiNetPublicIP, client.Apply, + client.FieldOwner(apinetletv1alpha1.FieldOwner), + client.ForceOwnership, + ); err != nil { + return netip.Addr{}, fmt.Errorf("error applying apinet public ip: %w", err) + } + log.V(1).Info("Applied apinet public ip") + + if !apiNetPublicIP.IsAllocated() { + return netip.Addr{}, nil + } + ip := apiNetPublicIP.Spec.IP + return ip.Addr, nil +} + +func (r *LoadBalancerReconciler) patchStatus(ctx context.Context, log logr.Logger, loadBalancer *networkingv1alpha1.LoadBalancer, ips []netip.Addr) error { + base := loadBalancer.DeepCopy() + loadBalancer.Status.IPs = []commonv1alpha1.IP{} + + for _, ip := range ips { + if !ip.IsValid() { + log.V(2).Info("Public ip is not yet allocated", "ip", ip.String()) + continue + } + + log.V(2).Info("Public ip is allocated", "ip", ip.String()) + loadBalancer.Status.IPs = append(loadBalancer.Status.IPs, commonv1alpha1.IP{ + Addr: ip, + }) + } + + return r.Status().Patch(ctx, loadBalancer, client.MergeFrom(base)) +} + +func (r *LoadBalancerReconciler) SetupWithManager(mgr ctrl.Manager, apiNetCluster cluster.Cluster) error { + log := ctrl.Log.WithName("loadbalancer").WithName("setup") + + return ctrl.NewControllerManagedBy(mgr). + For( + &networkingv1alpha1.LoadBalancer{}, + builder.WithPredicates( + predicates.ResourceHasFilterLabel(log, r.WatchFilterValue), + predicates.ResourceIsNotExternallyManaged(log), + ), + ). + Watches( + source.NewKindWithCache(&onmetalapinetv1alpha1.PublicIP{}, apiNetCluster.GetCache()), + handler.EnqueueRequestsFromMapFunc(func(obj client.Object) []ctrl.Request { + apiNetPublicIP := obj.(*onmetalapinetv1alpha1.PublicIP) + + if apiNetPublicIP.Namespace != r.APINetNamespace { + return nil + } + + namespace, ok := apiNetPublicIP.Labels[apinetletv1alpha1.LoadBalancerNamespaceLabel] + if !ok { + return nil + } + + name, ok := apiNetPublicIP.Labels[apinetletv1alpha1.LoadBalancerNameLabel] + if !ok { + return nil + } + + return []ctrl.Request{{NamespacedName: client.ObjectKey{Namespace: namespace, Name: name}}} + }), + builder.WithPredicates( + getApiNetPublicIPAllocationChangedPredicate(), + ), + ). + Complete(r) +} diff --git a/apinetlet/controllers/loadbalancer_controller_test.go b/apinetlet/controllers/loadbalancer_controller_test.go new file mode 100644 index 00000000..7eec6600 --- /dev/null +++ b/apinetlet/controllers/loadbalancer_controller_test.go @@ -0,0 +1,176 @@ +// Copyright 2022 OnMetal authors +// +// 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 controllers + +import ( + "fmt" + "strings" + + onmetalapinetv1alpha1 "github.com/onmetal/onmetal-api-net/api/v1alpha1" + apinetletv1alpha1 "github.com/onmetal/onmetal-api-net/apinetlet/api/v1alpha1" + commonv1alpha1 "github.com/onmetal/onmetal-api/api/common/v1alpha1" + networkingv1alpha1 "github.com/onmetal/onmetal-api/api/networking/v1alpha1" + "github.com/onmetal/onmetal-api/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" +) + +var _ = Describe("LoadBalancerController", func() { + ctx := testutils.SetupContext() + ns := SetupTest(ctx) + + It("should allocate a public ip", func() { + By("creating a network") + network := &networkingv1alpha1.Network{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "network-", + }, + } + Expect(k8sClient.Create(ctx, network)).To(Succeed()) + + ipFamily := corev1.IPv4Protocol + + By("creating a load balancer") + loadBalancer := &networkingv1alpha1.LoadBalancer{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "load-balancer-", + }, + Spec: networkingv1alpha1.LoadBalancerSpec{ + Type: networkingv1alpha1.LoadBalancerTypePublic, + IPFamilies: []corev1.IPFamily{ + ipFamily, + }, + NetworkRef: corev1.LocalObjectReference{Name: network.Name}, + }, + } + Expect(k8sClient.Create(ctx, loadBalancer)).To(Succeed()) + + By("waiting for the corresponding public ip to be created") + publicIP := &onmetalapinetv1alpha1.PublicIP{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: fmt.Sprintf("%s-%s", loadBalancer.UID, strings.ToLower(string(ipFamily))), + }, + } + Eventually(Get(publicIP)).Should(Succeed()) + + By("inspecting the created public ip") + Expect(publicIP.Labels).To(Equal(map[string]string{ + apinetletv1alpha1.LoadBalancerNamespaceLabel: loadBalancer.Namespace, + apinetletv1alpha1.LoadBalancerNameLabel: loadBalancer.Name, + apinetletv1alpha1.LoadBalancerUIDLabel: string(loadBalancer.UID), + })) + Expect(publicIP.Spec).To(Equal(onmetalapinetv1alpha1.PublicIPSpec{ + IPFamily: ipFamily, + })) + + By("asserting the load balancer does not get an ip address") + Consistently(Object(loadBalancer)).Should(HaveField("Status.IPs", BeNil())) + + By("patching the public ip spec ips") + basePublicIP := publicIP.DeepCopy() + publicIP.Spec.IP = onmetalapinetv1alpha1.MustParseNewIP("10.0.0.1") + Expect(k8sClient.Patch(ctx, publicIP, client.MergeFrom(basePublicIP))).To(Succeed()) + + By("patching the public ip status to allocated") + basePublicIP = publicIP.DeepCopy() + onmetalapinetv1alpha1.SetPublicIPCondition(&publicIP.Status.Conditions, onmetalapinetv1alpha1.PublicIPCondition{ + Type: onmetalapinetv1alpha1.PublicIPAllocated, + Status: corev1.ConditionTrue, + }) + Expect(k8sClient.Status().Patch(ctx, publicIP, client.MergeFrom(basePublicIP))).To(Succeed()) + + By("checking that load balancer contains ip") + Eventually(Object(loadBalancer)).Should(HaveField("Status.IPs", + ContainElement(*commonv1alpha1.MustParseNewIP("10.0.0.1")), + )) + + ipFamily2 := corev1.IPv6Protocol + + By("requesting further ip by adding another protocol") + loadBalancerBase := loadBalancer.DeepCopy() + loadBalancer.Spec.IPFamilies = append(loadBalancer.Spec.IPFamilies, ipFamily2) + Expect(k8sClient.Patch(ctx, loadBalancer, client.MergeFrom(loadBalancerBase))).To(Succeed()) + + By("waiting for the corresponding public ip to be created") + publicIP2 := &onmetalapinetv1alpha1.PublicIP{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: fmt.Sprintf("%s-%s", loadBalancer.UID, strings.ToLower(string(ipFamily2))), + }, + } + Eventually(Get(publicIP2)).Should(Succeed()) + + By("patching the second public ip spec ips") + basePublicIP2 := publicIP2.DeepCopy() + publicIP2.Spec.IP = onmetalapinetv1alpha1.MustParseNewIP("::ffff:a00:2") + Expect(k8sClient.Patch(ctx, publicIP2, client.MergeFrom(basePublicIP2))).To(Succeed()) + + By("patching the second public ip status to allocated") + basePublicIP2 = publicIP2.DeepCopy() + onmetalapinetv1alpha1.SetPublicIPCondition(&publicIP2.Status.Conditions, onmetalapinetv1alpha1.PublicIPCondition{ + Type: onmetalapinetv1alpha1.PublicIPAllocated, + Status: corev1.ConditionTrue, + }) + Expect(k8sClient.Status().Patch(ctx, publicIP2, client.MergeFrom(basePublicIP2))).To(Succeed()) + + By("checking that load balancer contains ips") + Eventually(Object(loadBalancer)).Should(HaveField("Status.IPs", + ContainElements( + *commonv1alpha1.MustParseNewIP("10.0.0.1"), + *commonv1alpha1.MustParseNewIP("::ffff:a00:2"), + ), + )) + + By("deleting the load balancer") + Expect(k8sClient.Delete(ctx, loadBalancer)).To(Succeed()) + + By("waiting for it to be gone") + Eventually(Get(loadBalancer)).Should(Satisfy(apierrors.IsNotFound)) + + By("asserting the corresponding public ips are gone as well") + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(publicIP), publicIP)).To(Satisfy(apierrors.IsNotFound)) + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(publicIP2), publicIP2)).To(Satisfy(apierrors.IsNotFound)) + }) + + It("should clean up dangling public ips", func() { + By("creating a public ip") + publicIP := &onmetalapinetv1alpha1.PublicIP{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "public-ip-", + Labels: map[string]string{ + apinetletv1alpha1.LoadBalancerNamespaceLabel: ns.Name, + apinetletv1alpha1.LoadBalancerNameLabel: "some-name", + apinetletv1alpha1.LoadBalancerUIDLabel: "some-uid", + }, + }, + Spec: onmetalapinetv1alpha1.PublicIPSpec{ + IPFamily: corev1.IPv4Protocol, + }, + } + Expect(k8sClient.Create(ctx, publicIP)).To(Succeed()) + + By("waiting for the public ip to be gone") + Eventually(Get(publicIP)).Should(Satisfy(apierrors.IsNotFound)) + }) +}) diff --git a/apinetlet/controllers/natgateway_controller.go b/apinetlet/controllers/natgateway_controller.go index 974afcc5..c1f14e31 100644 --- a/apinetlet/controllers/natgateway_controller.go +++ b/apinetlet/controllers/natgateway_controller.go @@ -43,7 +43,7 @@ const ( natGatewayFinalizer = "apinet.api.onmetal.de/natgateway" ) -type NatGatewayReconciler struct { +type NATGatewayReconciler struct { client.Client APINetClient client.Client @@ -59,7 +59,7 @@ type NatGatewayReconciler struct { //+kubebuilder:rbac:groups=apinet.api.onmetal.de,resources=publicips,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=apinet.api.onmetal.de,resources=publicips/status,verbs=get -func (r *NatGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *NATGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := ctrl.LoggerFrom(ctx) natGateway := &networkingv1alpha1.NATGateway{} if err := r.Get(ctx, req.NamespacedName, natGateway); err != nil { @@ -73,7 +73,7 @@ func (r *NatGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) return r.reconcileExists(ctx, log, natGateway) } -func (r *NatGatewayReconciler) deleteGone(ctx context.Context, log logr.Logger, natGatewayKey client.ObjectKey) (ctrl.Result, error) { +func (r *NATGatewayReconciler) deleteGone(ctx context.Context, log logr.Logger, natGatewayKey client.ObjectKey) (ctrl.Result, error) { log.V(1).Info("Delete gone") log.V(1).Info("Deleting any matching apinet public ips") @@ -91,7 +91,7 @@ func (r *NatGatewayReconciler) deleteGone(ctx context.Context, log logr.Logger, return ctrl.Result{}, nil } -func (r *NatGatewayReconciler) reconcileExists(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway) (ctrl.Result, error) { +func (r *NATGatewayReconciler) reconcileExists(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway) (ctrl.Result, error) { log = log.WithValues("UID", natGateway.UID) if !natGateway.DeletionTimestamp.IsZero() { return r.delete(ctx, log, natGateway) @@ -99,7 +99,7 @@ func (r *NatGatewayReconciler) reconcileExists(ctx context.Context, log logr.Log return r.reconcile(ctx, log, natGateway) } -func (r *NatGatewayReconciler) delete(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway) (ctrl.Result, error) { +func (r *NATGatewayReconciler) delete(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway) (ctrl.Result, error) { log.V(1).Info("Delete") if !controllerutil.ContainsFinalizer(natGateway, natGatewayFinalizer) { @@ -139,7 +139,7 @@ func (r *NatGatewayReconciler) delete(ctx context.Context, log logr.Logger, natG return ctrl.Result{}, nil } -func (r *NatGatewayReconciler) reconcile(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway) (ctrl.Result, error) { +func (r *NATGatewayReconciler) reconcile(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway) (ctrl.Result, error) { log.V(1).Info("Reconcile") log.V(1).Info("Ensuring finalizer") @@ -165,7 +165,7 @@ func (r *NatGatewayReconciler) reconcile(ctx context.Context, log logr.Logger, n return ctrl.Result{}, nil } -func (r *NatGatewayReconciler) applyPublicIP(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway, ipName string, ipFamily corev1.IPFamily) (netip.Addr, error) { +func (r *NATGatewayReconciler) applyPublicIP(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway, ipName string, ipFamily corev1.IPFamily) (netip.Addr, error) { apiNetPublicIP := &onmetalapinetv1alpha1.PublicIP{ TypeMeta: metav1.TypeMeta{ APIVersion: onmetalapinetv1alpha1.GroupVersion.String(), @@ -201,7 +201,7 @@ func (r *NatGatewayReconciler) applyPublicIP(ctx context.Context, log logr.Logge return ip.Addr, nil } -func (r *NatGatewayReconciler) applyPublicIPs(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway) (map[string]netip.Addr, error) { +func (r *NATGatewayReconciler) applyPublicIPs(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway) (map[string]netip.Addr, error) { ips := map[string]netip.Addr{} for _, ipFamily := range natGateway.Spec.IPFamilies { for _, ip := range natGateway.Spec.IPs { @@ -215,7 +215,7 @@ func (r *NatGatewayReconciler) applyPublicIPs(ctx context.Context, log logr.Logg return ips, nil } -func (r *NatGatewayReconciler) patchStatus(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway, ips map[string]netip.Addr) error { +func (r *NATGatewayReconciler) patchStatus(ctx context.Context, log logr.Logger, natGateway *networkingv1alpha1.NATGateway, ips map[string]netip.Addr) error { base := natGateway.DeepCopy() natGateway.Status.IPs = []networkingv1alpha1.NATGatewayIPStatus{} @@ -237,7 +237,7 @@ func (r *NatGatewayReconciler) patchStatus(ctx context.Context, log logr.Logger, return r.Status().Patch(ctx, natGateway, client.MergeFrom(base)) } -func (r *NatGatewayReconciler) SetupWithManager(mgr ctrl.Manager, apiNetCluster cluster.Cluster) error { +func (r *NATGatewayReconciler) SetupWithManager(mgr ctrl.Manager, apiNetCluster cluster.Cluster) error { log := ctrl.Log.WithName("natgateway").WithName("setup") return ctrl.NewControllerManagedBy(mgr). diff --git a/apinetlet/controllers/natgateway_controller_test.go b/apinetlet/controllers/natgateway_controller_test.go index 8b0db8b7..ce5bc2c0 100644 --- a/apinetlet/controllers/natgateway_controller_test.go +++ b/apinetlet/controllers/natgateway_controller_test.go @@ -53,7 +53,7 @@ var _ = Describe("NATGatewayController", func() { natGateway := &networkingv1alpha1.NATGateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns.Name, - GenerateName: "nat-gateway-ip-", + GenerateName: "nat-gateway-", }, Spec: networkingv1alpha1.NATGatewaySpec{ Type: networkingv1alpha1.NATGatewayTypePublic, diff --git a/apinetlet/main.go b/apinetlet/main.go index 47b7b4b4..04538310 100644 --- a/apinetlet/main.go +++ b/apinetlet/main.go @@ -161,6 +161,26 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Network") os.Exit(1) } + + if err = (&controllers.NATGatewayReconciler{ + Client: mgr.GetClient(), + APINetClient: apiNetCluster.GetClient(), + APINetNamespace: apiNetNamespace, + WatchFilterValue: watchFilterValue, + }).SetupWithManager(mgr, apiNetCluster); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "NATGateway") + os.Exit(1) + } + + if err = (&controllers.LoadBalancerReconciler{ + Client: mgr.GetClient(), + APINetClient: apiNetCluster.GetClient(), + APINetNamespace: apiNetNamespace, + WatchFilterValue: watchFilterValue, + }).SetupWithManager(mgr, apiNetCluster); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "LoadBalancer") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/apinetlet/rbac/role.yaml b/config/apinetlet/rbac/role.yaml index 654fbb6c..4a10c5b2 100644 --- a/config/apinetlet/rbac/role.yaml +++ b/config/apinetlet/rbac/role.yaml @@ -30,6 +30,31 @@ rules: - publicips/status verbs: - get +- apiGroups: + - networking.api.onmetal.de + resources: + - loadbalancers + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - networking.api.onmetal.de + resources: + - loadbalancers/finalizers + verbs: + - patch + - update +- apiGroups: + - networking.api.onmetal.de + resources: + - loadbalancers/status + verbs: + - get + - patch + - update - apiGroups: - networking.api.onmetal.de resources: