Skip to content

Commit

Permalink
controllers: new controller for maintenance mode
Browse files Browse the repository at this point in the history
Signed-off-by: Rewant Soni <[email protected]>
  • Loading branch information
rewantsoni committed Nov 20, 2024
1 parent 64a0317 commit 67f5431
Show file tree
Hide file tree
Showing 16 changed files with 845 additions and 54 deletions.
11 changes: 11 additions & 0 deletions api/v1alpha1/storageclient_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ const (
StorageClientFailed storageClientPhase = "Failed"
)

type maintenanceModePhase string

const (
MaintenanceModeUnknown maintenanceModePhase = "Unknown"
MaintenanceModeError maintenanceModePhase = "Error"
MaintenanceModeProgressing maintenanceModePhase = "Progressing"
MaintenanceModeCompleted maintenanceModePhase = "Completed"
)

// StorageClientSpec defines the desired state of StorageClient
type StorageClientSpec struct {
// StorageProviderEndpoint holds info to establish connection with the storage providing cluster.
Expand All @@ -50,6 +59,8 @@ type StorageClientSpec struct {
type StorageClientStatus struct {
Phase storageClientPhase `json:"phase,omitempty"`

MaintenanceModePhase maintenanceModePhase `json:"maintenanceModePhase,omitempty"`

// ConsumerID will hold the identity of this cluster inside the attached provider cluster
ConsumerID string `json:"id,omitempty"`
}
Expand Down
12 changes: 12 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func main() {
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
OperatorNamespace: utils.GetOperatorNamespace(),
AvailableCrds: availCrds,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "StorageClient")
os.Exit(1)
Expand Down Expand Up @@ -208,6 +209,17 @@ func main() {
os.Exit(1)
}

if availCrds[controller.MaintenanceModeCRDName] {
if err = (&controller.MaintenanceModeReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
OperatorNamespace: utils.GetOperatorNamespace(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MaintenanceMode")
os.Exit(1)
}
}

setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
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 @@ -65,6 +65,8 @@ spec:
description: ConsumerID will hold the identity of this cluster inside
the attached provider cluster
type: string
maintenanceModePhase:
type: string
phase:
type: string
type: object
Expand Down
25 changes: 25 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,31 @@ rules:
- list
- update
- watch
- apiGroups:
- ramendr.openshift.io
resources:
- maintenancemodes
verbs:
- create
- delete
- get
- list
- update
- watch
- apiGroups:
- ramendr.openshift.io
resources:
- maintenancemodes/finalizers
verbs:
- update
- apiGroups:
- ramendr.openshift.io
resources:
- maintenancemodes/status
verbs:
- get
- patch
- update
- apiGroups:
- security.openshift.io
resources:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.5
replace (
github.com/portworx/sched-ops => github.com/portworx/sched-ops v0.20.4-openstorage-rc3 // required by Rook v1.12
github.com/red-hat-storage/ocs-client-operator/api => ./api
github.com/red-hat-storage/ocs-operator/services/provider/api/v4 => github.com/rewantsoni/ocs-operator/services/provider/api/v4 v4.0.0-20241119181812-8de4026c1334
vbom.ml/util => github.com/fvbommel/util v0.0.0-20180919145318-efcd4e0f9787
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/ramendr/ramen/api v0.0.0-20241001141243-29d6f22ad237 h1:ig6ePD0yopC5Qi5BRmhsIsKaOkdsGXTSmG3HTYIpquo=
github.com/ramendr/ramen/api v0.0.0-20241001141243-29d6f22ad237/go.mod h1:nO6VM/+PEhcPGyFIQJdhY6ip822cA61PAy/s6IjenAA=
github.com/red-hat-storage/ocs-operator/services/provider/api/v4 v4.0.0-20241015071140-98c8184c6eec h1:M64BdwKMV3jKxcRsZiaGbRKsvlbhRGVZgcb4V/MFeWk=
github.com/red-hat-storage/ocs-operator/services/provider/api/v4 v4.0.0-20241015071140-98c8184c6eec/go.mod h1:t9GJk69TGXABBF8fFPB+ImpbA9mJyRS86wW6+Qn8xHo=
github.com/rewantsoni/ocs-operator/services/provider/api/v4 v4.0.0-20241119181812-8de4026c1334 h1:8/XNNGlwLOY5z0UIMkTlZmQUyFyNCgbg+BsTf3pSLX0=
github.com/rewantsoni/ocs-operator/services/provider/api/v4 v4.0.0-20241119181812-8de4026c1334/go.mod h1:t9GJk69TGXABBF8fFPB+ImpbA9mJyRS86wW6+Qn8xHo=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
Expand Down
237 changes: 237 additions & 0 deletions internal/controller/maintenancemode_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package controller

import (
"context"
"fmt"
"github.com/go-logr/logr"
ramenv1alpha1 "github.com/ramendr/ramen/api/v1alpha1"
"github.com/red-hat-storage/ocs-client-operator/api/v1alpha1"
providerclient "github.com/red-hat-storage/ocs-operator/services/provider/api/v4/client"
storagev1 "k8s.io/api/storage/v1"
kerrors "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"
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/controller/controllerutil"
"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"
"time"
)

const (
maintenanceModeFinalizer = "ocs-client-operator.ocs.openshift.io/maintenance-mode"
ramenReplicationIdLabel = "ramendr.openshift.io/replicationID"

Check failure on line 28 in internal/controller/maintenancemode_controller.go

View workflow job for this annotation

GitHub Actions / golangci-lint

var-naming: const ramenReplicationIdLabel should be ramenReplicationIDLabel (revive)
MaintenanceModeCRDName = "maintenancemodes.ramendr.openshift.io"
)

// MaintenanceModeReconciler reconciles a ClusterVersion object
type MaintenanceModeReconciler struct {
client.Client
OperatorNamespace string
Scheme *runtime.Scheme

log logr.Logger
ctx context.Context
maintenanceMode *ramenv1alpha1.MaintenanceMode
storageClass *storagev1.StorageClass
storageClient *v1alpha1.StorageClient
}

// SetupWithManager sets up the controller with the Manager.
func (r *MaintenanceModeReconciler) SetupWithManager(mgr ctrl.Manager) error {
generationChangePredicate := predicate.GenerationChangedPredicate{}
bldr := ctrl.NewControllerManagedBy(mgr).
For(&ramenv1alpha1.MaintenanceMode{}, builder.WithPredicates(generationChangePredicate)).
Watches(&storagev1.StorageClass{}, &handler.EnqueueRequestForObject{}).
Watches(&v1alpha1.StorageClient{}, &handler.EnqueueRequestForObject{})

return bldr.Complete(r)
}

//+kubebuilder:rbac:groups=ramendr.openshift.io,resources=maintenancemodes,verbs=get;list;update;create;watch;delete
//+kubebuilder:rbac:groups=ramendr.openshift.io,resources=maintenancemodes/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=ramendr.openshift.io,resources=maintenancemodes/finalizers,verbs=update
//+kubebuilder:rbac:groups=ocs.openshift.io,resources=storageclients,verbs=get;list;watch
//+kubebuilder:rbac:groups=storage.k8s.io,resources=storageclasses,verbs=get;list;watch

func (r *MaintenanceModeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
r.ctx = ctx
r.log = log.FromContext(ctx, "MaintenanceMode", req)
r.log.Info("Reconciling MaintenanceMode")

r.maintenanceMode = &ramenv1alpha1.MaintenanceMode{}
r.maintenanceMode.Name = req.Name
if err := r.get(r.maintenanceMode); err != nil {
if kerrors.IsNotFound(err) {
r.log.Info("Maintenance Mode resource not found. Ignoring since object might be deleted.")
return reconcile.Result{}, nil
}
r.log.Error(err, "failed to get the Maintenance Mode")
return reconcile.Result{}, err
}

err := r.findStorageClassForMaintenanceMode()
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to find storageClass for maintenance mode: %v ", err)
}

// If no storageClass for the targetID found, exit
if r.storageClass == nil {
r.log.Info("no storage class found for the maintenance mode")
return reconcile.Result{}, nil
}

err = r.findStorageClientLinkedWithStorageClass()
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to find storageClient for maintenance mode: %v ", err)
}

result, reconcileErr := r.reconcileStates()

// Apply status changes to the StorageClient
statusErr := r.Client.Status().Update(ctx, r.storageClient)
if statusErr != nil {
r.log.Error(statusErr, "Failed to update MaintenanceMode status.")
}
if reconcileErr != nil {
err = reconcileErr
} else if statusErr != nil {
err = statusErr
}
return result, err
}

func (r *MaintenanceModeReconciler) reconcileStates() (ctrl.Result, error) {
providerClient, err := providerclient.NewProviderClient(
r.ctx,
r.storageClient.Spec.StorageProviderEndpoint,
10*time.Second,
)
if err != nil {
return reconcile.Result{}, fmt.Errorf("failed to create provider client with endpoint %v: %v", r.storageClient.Spec.StorageProviderEndpoint, err)
}
// Close client-side connections.
defer providerClient.Close()

if r.maintenanceMode.GetDeletionTimestamp().IsZero() {

//ensure finalizer
if controllerutil.AddFinalizer(r.maintenanceMode, maintenanceModeFinalizer) {
r.log.Info("finalizer missing on the Maintenance Mode resource, adding...")
if err := r.Client.Update(r.ctx, r.maintenanceMode); err != nil {
return ctrl.Result{}, err
}
}

if r.storageClient.Status.MaintenanceModePhase == "" {
_, err := providerClient.RequestMaintenanceMode(r.ctx, r.storageClient.Status.ConsumerID, modesAsSliceOfString(r.maintenanceMode.Spec.Modes))
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to Request maintenance mode: %v", err)
}
r.maintenanceMode.Status.State = ramenv1alpha1.MModeStateUnknown
}
r.maintenanceMode.Status.State = ramenv1alpha1.MModeState(r.storageClient.Status.MaintenanceModePhase)
r.maintenanceMode.Status.ObservedGeneration = r.maintenanceMode.Generation
meta.SetStatusCondition(&r.maintenanceMode.Status.Conditions,
metav1.Condition{
Type: string(ramenv1alpha1.MModeConditionFailoverActivated),
ObservedGeneration: r.maintenanceMode.Generation,
Reason: string(r.storageClient.Status.MaintenanceModePhase),
Status: metav1.ConditionTrue,
},
)
} else {
// deletion phase
_, err := providerClient.RequestMaintenanceMode(r.ctx, r.storageClient.Status.ConsumerID, []string{})
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to Request maintenance mode: %v", err)
}
if r.storageClient.Status.MaintenanceModePhase != "" {
r.maintenanceMode.Status.State = ramenv1alpha1.MModeState(r.storageClient.Status.MaintenanceModePhase)
r.maintenanceMode.Status.ObservedGeneration = r.maintenanceMode.Generation
meta.SetStatusCondition(&r.maintenanceMode.Status.Conditions,
metav1.Condition{
Type: string(ramenv1alpha1.MModeConditionFailoverActivated),
ObservedGeneration: r.maintenanceMode.Generation,
Reason: string(r.storageClient.Status.MaintenanceModePhase),
Status: metav1.ConditionTrue,
},
)
return ctrl.Result{}, fmt.Errorf("wating for maintenance mode to be deleted")

Check failure on line 165 in internal/controller/maintenancemode_controller.go

View workflow job for this annotation

GitHub Actions / codespell

wating ==> waiting
}
//remove finalizer
if controllerutil.RemoveFinalizer(r.maintenanceMode, maintenanceModeFinalizer) {
if err := r.Client.Update(r.ctx, r.maintenanceMode); err != nil {
return ctrl.Result{}, err
}
r.log.Info("finalizer removed successfully")
}
}
return ctrl.Result{}, nil
}

func modesAsSliceOfString(modes []ramenv1alpha1.MMode) []string {
stringModes := make([]string, len(modes))
for i, mode := range modes {
stringModes[i] = string(mode)
}
return stringModes
}

func (r *MaintenanceModeReconciler) findStorageClassForMaintenanceMode() error {
storageClassList := &storagev1.StorageClassList{}

err := r.list(storageClassList)
if err != nil {
r.log.Error(err, "unable to list storage classes")
return err
}

for i := range storageClassList.Items {
storageClass := storageClassList.Items[i]
if storageClass.GetAnnotations()[ramenReplicationIdLabel] == r.maintenanceMode.Spec.TargetID {
r.storageClass = &storageClassList.Items[i]
return nil
}
}
return fmt.Errorf("failed to find storage class for maintenance mode %s", r.maintenanceMode.Spec.TargetID)
}

func (r *MaintenanceModeReconciler) findStorageClientLinkedWithStorageClass() error {
r.storageClient = &v1alpha1.StorageClient{}
val, ok := r.storageClass.GetAnnotations()[storageClientAnnotation]
if !ok {
return fmt.Errorf("no storage client linked to storage class %s", r.storageClass.Name)
}

r.storageClient.Name = val
err := r.get(r.storageClient)
if err != nil {
return fmt.Errorf("failed to get the storage client: %v", err)
}
return nil
}

func (r *MaintenanceModeReconciler) get(obj client.Object, opts ...client.GetOption) error {
return r.Get(r.ctx, client.ObjectKeyFromObject(obj), obj, opts...)
}

func (r *MaintenanceModeReconciler) list(obj client.ObjectList, opts ...client.ListOption) error {
return r.List(r.ctx, obj, opts...)
}

func (r *MaintenanceModeReconciler) update(obj client.Object, opts ...client.UpdateOption) error {

Check failure on line 228 in internal/controller/maintenancemode_controller.go

View workflow job for this annotation

GitHub Actions / golangci-lint

func `(*MaintenanceModeReconciler).update` is unused (unused)
return r.Update(r.ctx, obj, opts...)
}

func (r *MaintenanceModeReconciler) delete(obj client.Object, opts ...client.DeleteOption) error {

Check failure on line 232 in internal/controller/maintenancemode_controller.go

View workflow job for this annotation

GitHub Actions / golangci-lint

func `(*MaintenanceModeReconciler).delete` is unused (unused)
if err := r.Delete(r.ctx, obj, opts...); err != nil && !kerrors.IsNotFound(err) {
return err
}
return nil
}
8 changes: 5 additions & 3 deletions internal/controller/storageclaim_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ import (
)

const (
storageClaimFinalizer = "storageclaim.ocs.openshift.io"
storageClaimAnnotation = "ocs.openshift.io/storageclaim"
keyRotationAnnotation = "keyrotation.csiaddons.openshift.io/schedule"
storageClaimFinalizer = "storageclaim.ocs.openshift.io"
storageClaimAnnotation = "ocs.openshift.io/storageclaim"
storageClientAnnotation = "ocs.openshift.io/storageclient"
keyRotationAnnotation = "keyrotation.csiaddons.openshift.io/schedule"

pvClusterIDIndexName = "index:persistentVolumeClusterID"
vscClusterIDIndexName = "index:volumeSnapshotContentCSIDriver"
Expand Down Expand Up @@ -392,6 +393,7 @@ func (r *StorageClaimReconciler) reconcilePhases() (reconcile.Result, error) {
storageClass = r.getCephRBDStorageClass(data)
}
utils.AddAnnotation(storageClass, storageClaimAnnotation, r.storageClaim.Name)
utils.AddAnnotation(storageClass, storageClientAnnotation, r.storageClient.Name)
err = r.createOrReplaceStorageClass(storageClass)
if err != nil {
return reconcile.Result{}, fmt.Errorf("failed to create or update StorageClass: %s", err)
Expand Down
Loading

0 comments on commit 67f5431

Please sign in to comment.