diff --git a/apis/v1/binding_types.go b/apis/v1/binding_types.go index 340d36596..596d8c00f 100644 --- a/apis/v1/binding_types.go +++ b/apis/v1/binding_types.go @@ -38,6 +38,10 @@ type ResourceBindingSpec struct { // it points to the name of the leading snapshot of the index group. ResourceSnapshotName string `json:"resourceSnapshotName"` + // PolicySnapshotName is the name of the policy snapshot that this resource binding & resource snapshot points to. + // It is required to decide whether the binding needs to be deleted or not without talking to API server. + PolicySnapshotName string `json:"policySnapshotName"` + // TargetCluster is the name of the cluster that the scheduler assigns the resources to. TargetCluster string `json:"targetCluster"` } diff --git a/apis/v1/commons.go b/apis/v1/commons.go index 5a4335291..7522be1cb 100644 --- a/apis/v1/commons.go +++ b/apis/v1/commons.go @@ -14,7 +14,7 @@ type ClusterState string const ( // Unprefixed labels/annotations are reserved for end-users - // we will add a fleet.azure.com to designate these labels/annotations as official fleet labels/annotations. + // we will add a fleet.azure.com to designate these labels/annotations/finalizers as official fleet labels/annotations/finalizers. // See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#label-selector-and-annotation-conventions fleetPrefix = "fleet.azure.com/" diff --git a/apis/v1/resourceSnapshot_types.go b/apis/v1/resourceSnapshot_types.go index e5c442265..0e1b6109a 100644 --- a/apis/v1/resourceSnapshot_types.go +++ b/apis/v1/resourceSnapshot_types.go @@ -21,6 +21,10 @@ const ( // NumberOfResourceSnapshotsAnnotation is the annotation that contains the total number of resource snapshots. NumberOfResourceSnapshotsAnnotation = fleetPrefix + "numberOfResourceSnapshots" + + // ClusterResourceSnapshotFinalizer will be added by the clusterResourceRollout controller to clean up the clusterResourceBindings + // before the resource snapshot is deleted. + ClusterResourceSnapshotFinalizer = fleetPrefix + "clusterresourcebinding-cleanup" ) // +genclient @@ -67,8 +71,9 @@ type ResourceSnapShotSpec struct { // +required SelectedResources []ResourceContent `json:"selectedResources"` - // PolicySnapShotName is the name of the policy snapshot that this resource snapshot is pointing to. - PolicySnapShotName string `json:"policySnapShotName"` + // PolicySnapshotName is the name of the policy snapshot that this resource snapshot is pointing to. + // +required + PolicySnapshotName string `json:"policySnapshotName"` } // ResourceContent contains the content of a resource diff --git a/pkg/controllers/clusterresourcerollout/clusterresourcerollout_controller.go b/pkg/controllers/clusterresourcerollout/clusterresourcerollout_controller.go new file mode 100644 index 000000000..02060b1ea --- /dev/null +++ b/pkg/controllers/clusterresourcerollout/clusterresourcerollout_controller.go @@ -0,0 +1,79 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +package clusterresourcerollout + +import ( + "context" + "time" + + apierrors "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/controller/controllerutil" + + fleetv1 "go.goms.io/fleet/apis/v1" +) + +// Reconciler reconciles the active ClusterResourceSnapshot object. +type Reconciler struct { + client.Client + recorder record.EventRecorder +} + +// Reconcile rollouts the resources by updating/deleting the clusterResourceBindings. +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + name := req.NamespacedName + crs := fleetv1.ClusterResourceSnapshot{} + crsKRef := klog.KRef(name.Namespace, name.Name) + + startTime := time.Now() + klog.V(2).InfoS("Reconciliation starts", "clusterResourceSnapshot", crsKRef) + defer func() { + latency := time.Since(startTime).Milliseconds() + klog.V(2).InfoS("Reconciliation ends", "clusterResourceSnapshot", crsKRef, "latency", latency) + }() + + if err := r.Client.Get(ctx, name, &crs); err != nil { + if apierrors.IsNotFound(err) { + klog.V(4).InfoS("Ignoring NotFound clusterResourceSnapshot", "clusterResourceSnapshot", crsKRef) + return ctrl.Result{}, nil + } + klog.ErrorS(err, "Failed to get clusterResourceSnapshot", "clusterResourceSnapshot", crsKRef) + return ctrl.Result{}, err + } + + if crs.ObjectMeta.DeletionTimestamp != nil { + return r.handleDelete(ctx, &crs) + } + + // register finalizer + if !controllerutil.ContainsFinalizer(&crs, fleetv1.ClusterResourceSnapshotFinalizer) { + controllerutil.AddFinalizer(&crs, fleetv1.ClusterResourceSnapshotFinalizer) + if err := r.Update(ctx, &crs); err != nil { + klog.ErrorS(err, "Failed to add mcs finalizer", "clusterResourceSnapshot", crsKRef) + return ctrl.Result{}, err + } + } + return r.handleUpdate(ctx, &crs) +} + +func (r *Reconciler) handleDelete(_ context.Context, _ *fleetv1.ClusterResourceSnapshot) (ctrl.Result, error) { + return ctrl.Result{}, nil +} + +func (r *Reconciler) handleUpdate(_ context.Context, _ *fleetv1.ClusterResourceSnapshot) (ctrl.Result, error) { + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + r.recorder = mgr.GetEventRecorderFor("clusterResourceRollout") + return ctrl.NewControllerManagedBy(mgr). + For(&fleetv1.ClusterResourceSnapshot{}). + Complete(r) +}