diff --git a/internal/controller/driver_controller.go b/internal/controller/driver_controller.go index f31633b6..0edc8768 100644 --- a/internal/controller/driver_controller.go +++ b/internal/controller/driver_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "encoding/json" "errors" "fmt" "maps" @@ -27,7 +28,10 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -46,6 +50,8 @@ import ( //+kubebuilder:rbac:groups=csi.ceph.io,resources=drivers/finalizers,verbs=update //+kubebuilder:rbac:groups=csi.ceph.io,resources=operatorconfigs,verbs=get;list;watch +const ownerRefAnnotationKey = "csi.ceph.io/ownerref" + // A regexp used to parse driver short name and driver type from the // driver's full name var nameRegExp, _ = regexp.Compile(`^(.*)\.(rbd|cephfs|nfs)\.csi\.ceph\.com$`) @@ -95,8 +101,8 @@ func (r *DriverReconciler) SetupWithManager(mgr ctrl.Manager) error { }, ) - // Enqueue an event for all existing drivers, used to trigger a reconcile for all drivers - // whenever the driver default configuration changes + // Enqueue a reconcile request for all existing drivers, used to trigger a reconcile + // for all drivers whenever the driver default configuration changes enqueueAllDrivers := handler.EnqueueRequestsFromMapFunc( func(_ context.Context, obj client.Object) []reconcile.Request { driverList := csiv1a1.DriverList{} @@ -112,9 +118,29 @@ func (r *DriverReconciler) SetupWithManager(mgr ctrl.Manager) error { }, ) + // Enqueue a reconcile request based on an annotation marking a soft ownership + enqueueFromOwnerRefAnnotation := handler.EnqueueRequestsFromMapFunc( + func(_ context.Context, obj client.Object) []reconcile.Request { + ownerRef := obj.GetAnnotations()[ownerRefAnnotationKey] + if ownerRef == "" { + return nil + } + + ownerObjKey := client.ObjectKey{} + if err := json.Unmarshal([]byte(ownerRef), &ownerObjKey); err != nil { + return nil + } + + return []reconcile.Request{{ + NamespacedName: ownerObjKey, + }} + }, + ) + return ctrl.NewControllerManagedBy(mgr). For(&csiv1a1.Driver{}). Watches(&csiv1a1.OperatorConfig{}, enqueueAllDrivers, driverDefaultsPredicate). + Watches(&storagev1.CSIDriver{}, enqueueFromOwnerRefAnnotation). Complete(r) } @@ -145,10 +171,10 @@ func (r *driverReconcile) reconcile() (ctrl.Result, error) { // Concurrently reconcile different aspects of the clusters actual state to meet // the desired state defined on the driver object errChan := utils.RunConcurrently( - r.upsertPluginDeamonSet, - r.upsertProvisionerDeployment, - r.upsertK8sCSIDriver, - r.upsertLivnessService, + r.reconcileK8sCsiDriver, + r.reconcilePluginDeamonSet, + r.reconcileProvisionerDeployment, + r.reconcileLivnessService, ) // Check if any reconcilatin error where raised during the concurrent execution @@ -163,6 +189,16 @@ func (r *driverReconcile) reconcile() (ctrl.Result, error) { } func (r *driverReconcile) LoadAndValidateDesiredState() error { + // Validate that the requested name for the CSI driver isn't already claimed by an existing CSI driver + // (Can happen if a driver with an identical name was created in a different namespace) + if err := r.Get(r.ctx, client.ObjectKey{Name: r.driver.Name}, &storagev1.CSIDriver{}); err == nil { + r.log.Error(err, "Desired name already in use by a different CSI Driver", "name", r.driver.Name) + return nil + } else if !k8serrors.IsNotFound(err) { + r.log.Error(err, "Failed to query the existence of a CSI Driver", "name", r.driver.Name) + return err + } + // Load operator configuration resource opConfig := csiv1a1.OperatorConfig{} opConfig.Name = operatorConfigName @@ -208,19 +244,72 @@ func (r *driverReconcile) LoadAndValidateDesiredState() error { return nil } -func (r *driverReconcile) upsertPluginDeamonSet() error { +func (r *driverReconcile) reconcileK8sCsiDriver() error { + existingCsiDriver := &storagev1.CSIDriver{} + existingCsiDriver.Name = r.driver.Name + + log := r.log.WithValues("driverName", existingCsiDriver.Name) + log.Info("Reconciling CSI Driver resource") + + if err := r.Get(r.ctx, client.ObjectKeyFromObject(existingCsiDriver), existingCsiDriver); client.IgnoreNotFound(err) != nil { + log.Error(err, "Failed to load CSI Driver resource") + return err + } + + desiredCsiDriver := existingCsiDriver.DeepCopy() + desiredCsiDriver.Spec = storagev1.CSIDriverSpec{ + AttachRequired: r.driver.Spec.AttachRequired, + PodInfoOnMount: ptr.To(false), + FSGroupPolicy: &r.driver.Spec.FsGroupPolicy, + } + + ownerObjKey := client.ObjectKeyFromObject(&r.driver) + if bytes, err := json.Marshal(ownerObjKey); err != nil { + log.Error( + err, + "Failed to JSON marshal owner obj key for CSI driver resource", + "ownerObjKey", + ownerObjKey, + ) + return err + } else { + utils.AddAnnotation(desiredCsiDriver, ownerRefAnnotationKey, string(bytes)) + } + + if existingCsiDriver.UID == "" || !reflect.DeepEqual(desiredCsiDriver, existingCsiDriver) { + if existingCsiDriver.UID != "" { + log.Info("CSI Driver resource exist but does not meet desired state") + if err := r.Delete(r.ctx, existingCsiDriver); err != nil { + log.Error(err, "Failed to delete existing CSI Driver resource") + return err + } + log.Info("CSI Driver resource deleted successfully") + } else { + log.Info("CSI Driver resource does not exist") + } + + if err := r.Create(r.ctx, desiredCsiDriver); err != nil { + log.Error(err, "Failed to create a CSI Driver resource") + return err + } + + log.Info("CSI Driver resource created successfully") + } else { + log.Info("CSI Driver resource already meets desired state") + } + return nil } -func (r *driverReconcile) upsertProvisionerDeployment() error { +func (r *driverReconcile) reconcilePluginDeamonSet() error { return nil } -func (r *driverReconcile) upsertK8sCSIDriver() error { +func (r *driverReconcile) reconcileProvisionerDeployment() error { return nil } -func (r *driverReconcile) upsertLivnessService() error { +func (r *driverReconcile) reconcileLivnessService() error { return nil } diff --git a/utils/utils.go b/utils/utils.go index 0e635eb2..eccb1156 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -18,6 +18,8 @@ package utils import ( "sync" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func RunConcurrently(fnList ...func() error) chan error { @@ -51,3 +53,17 @@ func ChannelToSlice[T any](c chan T) []T { } return list } + +// AddAnnotation adds an annotation to a resource metadata, returns true if added else false +func AddAnnotation(obj metav1.Object, key string, value string) bool { + annotations := obj.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + obj.SetAnnotations(annotations) + } + if oldValue, exist := annotations[key]; !exist || oldValue != value { + annotations[key] = value + return true + } + return false +}