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 }