mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-08-23 22:26:49 +00:00
Skip garbage collection for ManifestWorks that have the ManifestWorkReplicaSet controller label, as these should be managed exclusively by the ManifestWorkReplicaSet controller. Changes: - Fix logic bug in controller to properly check for ReplicaSet label - Add unit tests for label-based GC skip behavior - Add integration test to verify GC skip for ReplicaSet-managed works 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Jian Qiu <jqiu@redhat.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
119 lines
4.1 KiB
Go
119 lines
4.1 KiB
Go
package manifestworkgarbagecollection
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/klog/v2"
|
|
|
|
workclientset "open-cluster-management.io/api/client/work/clientset/versioned"
|
|
workinformers "open-cluster-management.io/api/client/work/informers/externalversions/work/v1"
|
|
worklisters "open-cluster-management.io/api/client/work/listers/work/v1"
|
|
workapiv1 "open-cluster-management.io/api/work/v1"
|
|
workapiv1alpha1 "open-cluster-management.io/api/work/v1alpha1"
|
|
"open-cluster-management.io/sdk-go/pkg/basecontroller/factory"
|
|
|
|
"open-cluster-management.io/ocm/pkg/common/queue"
|
|
)
|
|
|
|
// ManifestWorkGarbageCollectionController is to delete the manifestworks when it has the completed condition.
|
|
type ManifestWorkGarbageCollectionController struct {
|
|
workClient workclientset.Interface
|
|
workLister worklisters.ManifestWorkLister
|
|
}
|
|
|
|
// NewManifestWorkGarbageCollectionController creates a new ManifestWorkGarbageCollectionController
|
|
func NewManifestWorkGarbageCollectionController(
|
|
workClient workclientset.Interface,
|
|
manifestWorkInformer workinformers.ManifestWorkInformer,
|
|
) factory.Controller {
|
|
controller := &ManifestWorkGarbageCollectionController{
|
|
workClient: workClient,
|
|
workLister: manifestWorkInformer.Lister(),
|
|
}
|
|
|
|
return factory.New().
|
|
WithInformersQueueKeysFunc(
|
|
queue.QueueKeyByMetaNamespaceName,
|
|
manifestWorkInformer.Informer(),
|
|
).
|
|
WithSync(controller.sync).
|
|
ToController("ManifestWorkGarbageCollectionController")
|
|
}
|
|
|
|
// sync is the main reconcile loop for completed ManifestWork TTL
|
|
func (c *ManifestWorkGarbageCollectionController) sync(ctx context.Context, controllerContext factory.SyncContext, key string) error {
|
|
logger := klog.FromContext(ctx)
|
|
logger.V(4).Info("Reconciling ManifestWork for TTL processing", "key", key)
|
|
|
|
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
|
if err != nil {
|
|
utilruntime.HandleError(err)
|
|
return nil
|
|
}
|
|
|
|
manifestWork, err := c.workLister.ManifestWorks(namespace).Get(name)
|
|
switch {
|
|
case apierrors.IsNotFound(err):
|
|
return nil
|
|
case err != nil:
|
|
return err
|
|
}
|
|
|
|
if manifestWork.DeletionTimestamp != nil {
|
|
return nil
|
|
}
|
|
|
|
// skip if manifestwork is generated by manifesworkreplicaset, which should not be deleted by GC since
|
|
// manifestworkreplicaset controller is responsible for managing it.
|
|
if len(manifestWork.Labels) > 0 {
|
|
if _, ok := manifestWork.Labels[workapiv1alpha1.ManifestWorkReplicaSetControllerNameLabelKey]; ok {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Check if ManifestWork has TTLSecondsAfterFinished configured
|
|
if manifestWork.Spec.DeleteOption == nil || manifestWork.Spec.DeleteOption.TTLSecondsAfterFinished == nil {
|
|
return nil
|
|
}
|
|
|
|
// Find the Complete condition
|
|
completedCondition := meta.FindStatusCondition(manifestWork.Status.Conditions, workapiv1.WorkComplete)
|
|
if completedCondition == nil || completedCondition.Status != metav1.ConditionTrue {
|
|
return nil
|
|
}
|
|
|
|
ttlSeconds := *manifestWork.Spec.DeleteOption.TTLSecondsAfterFinished
|
|
if ttlSeconds > 0 {
|
|
// Calculate time elapsed since completion
|
|
// Compute deadline precisely using durations and handle clock skew.
|
|
completedTime := completedCondition.LastTransitionTime.Time
|
|
ttl := time.Duration(ttlSeconds) * time.Second
|
|
deadline := completedTime.Add(ttl)
|
|
now := time.Now()
|
|
if now.Before(deadline) {
|
|
requeueAfter := time.Until(deadline)
|
|
logger.V(4).Info("ManifestWork completed; will be deleted after remaining TTL",
|
|
"namespace", namespace, "name", name, "remaining", requeueAfter)
|
|
controllerContext.Queue().AddAfter(key, requeueAfter)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Time to delete the ManifestWork
|
|
logger.Info("Deleting completed ManifestWork after TTL expiry",
|
|
"namespace", namespace, "name", name, "ttlSeconds", ttlSeconds)
|
|
err = c.workClient.WorkV1().ManifestWorks(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
return fmt.Errorf("failed to delete completed ManifestWork %s/%s: %w", namespace, name, err)
|
|
}
|
|
|
|
return nil
|
|
}
|