Skip to content

Commit

Permalink
controllers: set operatorconditions for upgradeability
Browse files Browse the repository at this point in the history
Signed-off-by: Leela Venkaiah G <[email protected]>
  • Loading branch information
leelavg committed Jan 10, 2024
1 parent 9bd12cc commit 1edf4ea
Show file tree
Hide file tree
Showing 23 changed files with 1,093 additions and 57 deletions.
5 changes: 4 additions & 1 deletion api/v1alpha1/storageclient_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ type StorageClientSpec struct {

// StorageClientStatus defines the observed state of StorageClient
type StorageClientStatus struct {
// NOTE: Do not remove "omitempty" from the tags as that'll make mergepatch incompatible

Phase storageClientPhase `json:"phase,omitempty"`

// ConsumerID will hold the identity of this cluster inside the attached provider cluster
ConsumerID string `json:"id,omitempty"`
ConsumerID string `json:"id,omitempty"`
DesiredOperatorVersion string `json:"desiredOperatorVersion,omitempty"`
}

//+kubebuilder:object:root=true
Expand Down
13 changes: 13 additions & 0 deletions bundle/manifests/ocs-client-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ spec:
- get
- list
- watch
- apiGroups:
- operators.coreos.com
resources:
- subscriptions
verbs:
- get
- list
- apiGroups:
- security.openshift.io
resources:
Expand Down Expand Up @@ -599,6 +606,12 @@ spec:
verbs:
- get
- list
- apiGroups:
- ocs.openshift.io
resources:
- storageclients/status
verbs:
- patch
- apiGroups:
- ""
resources:
Expand Down
2 changes: 2 additions & 0 deletions bundle/manifests/ocs.openshift.io_storageclients.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ spec:
status:
description: StorageClientStatus defines the observed state of StorageClient
properties:
desiredOperatorVersion:
type: string
id:
description: ConsumerID will hold the identity of this cluster inside
the attached provider cluster
Expand Down
2 changes: 2 additions & 0 deletions config/crd/bases/ocs.openshift.io_storageclients.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ spec:
status:
description: StorageClientStatus defines the observed state of StorageClient
properties:
desiredOperatorVersion:
type: string
id:
description: ConsumerID will hold the identity of this cluster inside
the attached provider cluster
Expand Down
7 changes: 7 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ rules:
- get
- list
- watch
- apiGroups:
- operators.coreos.com
resources:
- subscriptions
verbs:
- get
- list
- apiGroups:
- security.openshift.io
resources:
Expand Down
6 changes: 6 additions & 0 deletions config/rbac/status-reporter-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ rules:
verbs:
- get
- list
- apiGroups:
- ocs.openshift.io
resources:
- storageclients/status
verbs:
- patch
- apiGroups:
- ""
resources:
Expand Down
22 changes: 4 additions & 18 deletions controllers/storageclient_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ import (
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/red-hat-storage/ocs-client-operator/api/v1alpha1"
"github.com/red-hat-storage/ocs-client-operator/pkg/utils"

configv1 "github.com/openshift/api/config/v1"
opv1a1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
providerClient "github.com/red-hat-storage/ocs-operator/v4/services/provider/client"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
Expand Down Expand Up @@ -61,8 +59,6 @@ const (
storageClientNameLabel = "ocs.openshift.io/storageclient.name"
storageClientNamespaceLabel = "ocs.openshift.io/storageclient.namespace"
storageClientFinalizer = "storageclient.ocs.openshift.io"

csvPrefix = "ocs-client-operator"
)

// StorageClientReconciler reconciles a StorageClient object
Expand Down Expand Up @@ -271,16 +267,10 @@ func (s *StorageClientReconciler) onboardConsumer(instance *v1alpha1.StorageClie
return reconcile.Result{}, fmt.Errorf("failed to get the clusterVersion version of the OCP cluster: %v", err)
}

// TODO Have a version file corresponding to the release
csvList := opv1a1.ClusterServiceVersionList{}
if err = s.list(&csvList, client.InNamespace(s.OperatorNamespace)); err != nil {
return reconcile.Result{}, fmt.Errorf("failed to list csv resources in ns: %v, err: %v", s.OperatorNamespace, err)
}
csv := utils.Find(csvList.Items, func(csv *opv1a1.ClusterServiceVersion) bool {
return strings.HasPrefix(csv.Name, csvPrefix)
})
if csv == nil {
return reconcile.Result{}, fmt.Errorf("unable to find csv with prefix %q", csvPrefix)
csv, err := utils.GetCSVByPrefix(s.ctx, s.Client, "ocs-client-operator", s.OperatorNamespace)
if err != nil {
s.Log.Error(err, "failed to get clusterserviceverison of ocs client operator")
return reconcile.Result{}, fmt.Errorf("failed to get clusterserviceversion of ocs client operator: %v", err)
}
name := fmt.Sprintf("storageconsumer-%s", clusterVersion.Spec.ClusterID)
onboardRequest := providerClient.NewOnboardConsumerRequest().
Expand Down Expand Up @@ -506,7 +496,3 @@ func (s *StorageClientReconciler) reconcileClientStatusReporterJob(instance *v1a
}
return reconcile.Result{}, nil
}

func (s *StorageClientReconciler) list(obj client.ObjectList, listOptions ...client.ListOption) error {
return s.Client.List(s.ctx, obj, listOptions...)
}
231 changes: 231 additions & 0 deletions controllers/upgrade_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
Copyright 2024 Red Hat, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
"context"
"fmt"
"strings"

"github.com/red-hat-storage/ocs-client-operator/api/v1alpha1"
"github.com/red-hat-storage/ocs-client-operator/pkg/utils"

"github.com/blang/semver/v4"
"github.com/go-logr/logr"
opv1a1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-lib/conditions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
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"
)

const (
ocsClientOperatorSubscriptionPackageName = "ocs-client-operator"
// TODO(lgangava): there can be "eus" or "fast" or "candidate" supported channels
// and custom channels also might exist, either get this data as part of a configmap
// or cut the name by '-'
subscriptionChannelNamePrefix = "stable-"
controllerName = "upgrade"
upgradeConditionReason = "ConstraintsSatisfied"
dummyPatchVersion = ".99"
)

type UpgradeReconciler struct {
client.Client
ctx context.Context
log logr.Logger

Scheme *runtime.Scheme
OperatorCondition conditions.Condition
OperatorNamespace string
}

func (r *UpgradeReconciler) SetupWithManager(mgr ctrl.Manager) error {
storageClientDesiredVersionPredicate := predicate.Funcs{
CreateFunc: func(_ event.CreateEvent) bool {
return false
},
UpdateFunc: func(e event.UpdateEvent) bool {
if e.ObjectOld == nil || e.ObjectNew == nil {
return false
}

oldObj, _ := e.ObjectOld.(*v1alpha1.StorageClient)
newObj, _ := e.ObjectNew.(*v1alpha1.StorageClient)
if oldObj == nil || newObj == nil {
return false
}

return oldObj.Status.DesiredOperatorVersion != newObj.Status.DesiredOperatorVersion
},
}
ocsClientOperatorSubscriptionPredicate := predicate.Funcs{
CreateFunc: func(_ event.CreateEvent) bool {
return false
},
UpdateFunc: func(e event.UpdateEvent) bool {
if e.ObjectNew == nil {
return false
}

newObj, _ := e.ObjectNew.(*opv1a1.Subscription)
if newObj == nil {
return false
}

return newObj.Spec.Package == ocsClientOperatorSubscriptionPackageName
},
DeleteFunc: func(_ event.DeleteEvent) bool {
return false
},
}
return ctrl.NewControllerManagedBy(mgr).
Named(controllerName).
Watches(&v1alpha1.StorageClient{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(storageClientDesiredVersionPredicate)).
Watches(&opv1a1.Subscription{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(ocsClientOperatorSubscriptionPredicate)).
Complete(r)
}

//+kubebuilder:rbac:groups=operators.coreos.com,resources=subscriptions,verbs=get;list

func (r *UpgradeReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) {
r.ctx = ctx
r.log = log.FromContext(ctx)

r.log.Info("starting reconcile")
result, err := r.reconcilePhases()
if err != nil {
r.log.Error(err, "an error occurred during reconcilePhases")
}
r.log.Info("reconciling completed")

return result, err
}

func (r *UpgradeReconciler) reconcilePhases() (ctrl.Result, error) {
if err := r.reconcileOperatorCondition(); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}

func (r *UpgradeReconciler) reconcileOperatorCondition() error {

var messages []string
currentVersion, err := r.getCurrentOperatorVersion()
if err != nil {
r.log.Error(err, "failed to get current operator version")
messages = append(messages, "failed to get current operator version")
}
desiredVersion, err := r.getDesiredOperatorVersion()
if err != nil {
r.log.Error(err, "failed to get desired operator version")
messages = append(messages, "failed to get desired operator version")
}

if len(messages) > 0 {
return r.setOperatorCondition(metav1.ConditionFalse, strings.Join(messages, "; "))
}

if currentVersion == nil || desiredVersion == nil {
return r.setOperatorCondition(metav1.ConditionTrue, "No errors are reported.")
}

if currentVersion.Major > desiredVersion.Major || currentVersion.Minor > desiredVersion.Minor {
return r.setOperatorCondition(metav1.ConditionFalse, "Current operator version is ahead of desired operator version")
}

return r.setOperatorCondition(metav1.ConditionTrue, "No errors are reported.")
}

func (r *UpgradeReconciler) setOperatorCondition(isUpgradeable metav1.ConditionStatus, message string) error {
return r.OperatorCondition.
Set(r.ctx, isUpgradeable, conditions.WithReason(upgradeConditionReason), conditions.WithMessage(message))
}

// returns current version of the operator based on the subscription
func (r *UpgradeReconciler) getCurrentOperatorVersion() (*semver.Version, error) {
subscriptionList := &opv1a1.SubscriptionList{}
if err := r.list(subscriptionList, client.InNamespace(r.OperatorNamespace)); err != nil {
return nil, err
}

subscription := utils.Find(subscriptionList.Items, func(sub *opv1a1.Subscription) bool {
return sub.Spec.Package == ocsClientOperatorSubscriptionPackageName
})

if subscription == nil {
r.log.Info("subscription of ocs-client-operator doesn't exist")
return nil, nil
}

channel := subscription.Spec.Channel
version, ok := strings.CutPrefix(channel, subscriptionChannelNamePrefix)
if !ok {
return nil, fmt.Errorf("subscription doesn't refer to a stable channel")
}
// appending patchversion to pass semver checks
version += dummyPatchVersion

currentVersion, err := semver.Make(version)
if err != nil {
return nil, err
}

return &currentVersion, nil
}

// returns desired version of the operator based on storageclients
func (r *UpgradeReconciler) getDesiredOperatorVersion() (*semver.Version, error) {
storageClientList := &v1alpha1.StorageClientList{}
if err := r.list(storageClientList); err != nil {
return nil, err
}

if len(storageClientList.Items) == 0 {
r.log.Info("no storageclients exist")
return nil, nil
}

// appending patchversion to pass semver checks
oldest, err := semver.Make(storageClientList.Items[0].Status.DesiredOperatorVersion + dummyPatchVersion)
if err != nil {
return nil, err
}

for idx := range storageClientList.Items {
current, err := semver.Make(storageClientList.Items[idx].Status.DesiredOperatorVersion + dummyPatchVersion)
if err != nil {
return nil, err
}
if current.LE(oldest) {
oldest = current
}
}

return &oldest, nil
}

func (r *UpgradeReconciler) list(list client.ObjectList, opts ...client.ListOption) error {
return r.Client.List(r.ctx, list, opts...)
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ require (

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/blang/semver/v4 v4.0.0
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
Expand All @@ -60,6 +60,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/operator-framework/operator-lib v0.11.1-0.20231020142438-152ee1fb7f83
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ github.com/openshift/api v0.0.0-20231212225112-7cca8a108d7b h1:H7Iu44OPp8/6mp2jS
github.com/openshift/api v0.0.0-20231212225112-7cca8a108d7b/go.mod h1:qNtV0315F+f8ld52TLtPvrfivZpdimOzTi3kn9IVbtU=
github.com/operator-framework/api v0.20.0 h1:A2YCRhr+6s0k3pRJacnwjh1Ue8BqjIGuQ2jvPg9XCB4=
github.com/operator-framework/api v0.20.0/go.mod h1:rXPOhrQ6mMeXqCmpDgt1ALoar9ZlHL+Iy5qut9R99a4=
github.com/operator-framework/operator-lib v0.11.1-0.20231020142438-152ee1fb7f83 h1:TDT89KCBWC5H6oP0w+NL8sQnqR3cXJpr256GnEY/1SA=
github.com/operator-framework/operator-lib v0.11.1-0.20231020142438-152ee1fb7f83/go.mod h1:rLCcQA2GOYpZQg8NbygwQupnuAPLJRetMGG76qvy6so=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down
Loading

0 comments on commit 1edf4ea

Please sign in to comment.