Skip to content

Commit

Permalink
controllers: surface dependent operator upgradeable conditions
Browse files Browse the repository at this point in the history
- odf-operator brings dependencies using OLM
- this PR ensures odf-operator sets it's own operator condition based on
upgradeability of it's dependents.

Signed-off-by: Leela Venkaiah G <lgangava@ibm.com>
  • Loading branch information
leelavg authored and iamniting committed Jul 25, 2024
1 parent 45f756c commit 88aa121
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 4 deletions.
146 changes: 144 additions & 2 deletions controllers/subscription_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,40 @@ import (
"github.com/go-logr/logr"
"go.uber.org/multierr"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
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/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
operatorv2 "github.com/operator-framework/api/pkg/operators/v2"
"github.com/operator-framework/operator-lib/conditions"
odfv1alpha1 "github.com/red-hat-storage/odf-operator/api/v1alpha1"
"github.com/red-hat-storage/odf-operator/pkg/util"
)

// SubscriptionReconciler reconciles a Subscription object
type SubscriptionReconciler struct {
client.Client
Scheme *runtime.Scheme
Recorder *EventReporter
Scheme *runtime.Scheme
Recorder *EventReporter
ConditionName string
OperatorCondition conditions.Condition
}

//+kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions/finalizers,verbs=update
//+kubebuilder:rbac:groups=operators.coreos.com,resources=installplans,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=operators.coreos.com,resources=clusterserviceversions/finalizers,verbs=update
//+kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions,verbs=get;list;watch

// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.8.3/pkg/reconcile
Expand All @@ -65,6 +75,11 @@ func (r *SubscriptionReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, err
}

err = r.setOperatorCondition(logger, req.NamespacedName.Namespace)
if err != nil {
return ctrl.Result{}, err
}

err = r.ensureSubscriptions(logger, req.NamespacedName.Namespace)
if err != nil {
return ctrl.Result{}, err
Expand Down Expand Up @@ -117,6 +132,45 @@ func (r *SubscriptionReconciler) ensureSubscriptions(logger logr.Logger, namespa
return err
}

func (r *SubscriptionReconciler) setOperatorCondition(logger logr.Logger, namespace string) error {
ocdList := &operatorv2.OperatorConditionList{}
err := r.Client.List(context.TODO(), ocdList, client.InNamespace(namespace))
if err != nil {
logger.Error(err, "failed to list OperatorConditions")
return err
}

condNames := append(GetVendorCsvNames(StorageClusterKind), GetVendorCsvNames(FlashSystemKind)...)

condMap := make(map[string]struct{}, len(condNames))
for i := range condNames {
condMap[condNames[i]] = struct{}{}
}

for ocdIdx := range ocdList.Items {
// skip operatorconditions of not dependent operators
if _, exist := condMap[ocdList.Items[ocdIdx].GetName()]; !exist {
continue
}

ocd := &ocdList.Items[ocdIdx]
cond := getNotUpgradeableCond(ocd)
if cond != nil {
// operator is not upgradeable
msg := fmt.Sprintf("%s:%s", ocd.GetName(), cond.Message)
logger.Info("setting operator upgradeable status", "status", cond.Status)
return r.OperatorCondition.Set(context.TODO(), cond.Status,
conditions.WithReason(cond.Reason), conditions.WithMessage(msg))
}
}

// all operators are upgradeable
status := metav1.ConditionTrue
logger.Info("setting operator upgradeable status", "status", status)
return r.OperatorCondition.Set(context.TODO(), status,
conditions.WithReason("Dependents"), conditions.WithMessage("No dependent reports not upgradeable status"))
}

// SetupWithManager sets up the controller with the Manager.
func (r *SubscriptionReconciler) SetupWithManager(mgr ctrl.Manager) error {

Expand Down Expand Up @@ -151,10 +205,98 @@ func (r *SubscriptionReconciler) SetupWithManager(mgr ctrl.Manager) error {
},
}

conditionPredicate := predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
return false
},
UpdateFunc: func(e event.UpdateEvent) bool {
// not mandatory but these checks wouldn't harm
if e.ObjectOld == nil || e.ObjectNew == nil {
return false
}
oldObj, _ := e.ObjectOld.(*operatorv2.OperatorCondition)
newObj, _ := e.ObjectNew.(*operatorv2.OperatorCondition)
if oldObj == nil || newObj == nil {
return false
}

// skip sending a reconcile event if our own condition is updated
if newObj.GetName() == r.ConditionName {
return false
}

// change in admin set conditions for upgradeability
oldOverride := util.Find(oldObj.Spec.Overrides, func(cond *metav1.Condition) bool {
return cond.Type == operatorv2.Upgradeable
})
newOverride := util.Find(newObj.Spec.Overrides, func(cond *metav1.Condition) bool {
return cond.Type == operatorv2.Upgradeable
})
if oldOverride != nil && newOverride == nil {
// override is removed
return true
}
if newOverride != nil {
if oldOverride == nil {
return true
}
return oldOverride.Status != newOverride.Status
}

// change in operator set conditions for upgradeability
oldCond := util.Find(oldObj.Status.Conditions, func(cond *metav1.Condition) bool {
return cond.Type == operatorv2.Upgradeable
})
newCond := util.Find(newObj.Status.Conditions, func(cond *metav1.Condition) bool {
return cond.Type == operatorv2.Upgradeable
})
if newCond != nil {
if oldCond == nil {
return true
}
return oldCond.Status != newCond.Status
}

return false
},
}
enqueueFromCondition := handler.EnqueueRequestsFromMapFunc(
func(ctx context.Context, obj client.Object) []reconcile.Request {
if _, ok := obj.(*operatorv2.OperatorCondition); !ok {
return []reconcile.Request{}
}
logger := log.FromContext(ctx)
sub, err := GetOdfSubscription(r.Client)
if err != nil {
logger.Error(err, "failed to get ODF Subscription")
return []reconcile.Request{}
}
return []reconcile.Request{{NamespacedName: types.NamespacedName{Name: sub.Name, Namespace: sub.Namespace}}}
},
)

return ctrl.NewControllerManagedBy(mgr).
For(&operatorv1alpha1.Subscription{},
builder.WithPredicates(generationChangedPredicate, subscriptionPredicate)).
Owns(&operatorv1alpha1.Subscription{},
builder.WithPredicates(generationChangedPredicate)).
Watches(&operatorv2.OperatorCondition{}, enqueueFromCondition, builder.WithPredicates(conditionPredicate)).
Complete(r)
}

func getNotUpgradeableCond(ocd *operatorv2.OperatorCondition) *metav1.Condition {
cond := util.Find(ocd.Spec.Overrides, func(cd *metav1.Condition) bool {
return cd.Type == operatorv2.Upgradeable
})
if cond != nil {
if cond.Status != "True" {
return cond
}
// if upgradeable is overridden we should skip checking operator set conditions
return nil
}

return util.Find(ocd.Status.Conditions, func(cd *metav1.Condition) bool {
return cd.Type == operatorv2.Upgradeable && cd.Status != "True"
})
}
18 changes: 16 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"

operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
operatorv2 "github.com/operator-framework/api/pkg/operators/v2"

ibmv1alpha1 "github.com/IBM/ibm-storage-odf-operator/api/v1alpha1"
ocsv1 "github.com/red-hat-storage/ocs-operator/api/v1"
Expand Down Expand Up @@ -61,6 +62,7 @@ func init() {
utilruntime.Must(ibmv1alpha1.AddToScheme(scheme))

utilruntime.Must(operatorv1alpha1.AddToScheme(scheme))
utilruntime.Must(operatorv2.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme

utilruntime.Must(consolev1.AddToScheme(scheme))
Expand Down Expand Up @@ -124,9 +126,21 @@ func main() {
os.Exit(1)
}

conditionName, err := util.GetConditionName(mgr.GetClient())
if err != nil {
setupLog.Error(err, "unable to get condition name")
os.Exit(1)
}
condition, err := util.NewUpgradeableCondition(mgr.GetClient())
if err != nil {
setupLog.Error(err, "unable to get OperatorCondition")
os.Exit(1)
}
subscriptionReconciler := &controllers.SubscriptionReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
ConditionName: conditionName,
OperatorCondition: condition,
}
if err = subscriptionReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Subscription")
Expand Down
18 changes: 18 additions & 0 deletions pkg/util/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"context"

configv1 "github.com/openshift/api/config/v1"
operatorv2 "github.com/operator-framework/api/pkg/operators/v2"
"github.com/operator-framework/operator-lib/conditions"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand All @@ -35,3 +37,19 @@ func DetermineOpenShiftVersion(client client.Client) (string, error) {
}
return clusterVersion, nil
}

func getConditionFactory(client client.Client) conditions.Factory {
return conditions.InClusterFactory{Client: client}
}

func GetConditionName(client client.Client) (string, error) {
namespacedName, err := getConditionFactory(client).GetNamespacedName()
if err != nil {
return "", err
}
return namespacedName.Name, nil
}

func NewUpgradeableCondition(client client.Client) (conditions.Condition, error) {
return getConditionFactory(client).NewCondition(operatorv2.ConditionType(operatorv2.Upgradeable))
}
11 changes: 11 additions & 0 deletions pkg/util/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,14 @@ func RemoveFromSlice(slice []string, s string) (result []string) {
}
return
}

// Find returns the first entry matching the function "f" or else return nil
func Find[T any](list []T, f func(item *T) bool) *T {
for idx := range list {
ele := &list[idx]
if f(ele) {
return ele
}
}
return nil
}

0 comments on commit 88aa121

Please sign in to comment.