Skip to content

Commit

Permalink
DRY out scoping predicate funcs
Browse files Browse the repository at this point in the history
  • Loading branch information
lucaspalm committed May 4, 2024
1 parent fd412e8 commit 3c11c28
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,13 @@ func (r *HostedClusterReconciler) SetupWithManager(mgr ctrl.Manager, createOrUpd
// namespaces, the events are filtered to enqueue only those resources which
// are annotated as being associated with a hostedcluster (using an annotation).
bldr := ctrl.NewControllerManagedBy(mgr).
For(&hyperv1.HostedCluster{}, builder.WithPredicates(hyperutil.PredicatesForHostedClusterAnnotationScoping())).
For(&hyperv1.HostedCluster{}, builder.WithPredicates(hyperutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
WithOptions(controller.Options{
RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(1*time.Second, 10*time.Second),
MaxConcurrentReconciles: 10,
})
for _, managedResource := range r.managedResources() {
bldr.Watches(managedResource, handler.EnqueueRequestsFromMapFunc(enqueueHostedClustersFunc(metricsSet, operatorNamespace, mgr.GetClient())), builder.WithPredicates(hyperutil.PredicatesForHostedClusterChildResourcesAnnotationScoping(mgr.GetClient())))
bldr.Watches(managedResource, handler.EnqueueRequestsFromMapFunc(enqueueHostedClustersFunc(metricsSet, operatorNamespace, mgr.GetClient())), builder.WithPredicates(hyperutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient())))
}

// Set based on SCC capability
Expand Down
18 changes: 9 additions & 9 deletions hypershift-operator/controllers/nodepool/nodepool_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,18 @@ var (

func (r *NodePoolReconciler) SetupWithManager(mgr ctrl.Manager) error {
controller, err := ctrl.NewControllerManagedBy(mgr).
For(&hyperv1.NodePool{}, builder.WithPredicates(supportutil.PredicatesForNodepoolAnnotationScoping(mgr.GetClient()))).
For(&hyperv1.NodePool{}, builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
// We want to reconcile when the HostedCluster IgnitionEndpoint is available.
Watches(&hyperv1.HostedCluster{}, handler.EnqueueRequestsFromMapFunc(r.enqueueNodePoolsForHostedCluster), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping())).
Watches(&capiv1.MachineDeployment{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForNodepoolChildResourcesAnnotationScoping(mgr.GetClient()))).
Watches(&capiv1.MachineSet{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForNodepoolChildResourcesAnnotationScoping(mgr.GetClient()))).
Watches(&capiaws.AWSMachineTemplate{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForNodepoolChildResourcesAnnotationScoping(mgr.GetClient()))).
Watches(&agentv1.AgentMachineTemplate{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForNodepoolChildResourcesAnnotationScoping(mgr.GetClient()))).
Watches(&capiazure.AzureMachineTemplate{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForNodepoolChildResourcesAnnotationScoping(mgr.GetClient()))).
Watches(&hyperv1.HostedCluster{}, handler.EnqueueRequestsFromMapFunc(r.enqueueNodePoolsForHostedCluster), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
Watches(&capiv1.MachineDeployment{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
Watches(&capiv1.MachineSet{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
Watches(&capiaws.AWSMachineTemplate{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
Watches(&agentv1.AgentMachineTemplate{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
Watches(&capiazure.AzureMachineTemplate{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
// We want to reconcile when the user data Secret or the token Secret is unexpectedly changed out of band.
Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForNodepoolChildResourcesAnnotationScoping(mgr.GetClient()))).
Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(enqueueParentNodePool), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
// We want to reconcile when the ConfigMaps referenced by the spec.config and also the core ones change.
Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(r.enqueueNodePoolsForConfig), builder.WithPredicates(supportutil.PredicatesForNodepoolChildResourcesAnnotationScoping(mgr.GetClient()))).
Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(r.enqueueNodePoolsForConfig), builder.WithPredicates(supportutil.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
WithOptions(controller.Options{
RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(1*time.Second, 10*time.Second),
MaxConcurrentReconciles: 10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (r *DedicatedServingComponentScheduler) SetupWithManager(mgr ctrl.Manager,

r.createOrUpdate = createOrUpdateProvider.CreateOrUpdate
builder := ctrl.NewControllerManagedBy(mgr).
For(&hyperv1.HostedCluster{}, builder.WithPredicates(util.PredicatesForHostedClusterAnnotationScoping())).
For(&hyperv1.HostedCluster{}, builder.WithPredicates(util.PredicatesForHostedClusterAnnotationScoping(mgr.GetClient()))).
WithOptions(controller.Options{
RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(1*time.Second, 10*time.Second),
MaxConcurrentReconciles: 10,
Expand Down
187 changes: 45 additions & 142 deletions support/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,173 +394,76 @@ func GetMgmtClusterCPUArch(ctx context.Context) (string, error) {
}

// PredicatesForHostedClusterAnnotationScoping returns predicate filters for all event types that will ignore incoming
// event requests for hostedcluster resources that do not match the "scope" annotation
// specified in the HOSTEDCLUSTERS_SCOPE_ANNOTATION env var. If not defined or empty, the default behavior is to accept all events for hostedclusters that do not have the annotation.
// The ENABLE_HOSTEDCLUSTERS_ANNOTATION_SCOPING env var must also be set to "true" to enable the scoping feature.
func PredicatesForHostedClusterAnnotationScoping() predicate.Predicate {
hcAnnotationScopingEnabledEnvVal := os.Getenv(EnableHostedClustersAnnotationScopingEnv)
hcScopeAnnotationEnvVal := os.Getenv(HostedClustersScopeAnnotationEnv)
filter := func(obj client.Object) bool {
if hcAnnotationScopingEnabledEnvVal != "true" {
return true
}
hostedClusterScopeAnnotation := ""
if obj.GetAnnotations() != nil {
hostedClusterScopeAnnotation = obj.GetAnnotations()[HostedClustersScopeAnnotation]
}
if hostedClusterScopeAnnotation == "" && hcScopeAnnotationEnvVal == "" {
return true
}
if hostedClusterScopeAnnotation != hcScopeAnnotationEnvVal {
return false // ignore event; the hostedcluster has a scope annotation that does not match what is defined in HOSTEDCLUSTERS_SCOPE_ANNOTATION
}
return true
}
return predicate.NewPredicateFuncs(filter)
}

// PredicatesForHostedClusterChildResourcesAnnotationScoping returns predicate filters for all event types that will ignore incoming
// event requests for resources in which the parent hostedcluster does not
// match the "scope" annotation specified in the HOSTEDCLUSTERS_SCOPE_ANNOTATION env var. If not defined or empty, the
// default behavior is to accept all events for hostedclusters that do not have the annotation.
// The ENABLE_HOSTEDCLUSTERS_ANNOTATION_SCOPING env var must also be set to "true" to enable the scoping feature.
func PredicatesForHostedClusterChildResourcesAnnotationScoping(r client.Reader) predicate.Predicate {
func PredicatesForHostedClusterAnnotationScoping(r client.Reader) predicate.Predicate {
hcAnnotationScopingEnabledEnvVal := os.Getenv(EnableHostedClustersAnnotationScopingEnv)
hcScopeAnnotationEnvVal := os.Getenv(HostedClustersScopeAnnotationEnv)
filter := func(obj client.Object) bool {
if hcAnnotationScopingEnabledEnvVal != "true" {
return true
}
hostedClusterName := ""
if obj.GetAnnotations() != nil {
hostedClusterName = obj.GetAnnotations()[HostedClusterAnnotation]
}
if hostedClusterName == "" {
return true
}
namespacedName := ParseNamespacedName(hostedClusterName)
hcluster := &hyperv1.HostedCluster{}
err := r.Get(context.Background(), namespacedName, hcluster)
if err != nil {
return true
}
hostedClusterScopeAnnotation := ""
if hcluster.GetAnnotations() != nil {
hostedClusterScopeAnnotation = hcluster.GetAnnotations()[HostedClustersScopeAnnotation]
return true // process event; the scoping feature has not been enabled via the ENABLE_HOSTEDCLUSTERS_ANNOTATION_SCOPING env var
}
hostedClusterScopeAnnotation := getHostedClusterScopeAnnotation(obj, r)
if hostedClusterScopeAnnotation == "" && hcScopeAnnotationEnvVal == "" {
return true
return true // process event; both the operator's scope and hostedcluster's scope are empty
}
if hostedClusterScopeAnnotation != hcScopeAnnotationEnvVal {
return false // ignore event; the parent hostedcluster's scope annotation does not match what is defined in HOSTEDCLUSTERS_SCOPE_ANNOTATION
return false // ignore event; the associated hostedcluster's scope annotation does not match what is defined in HOSTEDCLUSTERS_SCOPE_ANNOTATION
}
return true
}
return predicate.NewPredicateFuncs(filter)
}

// PredicatesForNodepoolAnnotationScoping returns predicate filters for all event types that will ignore incoming
// event requests for nodepool resources in which their owning hostedcluster resource doesn't have a scope annotation
// that matches what is specified in the HOSTEDCLUSTERS_SCOPE_ANNOTATION env var. If not defined or empty, the default behavior
// is to accept all nodepool events in which the owning hostedcluster resource does not have a corresponding scope annotation defined.
// The ENABLE_HOSTEDCLUSTERS_ANNOTATION_SCOPING env var must also be set to "true" to enable the scoping feature.
func PredicatesForNodepoolAnnotationScoping(r client.Reader) predicate.Predicate {
hcAnnotationScopingEnabledEnvVal := os.Getenv(EnableHostedClustersAnnotationScopingEnv)
hcScopeAnnotationEnvVal := os.Getenv(HostedClustersScopeAnnotationEnv)
filter := func(obj client.Object) bool {
if hcAnnotationScopingEnabledEnvVal != "true" {
return true
}

np, ok := obj.(*hyperv1.NodePool)
// getHostedClusterScopeAnnotation will extract the "scope" annotation from the hostedcluster resource that owns the specified object.
// Depending on the object type being passed in, slightly different paths will be used to ultimately retrieve the hostedcluster resource containing the annotation.
// If an annotation is not found, an empty string is returned.
func getHostedClusterScopeAnnotation(obj client.Object, r client.Reader) string {
hostedClusterName := ""
nodePoolName := ""
switch obj.(type) {
case *hyperv1.HostedCluster:
hc, ok := obj.(*hyperv1.HostedCluster)
if !ok {
return true
}

// use the Cluster Name from the nodepool spec to get the owning hostedcluster object
hostedClusterName := ""
if np.Spec.ClusterName != "" {
hostedClusterName = np.Spec.ClusterName
}
if hostedClusterName == "" {
return true
return ""
}
namespacedName := ParseNamespacedName(fmt.Sprintf("%s/%s", np.Namespace, hostedClusterName))
hcluster := &hyperv1.HostedCluster{}
err := r.Get(context.Background(), namespacedName, hcluster)
if err != nil {
return true
}

hostedClusterScopeAnnotation := ""
if hcluster.GetAnnotations() != nil {
hostedClusterScopeAnnotation = hcluster.GetAnnotations()[HostedClustersScopeAnnotation]
}
if hostedClusterScopeAnnotation == "" && hcScopeAnnotationEnvVal == "" {
return true
if hc.GetAnnotations() != nil {
return hc.GetAnnotations()[HostedClustersScopeAnnotation]
}
if hostedClusterScopeAnnotation != hcScopeAnnotationEnvVal {
return false // ignore event; the associated hostedcluster's scope annotation does not match what is defined in HOSTEDCLUSTERS_SCOPE_ANNOTATION
}
return true
}
return predicate.NewPredicateFuncs(filter)
}

// PredicatesForNodepoolChildResourcesAnnotationScoping returns predicate filters for all event types that will ignore incoming
// event requests for resources in which the parent hostedcluster does not
// match the "scope" annotation specified in the HOSTEDCLUSTERS_SCOPE_ANNOTATION env var. If not defined or empty, the
// default behavior is to accept all events for hostedclusters that do not have the annotation.
// The ENABLE_HOSTEDCLUSTERS_ANNOTATION_SCOPING env var must also be set to "true" to enable the scoping feature.
func PredicatesForNodepoolChildResourcesAnnotationScoping(r client.Reader) predicate.Predicate {
hcAnnotationScopingEnabledEnvVal := os.Getenv(EnableHostedClustersAnnotationScopingEnv)
hcScopeAnnotationEnvVal := os.Getenv(HostedClustersScopeAnnotationEnv)
filter := func(obj client.Object) bool {
if hcAnnotationScopingEnabledEnvVal != "true" {
return true
case *hyperv1.NodePool:
np, ok := obj.(*hyperv1.NodePool)
if !ok {
return ""
}

// use the object's "nodePool" annotation to retrieve the parent nodepool object
nodePoolName := ""
hostedClusterName = fmt.Sprintf("%s/%s", np.Namespace, np.Spec.ClusterName)
default:
if obj.GetAnnotations() != nil {
nodePoolName = obj.GetAnnotations()["hypershift.openshift.io/nodePool"]
hostedClusterName = obj.GetAnnotations()[HostedClusterAnnotation]
}
if nodePoolName == "" {
return true
}
namespacedName := ParseNamespacedName(nodePoolName)
np := &hyperv1.NodePool{}
err := r.Get(context.Background(), namespacedName, np)
if err != nil {
return true
}

// use the Cluster Name from the nodepool spec to get the owning hostedcluster object
hostedClusterName := ""
if np.Spec.ClusterName != "" {
hostedClusterName = np.Spec.ClusterName
}
if hostedClusterName == "" {
return true
}
namespacedName = ParseNamespacedName(fmt.Sprintf("%s/%s", np.Namespace, hostedClusterName))
hcluster := &hyperv1.HostedCluster{}
err = r.Get(context.Background(), namespacedName, hcluster)
if err != nil {
return true
}

hostedClusterScopeAnnotation := ""
if hcluster.GetAnnotations() != nil {
hostedClusterScopeAnnotation = hcluster.GetAnnotations()[HostedClustersScopeAnnotation]
}
if hostedClusterScopeAnnotation == "" && hcScopeAnnotationEnvVal == "" {
return true
}
if hostedClusterScopeAnnotation != hcScopeAnnotationEnvVal {
return false // ignore event; the associated hostedcluster's scope annotation does not match what is defined in HOSTEDCLUSTERS_SCOPE_ANNOTATION
if nodePoolName != "" {
namespacedName := ParseNamespacedName(nodePoolName)
np := &hyperv1.NodePool{}
err := r.Get(context.Background(), namespacedName, np)
if err != nil {
return ""
}
hostedClusterName = fmt.Sprintf("%s/%s", np.Namespace, np.Spec.ClusterName)
}
return true
}
return predicate.NewPredicateFuncs(filter)
if hostedClusterName == "" {
return ""
}
namespacedName := ParseNamespacedName(hostedClusterName)
hcluster := &hyperv1.HostedCluster{}
err := r.Get(context.Background(), namespacedName, hcluster)
if err != nil {
return ""
}
if hcluster.GetAnnotations() != nil {
return hcluster.GetAnnotations()[HostedClustersScopeAnnotation]
}
return ""
}

0 comments on commit 3c11c28

Please sign in to comment.