Skip to content

Commit

Permalink
refactor gc controller
Browse files Browse the repository at this point in the history
Signed-off-by: Zhiwei Yin <[email protected]>
  • Loading branch information
zhiweiyin318 committed Sep 11, 2023
1 parent 3fc013f commit d8eb6a2
Show file tree
Hide file tree
Showing 37 changed files with 2,013 additions and 504 deletions.
2 changes: 1 addition & 1 deletion deploy/cluster-manager/config/rbac/cluster_role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ rules:
# Some rbac needed in cluster-manager
- apiGroups: ["addon.open-cluster-management.io"]
resources: ["managedclusteraddons", "clustermanagementaddons"]
verbs: ["create", "update", "patch", "get", "list", "watch", "delete"]
verbs: ["create", "update", "patch", "get", "list", "watch", "delete", "deletecollection"]
- apiGroups: ["addon.open-cluster-management.io"]
resources: ["managedclusteraddons/status", "clustermanagementaddons/status"]
verbs: ["patch", "update"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ metadata:
categories: Integration & Delivery,OpenShift Optional
certified: "false"
containerImage: quay.io/open-cluster-management/registration-operator:latest
createdAt: "2023-09-04T03:11:35Z"
createdAt: "2023-09-07T15:52:35Z"
description: Manages the installation and upgrade of the ClusterManager.
operators.operatorframework.io/builder: operator-sdk-v1.28.0
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
Expand Down Expand Up @@ -327,6 +327,7 @@ spec:
- list
- watch
- delete
- deletecollection
- apiGroups:
- addon.open-cluster-management.io
resources:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ metadata:
categories: Integration & Delivery,OpenShift Optional
certified: "false"
containerImage: quay.io/open-cluster-management/registration-operator:latest
createdAt: "2023-09-04T03:11:35Z"
createdAt: "2023-09-07T15:52:35Z"
description: Manages the installation and upgrade of the Klusterlet.
operators.operatorframework.io/builder: operator-sdk-v1.28.0
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ rules:
# Allow hub to manage managed cluster addons
- apiGroups: ["addon.open-cluster-management.io"]
resources: ["managedclusteraddons"]
verbs: ["get", "list", "watch"]
verbs: ["get", "list", "watch", "delete", "deletecollection"]
- apiGroups: ["addon.open-cluster-management.io"]
resources: ["managedclusteraddons/status"]
verbs: ["patch", "update"]
Expand Down
39 changes: 38 additions & 1 deletion pkg/registration/helpers/testing/testinghelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"

addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
clusterv1 "open-cluster-management.io/api/cluster/v1"
workapiv1 "open-cluster-management.io/api/work/v1"
)
Expand Down Expand Up @@ -192,11 +193,12 @@ func NewNamespace(name string, terminated bool) *corev1.Namespace {
return namespace
}

func NewManifestWork(namespace, name string, finalizers []string, deletionTimestamp *metav1.Time) *workapiv1.ManifestWork {
func NewManifestWork(namespace, name string, finalizers []string, labels map[string]string, deletionTimestamp *metav1.Time) *workapiv1.ManifestWork {
work := &workapiv1.ManifestWork{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
Labels: labels,
Finalizers: finalizers,
DeletionTimestamp: deletionTimestamp,
},
Expand Down Expand Up @@ -230,6 +232,30 @@ func NewRoleBinding(namespace, name string, finalizers []string, labels map[stri
return rolebinding
}

func NewClusterRole(name string, finalizers []string, labels map[string]string, terminated bool) *rbacv1.ClusterRole {
clusterRole := &rbacv1.ClusterRole{}
clusterRole.Name = name
clusterRole.Finalizers = finalizers
clusterRole.Labels = labels
if terminated {
now := metav1.Now()
clusterRole.DeletionTimestamp = &now
}
return clusterRole
}

func NewClusterRoleBinding(name string, finalizers []string, labels map[string]string, terminated bool) *rbacv1.ClusterRoleBinding {
clusterRoleBinding := &rbacv1.ClusterRoleBinding{}
clusterRoleBinding.Name = name
clusterRoleBinding.Finalizers = finalizers
clusterRoleBinding.Labels = labels
if terminated {
now := metav1.Now()
clusterRoleBinding.DeletionTimestamp = &now
}
return clusterRoleBinding
}

func NewResourceList(cpu, mem int) corev1.ResourceList {
return corev1.ResourceList{
corev1.ResourceCPU: *resource.NewQuantity(int64(cpu), resource.DecimalExponent),
Expand Down Expand Up @@ -499,3 +525,14 @@ func WriteFile(filename string, data []byte) {
panic(err)
}
}

func NewManagedClusterAddons(name, namespace string, finalizers []string, deletionTimestamp *metav1.Time) *addonv1alpha1.ManagedClusterAddOn {
return &addonv1alpha1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Finalizers: finalizers,
DeletionTimestamp: deletionTimestamp,
},
}
}
29 changes: 7 additions & 22 deletions pkg/registration/hub/clusterrole/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package clusterrole

import (
"context"
"embed"
"fmt"

"github.com/openshift/library-go/pkg/controller/factory"
Expand All @@ -18,6 +17,7 @@ import (

"open-cluster-management.io/ocm/pkg/common/apply"
"open-cluster-management.io/ocm/pkg/common/queue"
"open-cluster-management.io/ocm/pkg/registration/hub/manifests"
)

const (
Expand All @@ -26,13 +26,10 @@ const (
)

var clusterRoleFiles = []string{
"manifests/managedcluster-registration-clusterrole.yaml",
"manifests/managedcluster-work-clusterrole.yaml",
"rbac/managedcluster-registration-clusterrole.yaml",
"rbac/managedcluster-work-clusterrole.yaml",
}

//go:embed manifests
var manifestFiles embed.FS

// clusterroleController maintains the necessary clusterroles for registration and work agent on hub cluster.
type clusterroleController struct {
kubeClient kubernetes.Interface
Expand Down Expand Up @@ -76,29 +73,17 @@ func (c *clusterroleController) sync(ctx context.Context, syncCtx factory.SyncCo
return err
}

var errs []error
// Clean up managedcluser cluserroles if there are no managed clusters
// gc controller will handle the clusterroles cleanup
if len(managedClusters) == 0 {
results := resourceapply.DeleteAll(
ctx,
resourceapply.NewKubeClientHolder(c.kubeClient),
c.eventRecorder,
manifestFiles.ReadFile,
clusterRoleFiles...,
)
for _, result := range results {
if result.Error != nil {
errs = append(errs, fmt.Errorf("%q (%T): %v", result.File, result.Type, result.Error))
}
}
return operatorhelpers.NewMultiLineAggregate(errs)
return nil
}

errs := []error{}
// Make sure the managedcluser cluserroles are existed if there are clusters
results := c.applier.Apply(
ctx,
syncCtx.Recorder(),
manifestFiles.ReadFile,
manifests.RBACManifests.ReadFile,
clusterRoleFiles...,
)

Expand Down
18 changes: 0 additions & 18 deletions pkg/registration/hub/clusterrole/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/openshift/library-go/pkg/operator/events/eventstesting"
"github.com/openshift/library-go/pkg/operator/resource/resourceapply"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubeinformers "k8s.io/client-go/informers"
kubefake "k8s.io/client-go/kubernetes/fake"
Expand Down Expand Up @@ -45,23 +44,6 @@ func TestSyncManagedClusterClusterRole(t *testing.T) {
}
},
},
{
name: "delete clusterroles",
clusters: []runtime.Object{},
clusterroles: []runtime.Object{
&rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: "open-cluster-management:managedcluster:registration"}},
&rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: "open-cluster-management:managedcluster:work"}},
},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
testingcommon.AssertActions(t, actions, "delete", "delete")
if actions[0].(clienttesting.DeleteActionImpl).Name != "open-cluster-management:managedcluster:registration" {
t.Errorf("expected registration clusterrole, but failed")
}
if actions[1].(clienttesting.DeleteActionImpl).Name != "open-cluster-management:managedcluster:work" {
t.Errorf("expected work clusterrole, but failed")
}
},
},
}

for _, c := range cases {
Expand Down
161 changes: 161 additions & 0 deletions pkg/registration/hub/gc/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package gc

import (
"context"
"time"

"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
operatorhelpers "github.com/openshift/library-go/pkg/operator/v1helpers"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
rbacv1listers "k8s.io/client-go/listers/rbac/v1"
"k8s.io/client-go/metadata"

addonv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
clientset "open-cluster-management.io/api/client/cluster/clientset/versioned"
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/patcher"
"open-cluster-management.io/ocm/pkg/common/queue"
)

type gcReconcileOp int

const (
gcReconcileRequeue gcReconcileOp = iota
gcReconcileStop
gcReconcileContinue
)

const (
// TODO: this condition definition should be moved to api repo
ManagedClusterConditionDeleting string = "ManagedClusterIsDeleting"
)

var (
addonGvr = schema.GroupVersionResource{Group: "addon.open-cluster-management.io", Version: "v1alpha1", Resource: "managedclusteraddons"}
workGvr = schema.GroupVersionResource{Group: "work.open-cluster-management.io", Version: "v1", Resource: "manifestworks"}
)

// gcReconciler is an interface for reconcile cleanup logic after cluster is deleted.
// clusterName is from the queueKey, cluster may be nil.
type gcReconciler interface {
reconcile(ctx context.Context, clusterName string, cluster *clusterv1.ManagedCluster) (gcReconcileOp, error)
}

type GCController struct {
clusterLister clusterv1listers.ManagedClusterLister
clusterPatcher patcher.Patcher[*clusterv1.ManagedCluster, clusterv1.ManagedClusterSpec, clusterv1.ManagedClusterStatus]
gcReconcilers []gcReconciler
}

// NewGCController ensures the related resources are cleaned up after cluster is deleted
func NewGCController(
clusterRoleLister rbacv1listers.ClusterRoleLister,
clusterRoleBindingLister rbacv1listers.ClusterRoleBindingLister,
roleBindingLister rbacv1listers.RoleBindingLister,
namespaceInformer corev1informers.NamespaceInformer,
clusterInformer informerv1.ManagedClusterInformer,
manifestWorkLister worklister.ManifestWorkLister,
clusterClient clientset.Interface,
kubeClient kubernetes.Interface,
metadataClient metadata.Interface,
eventRecorder events.Recorder,
) factory.Controller {
clusterPatcher := patcher.NewPatcher[
*clusterv1.ManagedCluster, clusterv1.ManagedClusterSpec, clusterv1.ManagedClusterStatus](
clusterClient.ClusterV1().ManagedClusters())
controller := &GCController{
clusterLister: clusterInformer.Lister(),
clusterPatcher: clusterPatcher,
gcReconcilers: []gcReconciler{
// currently only support to gc addons and works, need consider to add rbac to registration if gc other resources.
newGCResourcesController(metadataClient, []resourceFilter{
{addonGvr, ""}, {workGvr, getGCWorkLabelSelector()}}, eventRecorder),
newGCClusterRbacController(kubeClient, clusterPatcher, clusterInformer, clusterRoleLister,
clusterRoleBindingLister, roleBindingLister, manifestWorkLister, eventRecorder),
newGCClusterRoleController(clusterRoleLister, clusterInformer.Lister(), manifestWorkLister, kubeClient.RbacV1(), eventRecorder),
},
}

return factory.New().
WithInformersQueueKeysFunc(queue.QueueKeyByMetaName, clusterInformer.Informer(), namespaceInformer.Informer()).
WithSync(controller.sync).ToController("GCController", eventRecorder)
}

func (r *GCController) sync(ctx context.Context, controllerContext factory.SyncContext) error {
clusterName := controllerContext.QueueKey()
if clusterName == "" || clusterName == factory.DefaultQueueKey {
return nil
}

cluster, err := r.clusterLister.Get(clusterName)
if err != nil && !errors.IsNotFound(err) {
return err
}

var errs []error
// the reconcilers should reconcile in order.
for index := 0; index < len(r.gcReconcilers); index++ {
op, err := r.gcReconcilers[index].reconcile(ctx, clusterName, cluster)
switch op {
case gcReconcileRequeue:
controllerContext.Queue().AddAfter(clusterName, 10*time.Second)
return nil
case gcReconcileStop:
return err
}

if err != nil {
errs = append(errs, err)
}
}
if len(errs) == 0 {
return nil
}

if cluster == nil {
return utilerrors.NewAggregate(errs)
}

if cluster.DeletionTimestamp.IsZero() {
return utilerrors.NewAggregate(errs)
}

newCluster := cluster.DeepCopy()
applyErrors := operatorhelpers.NewMultiLineAggregate(errs)
meta.SetStatusCondition(&newCluster.Status.Conditions, metav1.Condition{
Type: ManagedClusterConditionDeleting,
Status: metav1.ConditionFalse,
Reason: "Error",
Message: applyErrors.Error(),
})

if _, err := r.clusterPatcher.PatchStatus(ctx, newCluster, newCluster.Status, cluster.Status); err != nil {
return err
}

return utilerrors.NewAggregate(errs)
}

// getGCWorkLabelSelector only filter the works which are not created by addon
func getGCWorkLabelSelector() string {
selector := &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: addonv1alpha1.AddonLabelKey,
Operator: metav1.LabelSelectorOpDoesNotExist,
},
},
}
return metav1.FormatLabelSelector(selector)
}
Loading

0 comments on commit d8eb6a2

Please sign in to comment.