-
Notifications
You must be signed in to change notification settings - Fork 32
OBC Controller and Reconcile #520
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 11 commits
d199af5
5295f7f
e2ebed2
4ba063b
5d62d88
f56369b
831b83e
4b66106
4159530
8f8bd07
1f5e9be
bf2d809
2773353
74a1bab
6780e67
b11eb72
da8406a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| package controller | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "slices" | ||
|
|
||
| "github.com/go-logr/logr" | ||
| nbv1 "github.com/noobaa/noobaa-operator/v5/pkg/apis/noobaa/v1alpha1" | ||
| "github.com/red-hat-storage/ocs-client-operator/api/v1alpha1" | ||
| "github.com/red-hat-storage/ocs-client-operator/pkg/utils" | ||
| storagev1 "k8s.io/api/storage/v1" | ||
| "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/controller/controllerutil" | ||
| "sigs.k8s.io/controller-runtime/pkg/predicate" | ||
| "sigs.k8s.io/controller-runtime/pkg/reconcile" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please format the imports as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I'll look again. |
||
| ) | ||
|
|
||
| const ( | ||
| ObcFinalizer = nbv1.ObjectBucketFinalizer | ||
| ObjectBucketClaimStatusPhaseFailed = "Failed" | ||
| ) | ||
|
|
||
| // OBCReconciler reconciles a ObjectBucketClaim object | ||
| type OBCReconciler struct { | ||
| client.Client | ||
| Scheme *runtime.Scheme | ||
| log logr.Logger | ||
| ctx context.Context | ||
| } | ||
|
|
||
| // SetupWithManager sets up the controller with the Manager | ||
| func (r *OBCReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
| // Reconcile on Create, Delete, and Update when the object is being deleted or when the spec (generation) changes. | ||
| obcPredicate := predicate.Or( | ||
| predicate.GenerationChangedPredicate{}, | ||
| predicate.NewPredicateFuncs(func(obj client.Object) bool { | ||
| return !obj.GetDeletionTimestamp().IsZero() | ||
| }), | ||
| ) | ||
|
|
||
| return ctrl.NewControllerManagedBy(mgr). | ||
| Named("ObjectBucketClaim"). | ||
| For( | ||
| &nbv1.ObjectBucketClaim{}, | ||
| builder.WithPredicates(obcPredicate), | ||
| ). | ||
| Complete(r) | ||
| } | ||
|
|
||
| //+kubebuilder:rbac:groups=objectbucket.io,resources=objectbucketclaims,verbs=get;list;watch;update | ||
| //+kubebuilder:rbac:groups=objectbucket.io,resources=objectbucketclaims/status,verbs=get;update;patch | ||
| //+kubebuilder:rbac:groups=ocs.openshift.io,resources=storageclients,verbs=get | ||
| //+kubebuilder:rbac:groups=storage.k8s.io,resources=storageclasses,verbs=get | ||
|
|
||
| func (r *OBCReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { | ||
| r.ctx = ctx | ||
| r.log = ctrl.LoggerFrom(r.ctx).WithName("OBC") | ||
|
|
||
| r.log.Info("Starting reconcile iteration for OBC", "req", req) | ||
|
|
||
| obc := &nbv1.ObjectBucketClaim{} | ||
| err := r.Get(r.ctx, req.NamespacedName, obc) | ||
| if err != nil { | ||
|
||
| if errors.IsNotFound(err) { | ||
| r.log.Info("OBC resource not found. Ignoring since object must be deleted.") | ||
| return reconcile.Result{}, nil | ||
| } | ||
| r.log.Error(err, "failed to get OBC") | ||
| return reconcile.Result{}, fmt.Errorf("failed to get OBC: %v", err) | ||
| } | ||
|
|
||
| if !obc.GetDeletionTimestamp().IsZero() { | ||
| r.log.Info("OBC deleted", "namespaced/name", client.ObjectKeyFromObject(obc)) | ||
| storageClient, err := r.getStorageClientFromStorageClass(obc.Spec.StorageClassName) | ||
| if err != nil { | ||
| r.log.Error(err, "failed to get StorageClient for OBC delete") | ||
| return reconcile.Result{}, fmt.Errorf("failed to get StorageClient for OBC delete: %v", err) | ||
| } | ||
| if err := r.notifyObcDeleted(storageClient, types.NamespacedName{Namespace: obc.Namespace, Name: obc.Name}); err != nil { | ||
| r.log.Error(err, "failed to notify provider of OBC deletion", "namespaced/name", client.ObjectKeyFromObject(obc)) | ||
| return reconcile.Result{}, fmt.Errorf("failed to in Notify gRPC call of OBC deleted: %v", err) | ||
| } | ||
| if controllerutil.RemoveFinalizer(obc, ObcFinalizer) { | ||
| r.log.Info("removing finalizer from OBC", "namespaced/name", client.ObjectKeyFromObject(obc)) | ||
| if err := r.Update(r.ctx, obc); err != nil { | ||
| r.log.Info("Failed to remove finalizer from OBC", "namespaced/name", client.ObjectKeyFromObject(obc)) | ||
| return reconcile.Result{}, fmt.Errorf("failed to remove finalizer from OBC: %v", err) | ||
| } | ||
| } | ||
| return reconcile.Result{}, nil | ||
| } | ||
|
|
||
| r.log.Info("OBC created", "namespace", obc.Namespace, "name", obc.Name) | ||
| if controllerutil.AddFinalizer(obc, ObcFinalizer) { | ||
| r.log.Info("Finalizer not found for OBC. Adding finalizer", "namespaced/name", client.ObjectKeyFromObject(obc)) | ||
| if err := r.Update(r.ctx, obc); err != nil { | ||
| r.log.Info("Failed to add finalizer to OBC", "namespaced/name", client.ObjectKeyFromObject(obc)) | ||
| return reconcile.Result{}, fmt.Errorf("failed to add finalizer to OBC: %v", err) | ||
| } | ||
| } | ||
| storageClient, err := r.getStorageClientFromStorageClass(obc.Spec.StorageClassName) | ||
| if err != nil { | ||
| r.log.Error(err, "failed to get StorageClient for OBC create") | ||
| obc.Status.Phase = ObjectBucketClaimStatusPhaseFailed | ||
| if statusErr := r.Client.Status().Update(r.ctx, obc); statusErr != nil { | ||
|
||
| r.log.Error(statusErr, "Failed to update OBC status") | ||
| } | ||
| return reconcile.Result{}, fmt.Errorf("failed to get StorageClient for OBC created: %v", err) | ||
| } | ||
| if err := r.notifyObcCreated(storageClient, obc); err != nil { | ||
| r.log.Error(err, "failed to notify provider of OBC creation", "namespaced/name", client.ObjectKeyFromObject(obc)) | ||
| obc.Status.Phase = ObjectBucketClaimStatusPhaseFailed | ||
| if statusErr := r.Client.Status().Update(r.ctx, obc); statusErr != nil { | ||
| r.log.Error(statusErr, "Failed to update OBC status") | ||
| } | ||
| return reconcile.Result{}, fmt.Errorf("failed to in Notify gRPC call of OBC creation: %v", err) | ||
|
|
||
| } | ||
| // Clear Failed status when a retry succeeds | ||
| if obc.Status.Phase == ObjectBucketClaimStatusPhaseFailed { | ||
| obc.Status.Phase = "" | ||
| if statusErr := r.Client.Status().Update(r.ctx, obc); statusErr != nil { | ||
| r.log.Error(statusErr, "Failed to update OBC status after success") | ||
| } | ||
| } | ||
| return reconcile.Result{}, nil | ||
| } | ||
|
|
||
| // getStorageClientFromStorageClass returns the StorageClient that owns the given StorageClass (via ownerReference). | ||
| func (r *OBCReconciler) getStorageClientFromStorageClass(storageClassName string) (*v1alpha1.StorageClient, error) { | ||
| sc := &storagev1.StorageClass{} | ||
| if err := r.Get(r.ctx, types.NamespacedName{Name: storageClassName}, sc); err != nil { | ||
| return nil, fmt.Errorf("get StorageClass %q: %w", storageClassName, err) | ||
| } | ||
| ownerStorageClientIndex := slices.IndexFunc(sc.OwnerReferences, func(owner metav1.OwnerReference) bool { | ||
| return owner.Kind == "StorageClient" | ||
| }) | ||
| if ownerStorageClientIndex == -1 { | ||
| return nil, fmt.Errorf("StorageClass %q has no StorageClient ownerReference", storageClassName) | ||
| } | ||
| storageClient := &v1alpha1.StorageClient{} | ||
| storageClientName := sc.OwnerReferences[ownerStorageClientIndex].Name | ||
| if err := r.Get(r.ctx, types.NamespacedName{Name: storageClientName}, storageClient); err != nil { | ||
| return nil, fmt.Errorf("get StorageClient %q (owner of StorageClass %q): %w", storageClientName, storageClassName, err) | ||
| } | ||
| if storageClient.Status.ConsumerID == "" || storageClient.Spec.StorageProviderEndpoint == "" { | ||
|
||
| return nil, fmt.Errorf("StorageClient %q has no ConsumerID or StorageProviderEndpoint", storageClient.Name) | ||
| } | ||
| return storageClient, nil | ||
| } | ||
|
|
||
| // notifyObcCreated notifies the provider of the creation of an OBC. | ||
| func (r *OBCReconciler) notifyObcCreated(storageClient *v1alpha1.StorageClient, obc *nbv1.ObjectBucketClaim) error { | ||
| pc, err := utils.NewProviderClientForStorageClient(r.ctx, storageClient.Spec.StorageProviderEndpoint) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer pc.Close() | ||
| _, err = pc.NotifyObcCreated(r.ctx, storageClient.Status.ConsumerID, obc) | ||
| if err != nil { | ||
| return fmt.Errorf("NotifyObcCreated: %w", err) | ||
| } | ||
| r.log.Info("Notify of OBC created completed", "namespace", obc.Namespace, "name", obc.Name) | ||
| return nil | ||
| } | ||
|
|
||
| // notifyObcDeleted notifies the provider of the deletion of an OBC. | ||
| func (r *OBCReconciler) notifyObcDeleted(storageClient *v1alpha1.StorageClient, obcDetails types.NamespacedName) error { | ||
| pc, err := utils.NewProviderClientForStorageClient(r.ctx, storageClient.Spec.StorageProviderEndpoint) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer pc.Close() | ||
| _, err = pc.NotifyObcDeleted(r.ctx, storageClient.Status.ConsumerID, obcDetails) | ||
| if err != nil { | ||
| return fmt.Errorf("NotifyObcDeleted: %w", err) | ||
| } | ||
| r.log.Info("Notify of OBC deleted completed", "namespace", obcDetails.Namespace, "name", obcDetails.Name) | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package utils | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| providerClient "github.com/red-hat-storage/ocs-operator/services/provider/api/v4/client" | ||
| ) | ||
|
|
||
| // NewProviderClientForStorageClient creates an OCS provider gRPC client for the given StorageClient. | ||
| func NewProviderClientForStorageClient(ctx context.Context, storageProviderEndpoint string) (*providerClient.OCSProviderClient, error) { | ||
| pc, err := providerClient.NewProviderClient(ctx, storageProviderEndpoint, OcsClientTimeout) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to create provider client with endpoint %v: %w", storageProviderEndpoint, err) | ||
| } | ||
| return pc, nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
even the returned OB/Secret/Configmap should be watched in other namespaces, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The secret and config map should be watched in other namespaces as well, yes -I added the change.
OB is cluster-scoped, so I didn't change.