Skip to content

Commit

Permalink
refactor rbacfinalizercontroller to fix cluster ns is terminating aft…
Browse files Browse the repository at this point in the history
…er delete clustermanager

Signed-off-by: Zhiwei Yin <zyin@redhat.com>
  • Loading branch information
zhiweiyin318 committed Jul 4, 2023
1 parent eb9b7fa commit 6fa715e
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 181 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ munge-csv
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
_output/
.idea/

.kubeconfig
.hub-kubeconfig
Expand Down
6 changes: 4 additions & 2 deletions pkg/registration/helpers/testing/testinghelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,23 +204,25 @@ func NewManifestWork(namespace, name string, finalizers []string, deletionTimest
return work
}

func NewRole(namespace, name string, finalizers []string, terminated bool) *rbacv1.Role {
func NewRole(namespace, name string, finalizers []string, labels map[string]string, terminated bool) *rbacv1.Role {
role := &rbacv1.Role{}
role.Namespace = namespace
role.Name = name
role.Finalizers = finalizers
role.Labels = labels
if terminated {
now := metav1.Now()
role.DeletionTimestamp = &now
}
return role
}

func NewRoleBinding(namespace, name string, finalizers []string, terminated bool) *rbacv1.RoleBinding {
func NewRoleBinding(namespace, name string, finalizers []string, labels map[string]string, terminated bool) *rbacv1.RoleBinding {
rolebinding := &rbacv1.RoleBinding{}
rolebinding.Namespace = namespace
rolebinding.Name = name
rolebinding.Finalizers = finalizers
rolebinding.Labels = labels
if terminated {
now := metav1.Now()
rolebinding.DeletionTimestamp = &now
Expand Down
5 changes: 2 additions & 3 deletions pkg/registration/hub/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,9 @@ func (m *HubManagerOptions) RunControllerManager(ctx context.Context, controller
)

rbacFinalizerController := rbacfinalizerdeletion.NewFinalizeController(
kubeInfomers.Rbac().V1().Roles(),
kubeInfomers.Rbac().V1().RoleBindings(),
kubeInfomers.Core().V1().Namespaces().Lister(),
clusterInformers.Cluster().V1().ManagedClusters().Lister(),
kubeInfomers.Core().V1().Namespaces(),
clusterInformers.Cluster().V1().ManagedClusters(),
workInformers.Work().V1().ManifestWorks().Lister(),
kubeClient.RbacV1(),
controllerContext.EventRecorder,
Expand Down
119 changes: 45 additions & 74 deletions pkg/registration/hub/rbacfinalizerdeletion/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,31 @@ package rbacfinalizerdeletion

import (
"context"
"fmt"
"open-cluster-management.io/ocm/pkg/common/queue"
"reflect"
"time"

"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/selection"
corev1informers "k8s.io/client-go/informers/core/v1"
rbacv1informers "k8s.io/client-go/informers/rbac/v1"
rbacv1client "k8s.io/client-go/kubernetes/typed/rbac/v1"
corelisters "k8s.io/client-go/listers/core/v1"
rbacv1listers "k8s.io/client-go/listers/rbac/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"

informerv1 "open-cluster-management.io/api/client/cluster/informers/externalversions/cluster/v1"
clusterv1listers "open-cluster-management.io/api/client/cluster/listers/cluster/v1"
worklister "open-cluster-management.io/api/client/work/listers/work/v1"
clusterv1 "open-cluster-management.io/api/cluster/v1"

"open-cluster-management.io/ocm/pkg/common/queue"
)

const (
Expand All @@ -45,125 +46,95 @@ type finalizeController struct {
// NewFinalizeController ensures all manifestworks are deleted before role/rolebinding for work
// agent are deleted in a terminating cluster namespace.
func NewFinalizeController(
roleInformer rbacv1informers.RoleInformer,
roleBindingInformer rbacv1informers.RoleBindingInformer,
namespaceLister corelisters.NamespaceLister,
clusterLister clusterv1listers.ManagedClusterLister,
namespaceInformer corev1informers.NamespaceInformer,
clusterInformer informerv1.ManagedClusterInformer,
manifestWorkLister worklister.ManifestWorkLister,
rbacClient rbacv1client.RbacV1Interface,
eventRecorder events.Recorder,
) factory.Controller {

controller := &finalizeController{
roleLister: roleInformer.Lister(),
roleBindingLister: roleBindingInformer.Lister(),
namespaceLister: namespaceLister,
clusterLister: clusterLister,
namespaceLister: namespaceInformer.Lister(),
clusterLister: clusterInformer.Lister(),
manifestWorkLister: manifestWorkLister,
rbacClient: rbacClient,
eventRecorder: eventRecorder,
}

return factory.New().
WithInformersQueueKeysFunc(queue.QueueKeyByMetaNamespaceName, roleInformer.Informer(), roleBindingInformer.Informer()).
WithInformersQueueKeysFunc(queue.QueueKeyByMetaNamespaceName, clusterInformer.Informer(), namespaceInformer.Informer()).
WithSync(controller.sync).ToController("FinalizeController", eventRecorder)
}

func (m *finalizeController) sync(ctx context.Context, controllerContext factory.SyncContext) error {
key := controllerContext.QueueKey()
namespace, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
// ignore role/rolebinding whose key is not in format: namespace/name
if key == "" {
return nil
}

cluster, err := m.clusterLister.Get(namespace)
if err != nil && !errors.IsNotFound(err) {
return err
}
ns, err := m.namespaceLister.Get(namespace)
_, clusterName, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
return err
return nil
}

// cluster is deleted or being deleted
role, rolebinding, err := m.getRoleAndRoleBinding(namespace, name)
if err != nil {
cluster, err := m.clusterLister.Get(clusterName)
if err != nil && !errors.IsNotFound(err) {
return err
}

err = m.syncRoleAndRoleBinding(ctx, controllerContext, role, rolebinding, ns, cluster)

if err != nil {
klog.Errorf("Reconcile role/rolebinding %s fails with err: %v", key, err)
}
return err
}

func (m *finalizeController) syncRoleAndRoleBinding(ctx context.Context, controllerContext factory.SyncContext,
role *rbacv1.Role, rolebinding *rbacv1.RoleBinding, ns *corev1.Namespace, cluster *clusterv1.ManagedCluster) error {
// Skip if neither role nor rolebinding has the finalizer
if !hasFinalizer(role, manifestWorkFinalizer) && !hasFinalizer(rolebinding, manifestWorkFinalizer) {
ns, err := m.namespaceLister.Get(clusterName)
if errors.IsNotFound(err) {
return nil
}
if err != nil {
return err
}

// There are two possible cases that we need to remove finalizers on role/rolebindings based on
// clean of manifestworks.
// 1. The namespace is finalizing.
// 2. The cluster is finalizing but namespace fails to be deleted.
if !ns.DeletionTimestamp.IsZero() || (cluster != nil && !cluster.DeletionTimestamp.IsZero()) {
// 2. The cluster is finalizing or not found.
if !ns.DeletionTimestamp.IsZero() || cluster == nil ||
(cluster != nil && !cluster.DeletionTimestamp.IsZero()) {
works, err := m.manifestWorkLister.ManifestWorks(ns.Name).List(labels.Everything())
if err != nil {
return err
}

if len(works) != 0 {
return fmt.Errorf("still having %d works in the cluster namespace %s", len(works), ns.Name)
}
}

// remove finalizer from role/rolebinding
if pendingFinalization(role) {
if err := m.removeFinalizerFromRole(ctx, role, manifestWorkFinalizer); err != nil {
return err
}
}

if pendingFinalization(rolebinding) {
if err := m.removeFinalizerFromRoleBinding(ctx, rolebinding, manifestWorkFinalizer); err != nil {
return err
controllerContext.Queue().AddAfter(clusterName, 10*time.Second)
klog.Warningf("still having %d works in the cluster namespace %s", len(works), ns.Name)
return nil
}
return m.syncRoleBindings(ctx, controllerContext, clusterName)
}
return nil
}

func (m *finalizeController) getRoleAndRoleBinding(namespace, name string) (*rbacv1.Role, *rbacv1.RoleBinding, error) {
role, err := m.roleLister.Roles(namespace).Get(name)
func (m *finalizeController) syncRoleBindings(ctx context.Context, controllerContext factory.SyncContext,
namespace string) error {
requirement, _ := labels.NewRequirement(clusterv1.ClusterNameLabelKey, selection.Exists, []string{})
selector := labels.NewSelector().Add(*requirement)
roleBindings, err := m.roleBindingLister.RoleBindings(namespace).List(selector)
if err != nil && !errors.IsNotFound(err) {
return nil, nil, err
}

rolebinding, err := m.roleBindingLister.RoleBindings(namespace).Get(name)
if err != nil && !errors.IsNotFound(err) {
return nil, nil, err
}

return role, rolebinding, nil
}

// removeFinalizerFromRole removes the particular finalizer from role
func (m *finalizeController) removeFinalizerFromRole(ctx context.Context, role *rbacv1.Role, finalizer string) error {
if role == nil {
return nil
return err
}

role = role.DeepCopy()
if changed := removeFinalizer(role, finalizer); !changed {
return nil
for _, roleBinding := range roleBindings {
// Skip if roleBinding has no the finalizer
if !hasFinalizer(roleBinding, manifestWorkFinalizer) {
continue
}
// remove finalizer from roleBinding
if pendingFinalization(roleBinding) {
if err := m.removeFinalizerFromRoleBinding(ctx, roleBinding, manifestWorkFinalizer); err != nil {
return err
}
}
}

_, err := m.rbacClient.Roles(role.Namespace).Update(ctx, role, metav1.UpdateOptions{})
return err
return nil
}

// removeFinalizerFromRoleBinding removes the particular finalizer from rolebinding
Expand Down
Loading

0 comments on commit 6fa715e

Please sign in to comment.