Skip to content

Commit

Permalink
Available CRDs check feature
Browse files Browse the repository at this point in the history
Reasons for this enhancement:
- A controller cannot set up a watch for a CRD that is not installed on the cluster, trying to set up a watch will panic the operator
- There is no known way, that we are aware of, to add a watch later without client cache issue

How does the enhancement work around the issue:
- A new controller to watch creation/deletion for the CRDs of interest to prevent unnecessary reconciles
- On start of the operator(main), detect which CRDs are avail (out of a fixed list)
- At the start each reconcile of new controller, we fetch the CRDs available again and compare it with CRDs fetched in previous step, If there is any change, we panic the op

Co-Authored-By: Rewant Soni <[email protected]>
  • Loading branch information
raaizik and rewantsoni committed Aug 1, 2024
1 parent bb238df commit 51cc46b
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
73 changes: 73 additions & 0 deletions controllers/crd/crd_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package crd

import (
"context"
"github.com/go-logr/logr"
"github.com/red-hat-storage/ocs-operator/v4/controllers/util"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/klog/v2"
"reflect"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// CustomResourceDefinitionReconciler reconciles a CustomResourceDefinition object
// nolint:revive
type CustomResourceDefinitionReconciler struct {
Client client.Client
ctx context.Context
Log logr.Logger
AvailableCrds map[string]bool
}

// Reconcile compares available CRDs maps following either a Create or Delete event
func (r *CustomResourceDefinitionReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
r.ctx = ctx
r.Log.Info("Reconciling CustomResourceDefinition.", "CRD", klog.KRef(request.Namespace, request.Name))

var err error
availableCrds, err := util.MapCRDAvailability(ctx, r.Client, util.CRDList...)
if err != nil {
return reconcile.Result{}, err
}
if !reflect.DeepEqual(availableCrds, r.AvailableCrds) {
r.Log.Info("CustomResourceDefinitions created/deleted. Restarting process.")
panic("CustomResourceDefinitions created/deleted. Restarting process.")
}
return reconcile.Result{}, nil
}

// SetupWithManager sets up a controller with a manager
func (r *CustomResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) error {
crdPredicate := predicate.Funcs{
CreateFunc: func(e event.TypedCreateEvent[client.Object]) bool {
crdAvailable, keyExist := r.AvailableCrds[e.Object.GetName()]
if keyExist && !crdAvailable {
r.Log.Info("CustomResourceDefinition %s was Created.", e.Object.GetName())
return true
}
return false
},
DeleteFunc: func(e event.TypedDeleteEvent[client.Object]) bool {
crdAvailable, keyExist := r.AvailableCrds[e.Object.GetName()]
if keyExist && crdAvailable {
r.Log.Info("CustomResourceDefinition %s was Deleted.", e.Object.GetName())
return true
}
return false
},
UpdateFunc: func(e event.TypedUpdateEvent[client.Object]) bool {
return false
},
GenericFunc: func(e event.TypedGenericEvent[client.Object]) bool {
return false
},
}
return ctrl.NewControllerManagedBy(mgr).
For(&apiextensionsv1.CustomResourceDefinition{}, builder.WithPredicates(crdPredicate)).
Complete(r)
}
16 changes: 16 additions & 0 deletions controllers/util/k8sutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
ocsv1 "github.com/red-hat-storage/ocs-operator/api/v4/v1"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -44,6 +45,8 @@ const (
OwnerUIDIndexName = "ownerUID"
)

var CRDList []string

// GetWatchNamespace returns the namespace the operator should be watching for changes
func GetWatchNamespace() (string, error) {
ns, found := os.LookupEnv(WatchNamespaceEnvVar)
Expand Down Expand Up @@ -149,3 +152,16 @@ func GenerateNameForNonResilientCephBlockPoolSC(initData *ocsv1.StorageCluster)
}
return fmt.Sprintf("%s-ceph-non-resilient-rbd", initData.Name)
}

func MapCRDAvailability(ctx context.Context, clnt client.Client, crdNames ...string) (map[string]bool, error) {
crdExist := map[string]bool{}
for _, crdName := range crdNames {
crd := &apiextensionsv1.CustomResourceDefinition{}
crd.Name = crdName
if err := clnt.Get(ctx, client.ObjectKeyFromObject(crd), crd); client.IgnoreNotFound(err) != nil {
return nil, fmt.Errorf("error getting CRD, %v", err)
}
crdExist[crdName] = crd.UID != ""
}
return crdExist, nil
}
17 changes: 17 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metrics "sigs.k8s.io/controller-runtime/pkg/metrics/server"

"github.com/red-hat-storage/ocs-operator/v4/controllers/crd"
"github.com/red-hat-storage/ocs-operator/v4/controllers/ocsinitialization"
"github.com/red-hat-storage/ocs-operator/v4/controllers/platform"
"github.com/red-hat-storage/ocs-operator/v4/controllers/storagecluster"
Expand Down Expand Up @@ -252,6 +253,22 @@ func main() {
os.Exit(1)
}

availCrds, err := util.MapCRDAvailability(context.Background(), apiClient, util.CRDList...)
if err != nil {
setupLog.Error(err, "Unable to get CRD")
os.Exit(1)
}
if len(util.CRDList) > 0 {
if err = (&crd.CustomResourceDefinitionReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("CustomResourceDefinitionReconciler"),
AvailableCrds: availCrds,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CustomResourceDefinitionReconciler")
os.Exit(1)
}
}

// Set OperatorCondition Upgradeable to True
// We have to at least default the condition to True or
// OLM will use the Readiness condition via our readiness probe instead:
Expand Down

0 comments on commit 51cc46b

Please sign in to comment.