github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/storage/storageprovider_controller.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package storage 21 22 import ( 23 "context" 24 "sync" 25 26 storagev1 "k8s.io/api/storage/v1" 27 "k8s.io/apimachinery/pkg/api/meta" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/client-go/tools/record" 31 ctrl "sigs.k8s.io/controller-runtime" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 "sigs.k8s.io/controller-runtime/pkg/handler" 34 "sigs.k8s.io/controller-runtime/pkg/log" 35 "sigs.k8s.io/controller-runtime/pkg/reconcile" 36 37 storagev1alpha1 "github.com/1aal/kubeblocks/apis/storage/v1alpha1" 38 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 39 ) 40 41 // StorageProviderReconciler reconciles a StorageProvider object 42 type StorageProviderReconciler struct { 43 client.Client 44 Scheme *runtime.Scheme 45 Recorder record.EventRecorder 46 47 mu sync.Mutex 48 driverDependencies map[string][]string // driver name => list of provider names 49 } 50 51 // +kubebuilder:rbac:groups=storage.kubeblocks.io,resources=storageproviders,verbs=get;list;watch;create;update;patch;delete 52 // +kubebuilder:rbac:groups=storage.kubeblocks.io,resources=storageproviders/status,verbs=get;update;patch 53 // +kubebuilder:rbac:groups=storage.kubeblocks.io,resources=storageproviders/finalizers,verbs=update 54 55 // +kubebuilder:rbac:groups=storage.k8s.io,resources=csidrivers,verbs=get;list;watch 56 57 // Reconcile is part of the main kubernetes reconciliation loop which aims to 58 // move the current state of the cluster closer to the desired state. 59 // 60 // For more details, check Reconcile and its Result here: 61 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile 62 func (r *StorageProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 63 logger := log.FromContext(ctx).WithValues("storageprovider", req.NamespacedName) 64 reqCtx := intctrlutil.RequestCtx{ 65 Ctx: ctx, 66 Req: req, 67 Log: logger, 68 Recorder: r.Recorder, 69 } 70 71 // get provider object 72 provider := &storagev1alpha1.StorageProvider{} 73 if err := r.Get(ctx, req.NamespacedName, provider); err != nil { 74 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to get StorageProvider") 75 } 76 77 // add dependency to CSIDrive 78 r.ensureDependency(provider) 79 80 // handle finalizer 81 res, err := intctrlutil.HandleCRDeletion(reqCtx, r, provider, storageFinalizerName, func() (*ctrl.Result, error) { 82 return nil, r.deleteExternalResources(reqCtx, provider) 83 }) 84 if res != nil { 85 return *res, err 86 } 87 88 // check CSI driver if specified 89 if provider.Spec.CSIDriverName != "" { 90 err := r.checkCSIDriver(reqCtx, provider.Spec.CSIDriverName) 91 if err != nil { 92 // update status for the CSI driver check 93 if updateStatusErr := r.updateStatus(reqCtx, provider, err); updateStatusErr != nil { 94 return intctrlutil.CheckedRequeueWithError(updateStatusErr, reqCtx.Log, 95 "failed to update status") 96 } 97 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, 98 "failed to check CSIDriver %s", provider.Spec.CSIDriverName) 99 } 100 } 101 102 // update status 103 if updateStatusErr := r.updateStatus(reqCtx, provider, nil); updateStatusErr != nil { 104 return intctrlutil.CheckedRequeueWithError(updateStatusErr, reqCtx.Log, 105 "failed to update status") 106 } 107 108 return intctrlutil.Reconciled() 109 } 110 111 func (r *StorageProviderReconciler) updateStatus(reqCtx intctrlutil.RequestCtx, 112 provider *storagev1alpha1.StorageProvider, 113 checkErr error) error { 114 var phase storagev1alpha1.StorageProviderPhase 115 var cond metav1.Condition 116 if checkErr == nil { 117 phase = storagev1alpha1.StorageProviderReady 118 cond = metav1.Condition{ 119 Type: storagev1alpha1.ConditionTypeCSIDriverInstalled, 120 Status: metav1.ConditionTrue, 121 Reason: CSIDriverObjectFound, 122 LastTransitionTime: metav1.Now(), 123 ObservedGeneration: provider.Generation, 124 } 125 } else { 126 phase = storagev1alpha1.StorageProviderNotReady 127 cond = metav1.Condition{ 128 Type: storagev1alpha1.ConditionTypeCSIDriverInstalled, 129 Status: metav1.ConditionUnknown, 130 Reason: CheckCSIDriverFailed, 131 Message: checkErr.Error(), 132 LastTransitionTime: metav1.Now(), 133 ObservedGeneration: provider.Generation, 134 } 135 } 136 137 if phase == provider.Status.Phase { 138 return nil 139 } 140 patch := client.MergeFrom(provider.DeepCopy()) 141 provider.Status.Phase = phase 142 meta.SetStatusCondition(&provider.Status.Conditions, cond) 143 return r.Client.Status().Patch(reqCtx.Ctx, provider, patch) 144 } 145 146 func (r *StorageProviderReconciler) checkCSIDriver(reqCtx intctrlutil.RequestCtx, driverName string) error { 147 // check existence of CSIDriver 148 return r.Client.Get(reqCtx.Ctx, client.ObjectKey{Name: driverName}, &storagev1.CSIDriver{}) 149 } 150 151 func (r *StorageProviderReconciler) ensureDependency(provider *storagev1alpha1.StorageProvider) { 152 if provider.Spec.CSIDriverName == "" { 153 return 154 } 155 r.mu.Lock() 156 defer r.mu.Unlock() 157 if r.driverDependencies == nil { 158 r.driverDependencies = make(map[string][]string) 159 } 160 driverName := provider.Spec.CSIDriverName 161 list := r.driverDependencies[driverName] 162 for _, x := range list { 163 if x == provider.Name { 164 return 165 } 166 } 167 r.driverDependencies[driverName] = append(list, provider.Name) 168 } 169 170 func (r *StorageProviderReconciler) removeDependency(provider *storagev1alpha1.StorageProvider) { 171 if provider.Spec.CSIDriverName == "" { 172 return 173 } 174 r.mu.Lock() 175 defer r.mu.Unlock() 176 list := r.driverDependencies[provider.Spec.CSIDriverName] 177 for i, x := range list { 178 if x == provider.Name { 179 list[i] = list[len(list)-1] 180 r.driverDependencies[provider.Spec.CSIDriverName] = list[:len(list)-1] 181 return 182 } 183 } 184 } 185 186 func (r *StorageProviderReconciler) deleteExternalResources( 187 reqCtx intctrlutil.RequestCtx, provider *storagev1alpha1.StorageProvider) error { 188 r.removeDependency(provider) 189 return nil 190 } 191 192 // SetupWithManager sets up the controller with the Manager. 193 func (r *StorageProviderReconciler) SetupWithManager(mgr ctrl.Manager) error { 194 return ctrl.NewControllerManagedBy(mgr). 195 For(&storagev1alpha1.StorageProvider{}). 196 Watches(&storagev1.CSIDriver{}, 197 handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []reconcile.Request { 198 r.mu.Lock() 199 defer r.mu.Unlock() 200 driverName := object.GetName() 201 list := r.driverDependencies[driverName] 202 var ret []reconcile.Request 203 for _, x := range list { 204 ret = append(ret, reconcile.Request{ 205 NamespacedName: client.ObjectKey{ 206 Name: x, 207 }, 208 }) 209 } 210 return ret 211 })). 212 Complete(r) 213 }