pkg/controllers/hub/membercluster/controller.go (96 lines of code) (raw):
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/
// Package membercluster features the MemberCluster controller for watching
// update/delete events to the MemberCluster object and removes finalizers
// on all fleet networking resources in the fleet member cluster namespace.
package membercluster
import (
"context"
"fmt"
"time"
"golang.org/x/sync/errgroup"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/tools/record"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
clusterv1beta1 "go.goms.io/fleet/apis/cluster/v1beta1"
fleetnetv1alpha1 "go.goms.io/fleet-networking/api/v1alpha1"
"go.goms.io/fleet-networking/pkg/common/hubconfig"
)
const (
ControllerName = "membercluster-controller"
)
// Reconciler reconciles a MemberCluster object.
type Reconciler struct {
client.Client
Recorder record.EventRecorder
// the wait time in minutes before we need to force delete a member cluster.
ForceDeleteWaitTime time.Duration
}
// Reconcile watches the deletion of the member cluster and removes finalizers on fleet networking resources in the
// member cluster namespace.
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
mcObjRef := klog.KRef(req.Namespace, req.Name)
startTime := time.Now()
klog.V(2).InfoS("Reconciliation starts", "memberCluster", mcObjRef)
defer func() {
latency := time.Since(startTime).Milliseconds()
klog.V(2).InfoS("Reconciliation ends", "memberCluster", mcObjRef, "latency", latency)
}()
var mc clusterv1beta1.MemberCluster
if err := r.Client.Get(ctx, req.NamespacedName, &mc); err != nil {
if errors.IsNotFound(err) {
klog.V(4).InfoS("Ignoring NotFound memberCluster", "memberCluster", mcObjRef)
return ctrl.Result{}, nil
}
klog.ErrorS(err, "Failed to get memberCluster", "memberCluster", mcObjRef)
return ctrl.Result{}, err
}
if mc.DeletionTimestamp.IsZero() {
klog.V(3).InfoS("The member cluster is not being deleted, ignore it", "memberCluster", mcObjRef)
return ctrl.Result{}, nil // no need to retry.
}
// Handle deleting member cluster, removes finalizers on all the resources in the cluster namespace
// after member cluster force delete wait time.
if !mc.DeletionTimestamp.IsZero() && time.Since(mc.DeletionTimestamp.Time) >= r.ForceDeleteWaitTime {
klog.V(2).InfoS("The member cluster deletion is stuck removing the "+
"finalizers from all the resources in member cluster namespace", "memberCluster", mcObjRef)
return r.removeFinalizer(ctx, mc)
}
// we need to only wait for force delete wait time, if the update/delete member cluster event takes
// longer to be reconciled we need to account for that time.
return ctrl.Result{RequeueAfter: r.ForceDeleteWaitTime - time.Since(mc.DeletionTimestamp.Time)}, nil
}
// removeFinalizer removes finalizers on the resources in the member cluster namespace.
// For EndpointSliceExport, InternalServiceImport & InternalServiceExport resources, the finalizers should be
// removed by other hub networking controllers when leaving. So this MemberCluster controller only handles
// EndpointSliceImports here.
func (r *Reconciler) removeFinalizer(ctx context.Context, mc clusterv1beta1.MemberCluster) (ctrl.Result, error) {
// Remove finalizer for EndpointSliceImport resources in the cluster namespace.
mcObjRef := klog.KRef(mc.Namespace, mc.Name)
mcNamespace := fmt.Sprintf(hubconfig.HubNamespaceNameFormat, mc.Name)
var endpointSliceImportList fleetnetv1alpha1.EndpointSliceImportList
if err := r.Client.List(ctx, &endpointSliceImportList, client.InNamespace(mcNamespace)); err != nil {
klog.ErrorS(err, "Failed to list endpointSliceImports", "memberCluster", mcObjRef)
return ctrl.Result{}, err
}
errs, ctx := errgroup.WithContext(ctx)
for i := range endpointSliceImportList.Items {
esi := &endpointSliceImportList.Items[i]
errs.Go(func() error {
esiObjRef := klog.KRef(esi.Namespace, esi.Name)
esi.SetFinalizers(nil)
if err := r.Client.Update(ctx, esi); err != nil {
klog.ErrorS(err, "Failed to remove finalizers for endpointSliceImport",
"memberCluster", mcObjRef, "endpointSliceImport", esiObjRef)
return err
}
klog.V(2).InfoS("Removed finalizers for endpointSliceImport",
"memberCluster", mcObjRef, "endpointSliceImport", esiObjRef)
return nil
})
}
return ctrl.Result{}, errs.Wait()
}
// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
customPredicate := predicate.Funcs{
CreateFunc: func(_ event.CreateEvent) bool {
// Ignore creation events.
return false
},
DeleteFunc: func(_ event.DeleteEvent) bool {
// trigger reconcile on delete event just in case update event is missed.
return true
},
UpdateFunc: func(e event.UpdateEvent) bool {
// If new object is being deleted, trigger reconcile.
return !e.ObjectNew.GetDeletionTimestamp().IsZero()
},
}
// Watch for changes to primary resource MemberCluster
return ctrl.NewControllerManagedBy(mgr).
For(&clusterv1beta1.MemberCluster{}).
WithEventFilter(customPredicate).
Complete(r)
}