github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/controllers/blockdeviceclaim/blockdeviceclaim_controller.go (about) 1 /* 2 Copyright 2021. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package blockdeviceclaim 18 19 import ( 20 21 "context" 22 "fmt" 23 24 util2 "github.com/openebs/node-disk-manager/pkg/controllers/util" 25 26 "github.com/go-logr/logr" 27 corev1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/errors" 29 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/client-go/tools/record" 32 "k8s.io/client-go/tools/reference" 33 "k8s.io/klog/v2" 34 ctrl "sigs.k8s.io/controller-runtime" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 "sigs.k8s.io/controller-runtime/pkg/reconcile" 37 38 apis "github.com/openebs/node-disk-manager/api/v1alpha1" 39 ndm "github.com/openebs/node-disk-manager/cmd/ndm_daemonset/controller" 40 "github.com/openebs/node-disk-manager/db/kubernetes" 41 "github.com/openebs/node-disk-manager/pkg/select/blockdevice" 42 "github.com/openebs/node-disk-manager/pkg/select/verify" 43 "github.com/openebs/node-disk-manager/pkg/util" 44 ) 45 46 // BlockDeviceClaimReconciler reconciles a BlockDeviceClaim object 47 type BlockDeviceClaimReconciler struct { 48 Client client.Client 49 Log logr.Logger 50 Scheme *runtime.Scheme 51 Recorder record.EventRecorder 52 } 53 54 //+kubebuilder:rbac:groups=openebs.io,resources=blockdeviceclaims,verbs=get;list;watch;create;update;patch;delete 55 //+kubebuilder:rbac:groups=openebs.io,resources=blockdeviceclaims/status,verbs=get;update;patch 56 //+kubebuilder:rbac:groups=openebs.io,resources=blockdeviceclaims/finalizers,verbs=update 57 58 // Reconcile is part of the main kubernetes reconciliation loop which aims to 59 // move the current state of the cluster closer to the desired state. 60 // For more details, check Reconcile and its Result here: 61 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.2/pkg/reconcile 62 func (r *BlockDeviceClaimReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { 63 _ = context.Background() 64 // _ = r.Log.WithValues("blockdeviceclaim", request.NamespacedName) 65 66 // your logic here 67 // Fetch the BlockDeviceClaim instance 68 69 instance := &apis.BlockDeviceClaim{} 70 err := r.Client.Get(context.TODO(), request.NamespacedName, instance) 71 if err != nil { 72 if errors.IsNotFound(err) { 73 // Request object not found, could have been deleted after reconcile request. 74 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 75 // Return and don't requeue 76 return reconcile.Result{}, nil 77 } 78 // Error reading the object - requeue the request. 79 return reconcile.Result{}, err 80 } 81 82 // check if reconciliation is disabled for this resource 83 if IsReconcileDisabled(instance) { 84 return reconcile.Result{}, nil 85 } 86 87 switch instance.Status.Phase { 88 case apis.BlockDeviceClaimStatusPending: 89 fallthrough 90 case apis.BlockDeviceClaimStatusEmpty: 91 klog.Infof("BDC %s claim phase is: %s", instance.Name, instance.Status.Phase) 92 // claim the BD only if deletion time stamp is not set. 93 // since BDC can now have multiple finalizers, we should not claim a 94 // BD if its deletiontime stamp is set. 95 if instance.DeletionTimestamp.IsZero() { 96 err := r.claimDeviceForBlockDeviceClaim(instance) 97 if err != nil { 98 klog.Errorf("%s failed to claim: %v", instance.Name, err) 99 return reconcile.Result{}, err 100 } 101 } 102 case apis.BlockDeviceClaimStatusInvalidCapacity: 103 // migrating state to Pending if in InvalidCapacity state. 104 // The InvalidCapacityState is deprecated and pending will be used. 105 // InvalidCapacity will be the reason for why the BDC is in Pending state. 106 instance.Status.Phase = apis.BlockDeviceClaimStatusPending 107 err := r.updateClaimStatus(apis.BlockDeviceClaimStatusPending, instance) 108 if err != nil { 109 klog.Errorf("error in updating phase to pending from invalid capacity for %s: %v", instance.Name, err) 110 } 111 klog.Infof("%s claim phase is: %s", instance.Name, instance.Status.Phase) 112 case apis.BlockDeviceClaimStatusDone: 113 err := r.FinalizerHandling(instance) 114 if err != nil { 115 klog.Errorf("Finalizer handling failed for %s: %v", instance.Name, err) 116 return reconcile.Result{}, err 117 } 118 } 119 120 return ctrl.Result{}, nil 121 } 122 123 // claimDeviceForBlockDeviceClaim is created, try to determine blockdevice which is 124 // free and has size equal/greater than BlockDeviceClaim request. 125 func (r *BlockDeviceClaimReconciler) claimDeviceForBlockDeviceClaim(instance *apis.BlockDeviceClaim) error { 126 127 config := blockdevice.NewConfig(&instance.Spec, r.Client) 128 129 // check for capacity only in auto selection 130 if !config.ManualSelection { 131 // perform verification of the claim, like capacity 132 // Get the capacity requested in the claim 133 _, err := verify.GetRequestedCapacity(instance.Spec.Resources.Requests) 134 if err != nil { 135 r.Recorder.Eventf(instance, corev1.EventTypeWarning, "InvalidCapacity", "Invalid Capacity requested") 136 //Update deviceClaim CR with pending status 137 instance.Status.Phase = apis.BlockDeviceClaimStatusPending 138 err1 := r.updateClaimStatus(instance.Status.Phase, instance) 139 if err1 != nil { 140 klog.Errorf("%s requested an invalid capacity: %v", instance.Name, err1) 141 return err1 142 } 143 klog.Infof("%s set to Pending due to invalid capacity request", instance.Name) 144 return err 145 } 146 } 147 148 // create selector from the label selector given in BDC spec. 149 selector := generateSelector(*instance) 150 151 // get list of block devices. 152 bdList, err := r.getListofDevices(selector) 153 if err != nil { 154 return err 155 } 156 157 selectedDevice, err := config.Filter(bdList) 158 if err != nil { 159 klog.Errorf("Error selecting device for %s: %v", instance.Name, err) 160 r.Recorder.Eventf(instance, corev1.EventTypeWarning, "SelectionFailed", err.Error()) 161 instance.Status.Phase = apis.BlockDeviceClaimStatusPending 162 } else { 163 instance.Spec.BlockDeviceName = selectedDevice.Name 164 instance.Status.Phase = apis.BlockDeviceClaimStatusDone 165 err = r.claimBlockDevice(selectedDevice, instance) 166 if err != nil { 167 return err 168 } 169 r.Recorder.Eventf(selectedDevice, corev1.EventTypeNormal, "BlockDeviceClaimed", "BlockDevice claimed by %v", instance.Name) 170 r.Recorder.Eventf(instance, corev1.EventTypeNormal, "BlockDeviceClaimed", "BlockDevice: %v claimed", instance.Spec.BlockDeviceName) 171 } 172 173 return r.updateClaimStatus(instance.Status.Phase, instance) 174 } 175 176 // FinalizerHandling removes the finalizer from the claim resource 177 func (r *BlockDeviceClaimReconciler) FinalizerHandling(instance *apis.BlockDeviceClaim) error { 178 179 if instance.ObjectMeta.DeletionTimestamp.IsZero() { 180 return nil 181 } 182 // The object is being deleted 183 // Check if the BDC has only NDM finalizer present. If yes, it means that the BDC 184 // was deleted by the owner itself, and NDM can proceed with releasing the BD and 185 // removing the NDM finalizer 186 if len(instance.ObjectMeta.Finalizers) == 1 && 187 util.Contains(instance.ObjectMeta.Finalizers, util2.BlockDeviceClaimFinalizer) { 188 // Finalizer is set, lets handle external dependency 189 if err := r.releaseClaimedBlockDevice(instance); err != nil { 190 klog.Errorf("Error releasing claimed block device %s from %s: %v", 191 instance.Spec.BlockDeviceName, instance.Name, err) 192 return err 193 } 194 r.Recorder.Eventf(instance, corev1.EventTypeNormal, "BlockDeviceReleased", "BlockDevice: %v is released", instance.Spec.BlockDeviceName) 195 196 // Remove finalizer from list and update it. 197 instance.ObjectMeta.Finalizers = util.RemoveString(instance.ObjectMeta.Finalizers, util2.BlockDeviceClaimFinalizer) 198 if err := r.Client.Update(context.TODO(), instance); err != nil { 199 klog.Errorf("Error removing finalizer from %s", instance.Name) 200 r.Recorder.Eventf(instance, corev1.EventTypeWarning, "UpdateOperationFailed", "Unable to remove Finalizer, due to error: %v", err.Error()) 201 return err 202 } 203 } 204 205 return nil 206 } 207 208 func (r *BlockDeviceClaimReconciler) updateClaimStatus(phase apis.DeviceClaimPhase, 209 instance *apis.BlockDeviceClaim) error { 210 switch phase { 211 case apis.BlockDeviceClaimStatusDone: 212 instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, util2.BlockDeviceClaimFinalizer) 213 r.Recorder.Eventf(instance, corev1.EventTypeNormal, "BlockDeviceClaimBound", "BlockDeviceClaim is bound to %v", instance.Spec.BlockDeviceName) 214 215 } 216 // Update BlockDeviceClaim CR 217 err := r.Client.Update(context.TODO(), instance) 218 if err != nil { 219 return fmt.Errorf("error updating status of BDC : %s, %v", instance.ObjectMeta.Name, err) 220 } 221 222 return nil 223 } 224 225 // isDeviceRequestedByThisDeviceClaim checks whether a claimed block device belongs to the given BDC 226 func (r *BlockDeviceClaimReconciler) isDeviceRequestedByThisDeviceClaim( 227 instance *apis.BlockDeviceClaim, item apis.BlockDevice) bool { 228 229 if item.Status.ClaimState != apis.BlockDeviceClaimed { 230 return false 231 232 } 233 234 if item.Spec.ClaimRef.Name != instance.ObjectMeta.Name { 235 return false 236 } 237 238 if item.Spec.ClaimRef.UID != instance.ObjectMeta.UID { 239 return false 240 } 241 242 if item.Spec.ClaimRef.Kind != instance.TypeMeta.Kind { 243 return false 244 } 245 return true 246 } 247 248 // releaseClaimedBlockDevice releases the block device claimed by this BlockDeviceClaim 249 func (r *BlockDeviceClaimReconciler) releaseClaimedBlockDevice( 250 instance *apis.BlockDeviceClaim) error { 251 252 klog.Infof("Releasing claimed block device %s from %s", instance.Spec.BlockDeviceName, instance.Name) 253 254 //Get BlockDevice list on all nodes 255 //empty selector is used to select everything. 256 selector := &v1.LabelSelector{} 257 bdList, err := r.getListofDevices(selector) 258 if err != nil { 259 return err 260 } 261 262 // Check if same deviceclaim holding the ObjRef 263 var claimedBd *apis.BlockDevice 264 for i := range bdList.Items { 265 // Found a blockdevice ObjRef with BlockDeviceClaim, Clear 266 // ObjRef and mark blockdevice released in etcd 267 if r.isDeviceRequestedByThisDeviceClaim(instance, bdList.Items[i]) { 268 claimedBd = &bdList.Items[i] 269 break 270 } 271 } 272 // This case occurs when a claimed BD is manually deleted by removing the finalizer. 273 // If this check is not performed, the NDM operator will continuously crash, because it 274 // will try to release a non existent BD. 275 if claimedBd == nil { 276 r.Recorder.Eventf(instance, corev1.EventTypeWarning, "BlockDeviceNotFound", "BlockDevice %s not found for releasing", instance.Spec.BlockDeviceName) 277 klog.Errorf("could not find blockdevice for claim: %s", instance.Name) 278 return fmt.Errorf("blockdevice: %s not found for releasing from bdc: %s", instance.Spec.BlockDeviceName, instance.Name) 279 } 280 281 dvr := claimedBd.DeepCopy() 282 dvr.Spec.ClaimRef = nil 283 dvr.Status.ClaimState = apis.BlockDeviceReleased 284 285 err = r.Client.Update(context.TODO(), dvr) 286 if err != nil { 287 klog.Errorf("Error updating ClaimRef of %s: %v", dvr.Name, err) 288 return err 289 } 290 r.Recorder.Eventf(dvr, corev1.EventTypeNormal, "BlockDeviceCleanUpInProgress", "Released from BDC: %v", instance.Name) 291 292 return nil 293 } 294 295 // claimBlockDevice is used to claim the passed on blockdevice 296 func (r *BlockDeviceClaimReconciler) claimBlockDevice(bd *apis.BlockDevice, instance *apis.BlockDeviceClaim) error { 297 claimRef, err := reference.GetReference(r.Scheme, instance) 298 if err != nil { 299 return fmt.Errorf("error getting claim reference for BDC:%s, %v", instance.ObjectMeta.Name, err) 300 } 301 // add finalizer to BlockDevice to prevent accidental deletion of BD 302 bd.Finalizers = append(bd.Finalizers, util2.BlockDeviceFinalizer) 303 bd.Spec.ClaimRef = claimRef 304 bd.Status.ClaimState = apis.BlockDeviceClaimed 305 err = r.Client.Update(context.TODO(), bd) 306 if err != nil { 307 return fmt.Errorf("error while updating BD:%s, %v", bd.ObjectMeta.Name, err) 308 } 309 klog.Infof("%s claimed by %s", bd.Name, instance.Name) 310 return nil 311 } 312 313 // GetBlockDevice get block device resource from etcd 314 func (r *BlockDeviceClaimReconciler) GetBlockDevice(name string) (*apis.BlockDevice, error) { 315 bd := &apis.BlockDevice{} 316 err := r.Client.Get(context.TODO(), 317 client.ObjectKey{Namespace: "", Name: name}, bd) 318 319 if err != nil { 320 return nil, err 321 } 322 return bd, nil 323 } 324 325 // getListofDevices gets the list of block devices on the node to which BlockDeviceClaim is made 326 // TODO: 327 // ListBlockDeviceResource in package cmd/ndm_daemonset/controller has the same functionality. 328 // Need to merge these 2 functions. 329 func (r *BlockDeviceClaimReconciler) getListofDevices(selector *v1.LabelSelector) (*apis.BlockDeviceList, error) { 330 331 //Initialize a deviceList object. 332 listBlockDevice := &apis.BlockDeviceList{ 333 TypeMeta: v1.TypeMeta{ 334 Kind: apis.BlockDeviceResourceKind, 335 APIVersion: apis.GroupVersion.Version, 336 }, 337 } 338 339 opts := &client.ListOptions{} 340 341 if sel, err := v1.LabelSelectorAsSelector(selector); err != nil { 342 // if conversion of selector errors out, the list call will be errored 343 return nil, err 344 } else { 345 opts.LabelSelector = sel 346 } 347 348 //Fetch deviceList with matching criteria 349 err := r.Client.List(context.TODO(), listBlockDevice, opts) 350 if err != nil { 351 return nil, err 352 } 353 354 return listBlockDevice, nil 355 } 356 357 // IsReconcileDisabled is used to check if reconciliation is disabled for 358 // BlockDeviceClaim 359 func IsReconcileDisabled(bdc *apis.BlockDeviceClaim) bool { 360 return bdc.Annotations[ndm.OpenEBSReconcile] == "false" 361 } 362 363 // generateSelector creates the label selector for BlockDevices from 364 // the BlockDeviceClaim spec 365 func generateSelector(bdc apis.BlockDeviceClaim) *v1.LabelSelector { 366 var hostName string 367 // get the hostname 368 if len(bdc.Spec.HostName) != 0 { 369 hostName = bdc.Spec.HostName 370 } 371 // the hostname in NodeAttribute will override the hostname in spec, since spec.hostName 372 // will be deprecated shortly 373 if len(bdc.Spec.BlockDeviceNodeAttributes.HostName) != 0 { 374 hostName = bdc.Spec.BlockDeviceNodeAttributes.HostName 375 } 376 377 // the hostname label is added into the user given list of labels. If the user hasn't 378 // given any selector, then the selector object is initialized. 379 selector := bdc.Spec.Selector.DeepCopy() 380 if selector == nil { 381 selector = &v1.LabelSelector{} 382 } 383 if selector.MatchLabels == nil { 384 selector.MatchLabels = make(map[string]string) 385 } 386 387 // if any hostname is provided, add it to selector 388 if len(hostName) != 0 { 389 selector.MatchLabels[kubernetes.KubernetesHostNameLabel] = hostName 390 } 391 return selector 392 } 393 394 func (r *BlockDeviceClaimReconciler) SetupWithManager(mgr ctrl.Manager) error { 395 396 return ctrl.NewControllerManagedBy(mgr). 397 For(&apis.BlockDeviceClaim{}). 398 Owns(&apis.BlockDeviceClaim{}). 399 Complete(r) 400 }