open-cluster-management.io/governance-policy-propagator@v0.13.0/controllers/propagator/replicatedpolicy_setup.go (about)

     1  package propagator
     2  
     3  import (
     4  	"sync"
     5  
     6  	"k8s.io/apimachinery/pkg/api/equality"
     7  	clusterv1beta1 "open-cluster-management.io/api/cluster/v1beta1"
     8  	appsv1 "open-cluster-management.io/multicloud-operators-subscription/pkg/apis/apps/placementrule/v1"
     9  	ctrl "sigs.k8s.io/controller-runtime"
    10  	"sigs.k8s.io/controller-runtime/pkg/builder"
    11  	"sigs.k8s.io/controller-runtime/pkg/controller"
    12  	"sigs.k8s.io/controller-runtime/pkg/event"
    13  	"sigs.k8s.io/controller-runtime/pkg/handler"
    14  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    15  	"sigs.k8s.io/controller-runtime/pkg/source"
    16  
    17  	policiesv1 "open-cluster-management.io/governance-policy-propagator/api/v1"
    18  	"open-cluster-management.io/governance-policy-propagator/controllers/common"
    19  )
    20  
    21  func (r *ReplicatedPolicyReconciler) SetupWithManager(
    22  	mgr ctrl.Manager,
    23  	maxConcurrentReconciles uint,
    24  	dependenciesSource source.Source,
    25  	updateSrc source.Source,
    26  	templateSrc source.Source,
    27  ) error {
    28  	return ctrl.NewControllerManagedBy(mgr).
    29  		WithOptions(controller.Options{MaxConcurrentReconciles: int(maxConcurrentReconciles)}).
    30  		Named("replicated-policy").
    31  		For(
    32  			&policiesv1.Policy{},
    33  			builder.WithPredicates(replicatedPolicyPredicates(r.ResourceVersions))).
    34  		WatchesRawSource(dependenciesSource, &handler.EnqueueRequestForObject{}).
    35  		WatchesRawSource(updateSrc, &handler.EnqueueRequestForObject{}).
    36  		WatchesRawSource(templateSrc, &handler.EnqueueRequestForObject{}).
    37  		Watches(
    38  			&clusterv1beta1.PlacementDecision{},
    39  			HandlerForDecision(mgr.GetClient()),
    40  		).
    41  		Watches(
    42  			&policiesv1.PlacementBinding{},
    43  			HandlerForBinding(mgr.GetClient()),
    44  		).
    45  		Watches(
    46  			&appsv1.PlacementRule{},
    47  			HandlerForRule(mgr.GetClient()),
    48  		).
    49  		Complete(r)
    50  }
    51  
    52  // replicatedPolicyPredicates triggers reconciliation if the policy is a replicated policy, and is
    53  // not a pure status update. It will use the ResourceVersions cache to try and skip events caused
    54  // by the replicated policy reconciler itself.
    55  func replicatedPolicyPredicates(resourceVersions *sync.Map) predicate.Funcs {
    56  	return predicate.Funcs{
    57  		CreateFunc: func(e event.CreateEvent) bool {
    58  			_, isReplicated := e.Object.GetLabels()[common.RootPolicyLabel]
    59  			if !isReplicated {
    60  				return false
    61  			}
    62  
    63  			key := e.Object.GetNamespace() + "/" + e.Object.GetName()
    64  			version, loaded := safeReadLoad(resourceVersions, key)
    65  			defer version.RUnlock()
    66  
    67  			return !loaded || version.resourceVersion != e.Object.GetResourceVersion()
    68  		},
    69  		DeleteFunc: func(e event.DeleteEvent) bool {
    70  			_, isReplicated := e.Object.GetLabels()[common.RootPolicyLabel]
    71  			if !isReplicated {
    72  				return false
    73  			}
    74  
    75  			key := e.Object.GetNamespace() + "/" + e.Object.GetName()
    76  			version, loaded := safeReadLoad(resourceVersions, key)
    77  			defer version.RUnlock()
    78  
    79  			return !loaded || version.resourceVersion != "deleted"
    80  		},
    81  		UpdateFunc: func(e event.UpdateEvent) bool {
    82  			_, newIsReplicated := e.ObjectNew.GetLabels()[common.RootPolicyLabel]
    83  			_, oldIsReplicated := e.ObjectOld.GetLabels()[common.RootPolicyLabel]
    84  
    85  			// if neither has the label, it is not a replicated policy
    86  			if !(oldIsReplicated || newIsReplicated) {
    87  				return false
    88  			}
    89  
    90  			key := e.ObjectNew.GetNamespace() + "/" + e.ObjectNew.GetName()
    91  			version, loaded := safeReadLoad(resourceVersions, key)
    92  			defer version.RUnlock()
    93  
    94  			if loaded && version.resourceVersion == e.ObjectNew.GetResourceVersion() {
    95  				return false
    96  			}
    97  
    98  			// Ignore pure status updates since those are handled by a separate controller
    99  			return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() ||
   100  				!equality.Semantic.DeepEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels()) ||
   101  				!equality.Semantic.DeepEqual(e.ObjectOld.GetAnnotations(), e.ObjectNew.GetAnnotations())
   102  		},
   103  	}
   104  }
   105  
   106  type lockingRsrcVersion struct {
   107  	*sync.RWMutex
   108  	resourceVersion string
   109  }
   110  
   111  // safeReadLoad gets the lockingRsrcVersion from the sync.Map if it already exists, or it puts a
   112  // new empty lockingRsrcVersion in the sync.Map if it was missing. In either case, this function
   113  // obtains the RLock before returning - the caller *must* call RUnlock() themselves. The bool
   114  // returned indicates if the key already existed in the sync.Map.
   115  func safeReadLoad(resourceVersions *sync.Map, key string) (*lockingRsrcVersion, bool) {
   116  	newRsrc := &lockingRsrcVersion{
   117  		RWMutex:         &sync.RWMutex{},
   118  		resourceVersion: "",
   119  	}
   120  
   121  	newRsrc.RLock()
   122  
   123  	rsrc, loaded := resourceVersions.LoadOrStore(key, newRsrc)
   124  	if loaded {
   125  		newRsrc.RUnlock()
   126  
   127  		version := rsrc.(*lockingRsrcVersion)
   128  		version.RLock()
   129  
   130  		return version, true
   131  	}
   132  
   133  	return newRsrc, false
   134  }
   135  
   136  // safeWriteLoad gets the lockingRsrcVersion from the sync.Map if it already exists, or it puts a
   137  // new empty lockingRsrcVersion in the sync.Map if it was missing. In either case, this function
   138  // obtains the Lock (a write lock) before returning - the caller *must* call Unlock() themselves.
   139  func safeWriteLoad(resourceVersions *sync.Map, key string) *lockingRsrcVersion {
   140  	newRsrc := &lockingRsrcVersion{
   141  		RWMutex:         &sync.RWMutex{},
   142  		resourceVersion: "",
   143  	}
   144  
   145  	newRsrc.Lock()
   146  
   147  	rsrc, loaded := resourceVersions.LoadOrStore(key, newRsrc)
   148  	if loaded {
   149  		newRsrc.Unlock()
   150  
   151  		version := rsrc.(*lockingRsrcVersion)
   152  		version.Lock()
   153  
   154  		return version
   155  	}
   156  
   157  	return newRsrc
   158  }