Files
my-rook-config/pkg/operator/ceph/csi/operator_driver.go
T
Sonny Sasaka dff04494fe csi: fix swapped provisionerPriorityClassName with pluginPriorityClassName
Previously provisionerPriorityClassName config is applied to node plugin
but pluginPriorityClassName is applied to controller plugin. This causes
a misconfiguration since the default values for
provisionerPriorityClassName is system-cluster-critical and
pluginPriorityClassName is system-node-critical.

The default priority for node plugin ought to be system-node-critical so
we can leverage this information to kill the pods in this priority last,
allowing other pods that may mount RBD-backed volume to unmount cleanly
first, otherwise the shutdown sequence may hang due to failure
unmounting RBD-backed volume when the node plugin pod is already killed.

Signed-off-by: Sonny Sasaka <sonnysasaka@gmail.com>
2026-04-16 08:30:28 -07:00

486 lines
17 KiB
Go

/*
Copyright 2024 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csi
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
csiopv1 "github.com/ceph/ceph-csi-operator/api/v1"
"github.com/pkg/errors"
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
"github.com/rook/rook/pkg/operator/k8sutil"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
k8scsiv1 "k8s.io/api/storage/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func (r *ReconcileCSI) createOrUpdateDriverResources(cluster cephv1.CephCluster, clusterInfo *cephclient.ClusterInfo) error {
if EnableRBD {
logger.Info("Creating RBD driver resources")
err := r.transferCSIDriverOwner(r.opManagerContext, RBDDriverName)
if err != nil {
return errors.Wrap(err, "failed to create update RBD driver for csi-operator driver CR ")
}
err = r.createOrUpdateRBDDriverResource(cluster, clusterInfo)
if err != nil {
return errors.Wrapf(err, "failed to create or update RBD driver resource in the namespace %q", r.opConfig.OperatorNamespace)
}
}
if EnableCephFS {
logger.Info("Creating CephFS driver resources")
err := r.transferCSIDriverOwner(r.opManagerContext, CephFSDriverName)
if err != nil {
return errors.Wrap(err, "failed to create update CephFS driver for csi-operator driver CR ")
}
err = r.createOrUpdateCephFSDriverResource(cluster, clusterInfo)
if err != nil {
return errors.Wrapf(err, "failed to create or update cephFS driver resource in the namespace %q", r.opConfig.OperatorNamespace)
}
}
if EnableNFS {
logger.Info("Creating NFS driver resources")
err := r.transferCSIDriverOwner(r.opManagerContext, NFSDriverName)
if err != nil {
return errors.Wrap(err, "failed to create update NFS driver for csi-operator driver CR ")
}
err = r.createOrUpdateNFSDriverResource(cluster, clusterInfo)
if err != nil {
return errors.Wrapf(err, "failed to create or update NFS driver resource in the namespace %q", r.opConfig.OperatorNamespace)
}
}
return nil
}
func (r *ReconcileCSI) createOrUpdateRBDDriverResource(cluster cephv1.CephCluster, clusterInfo *cephclient.ClusterInfo) error {
resourceName := fmt.Sprintf("%s.rbd.csi.ceph.com", r.opConfig.OperatorNamespace)
spec, err := r.generateDriverSpec(cluster)
if err != nil {
return err
}
spec.NodePlugin.Labels = CSIParam.CSIRBDPodLabels
spec.ControllerPlugin.Labels = CSIParam.CSIRBDPodLabels
rbdDriver := &csiopv1.Driver{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: r.opConfig.OperatorNamespace,
},
Spec: spec,
}
rbdDriver.Spec.ControllerPlugin.Resources = createDriverControllerPluginResources(rbdProvisionerResource)
rbdDriver.Spec.NodePlugin.Resources = createDriverNodePluginResouces(rbdPluginResource)
rbdDriver.Spec.NodePlugin.UpdateStrategy = &v1.DaemonSetUpdateStrategy{
Type: v1.RollingUpdateDaemonSetStrategyType,
}
if CSIParam.CSIDomainLabels != "" {
domainLabels := strings.Split(CSIParam.CSIDomainLabels, ",")
rbdDriver.Spec.NodePlugin.Topology = &csiopv1.TopologySpec{
DomainLabels: domainLabels,
}
}
if CSIParam.RBDPluginUpdateStrategy == "OnDelete" {
rbdDriver.Spec.NodePlugin.UpdateStrategy = &v1.DaemonSetUpdateStrategy{
Type: v1.OnDeleteDaemonSetStrategyType,
}
}
podVolumes := getPodVolumes(rbdPluginVolume, rbdPluginVolumeMount)
rbdDriver.Spec.NodePlugin.PodCommonSpec.Volumes = podVolumes
err = r.createOrUpdateDriverResource(clusterInfo, rbdDriver)
if err != nil {
return errors.Wrapf(err, "failed to create or update RBD driver resource %q", rbdDriver.Name)
}
return nil
}
func (r *ReconcileCSI) createOrUpdateCephFSDriverResource(cluster cephv1.CephCluster, clusterInfo *cephclient.ClusterInfo) error {
resourceName := fmt.Sprintf("%s.cephfs.csi.ceph.com", r.opConfig.OperatorNamespace)
spec, err := r.generateDriverSpec(cluster)
if err != nil {
return err
}
spec.NodePlugin.Labels = CSIParam.CSICephFSPodLabels
spec.ControllerPlugin.Labels = CSIParam.CSICephFSPodLabels
cephFsDriver := &csiopv1.Driver{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: r.opConfig.OperatorNamespace,
},
Spec: spec,
}
if CSIParam.VolumeGroupSnapshotCLIFlag != "" {
cephFsDriver.Spec.SnapshotPolicy = csiopv1.VolumeGroupSnapshotPolicy
}
cephFsDriver.Spec.ControllerPlugin.Resources = createDriverControllerPluginResources(cephFSProvisionerResource)
cephFsDriver.Spec.NodePlugin.Resources = createDriverNodePluginResouces(cephFSPluginResource)
cephFsDriver.Spec.NodePlugin.UpdateStrategy = &v1.DaemonSetUpdateStrategy{
Type: v1.RollingUpdateDaemonSetStrategyType,
}
if CSIParam.CSIDomainLabels != "" {
domainLabels := strings.Split(CSIParam.CSIDomainLabels, ",")
cephFsDriver.Spec.NodePlugin.Topology = &csiopv1.TopologySpec{
DomainLabels: domainLabels,
}
}
if CSIParam.RBDPluginUpdateStrategy == "OnDelete" {
cephFsDriver.Spec.NodePlugin.UpdateStrategy = &v1.DaemonSetUpdateStrategy{
Type: v1.OnDeleteDaemonSetStrategyType,
}
}
podVolumes := getPodVolumes(cephFSPluginVolume, cephFSPluginVolumeMount)
cephFsDriver.Spec.NodePlugin.PodCommonSpec.Volumes = podVolumes
err = r.createOrUpdateDriverResource(clusterInfo, cephFsDriver)
if err != nil {
return errors.Wrapf(err, "failed to create or update cephFS driver resource %q", cephFsDriver.Name)
}
return nil
}
func (r *ReconcileCSI) createOrUpdateNFSDriverResource(cluster cephv1.CephCluster, clusterInfo *cephclient.ClusterInfo) error {
resourceName := fmt.Sprintf("%s.nfs.csi.ceph.com", r.opConfig.OperatorNamespace)
spec, err := r.generateDriverSpec(cluster)
if err != nil {
return err
}
spec.NodePlugin.Labels = CSIParam.CSINFSPodLabels
spec.ControllerPlugin.Labels = CSIParam.CSINFSPodLabels
NFSDriver := &csiopv1.Driver{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: r.opConfig.OperatorNamespace,
},
Spec: spec,
}
NFSDriver.Spec.ControllerPlugin.Resources = createDriverControllerPluginResources(nfsProvisionerResource)
NFSDriver.Spec.NodePlugin.Resources = createDriverNodePluginResouces(nfsPluginResource)
NFSDriver.Spec.NodePlugin.UpdateStrategy = &v1.DaemonSetUpdateStrategy{
Type: v1.RollingUpdateDaemonSetStrategyType,
}
if CSIParam.CSIDomainLabels != "" {
domainLabels := strings.Split(CSIParam.CSIDomainLabels, ",")
NFSDriver.Spec.NodePlugin.Topology = &csiopv1.TopologySpec{
DomainLabels: domainLabels,
}
}
if CSIParam.RBDPluginUpdateStrategy == "OnDelete" {
NFSDriver.Spec.NodePlugin.UpdateStrategy = &v1.DaemonSetUpdateStrategy{
Type: v1.OnDeleteDaemonSetStrategyType,
}
}
err = r.createOrUpdateDriverResource(clusterInfo, NFSDriver)
if err != nil {
return errors.Wrapf(err, "failed to create or update NFS driver resource %q", NFSDriver.Name)
}
return nil
}
func (r ReconcileCSI) createOrUpdateDriverResource(clusterInfo *cephclient.ClusterInfo, driverResource *csiopv1.Driver) error {
spec := driverResource.Spec
err := r.client.Get(r.opManagerContext, types.NamespacedName{Name: driverResource.Name, Namespace: r.opConfig.OperatorNamespace}, driverResource)
if err != nil {
if apierrors.IsNotFound(err) {
err = r.client.Create(r.opManagerContext, driverResource)
if err != nil {
return errors.Wrapf(err, "failed to create CSI-operator driver CR %q", driverResource.Name)
}
logger.Infof("successfully created CSI driver cr %q", driverResource.Name)
return nil
}
return errors.Wrapf(err, "failed to get CSI-operator driver CR %q", opConfigCRName)
}
driverResource.Spec = spec
err = r.client.Update(r.opManagerContext, driverResource)
if err != nil {
return errors.Wrapf(err, "failed to update CSI-operator driver CR %q", driverResource.Name)
}
logger.Infof("successfully updated CSI-operator driver resource %q", driverResource.Name)
return nil
}
func (r *ReconcileCSI) generateDriverSpec(cluster cephv1.CephCluster) (csiopv1.DriverSpec, error) {
cephfsClientType := csiopv1.KernelCephFsClient
if CSIParam.ForceCephFSKernelClient == "false" {
cephfsClientType = csiopv1.AutoDetectCephFsClient
}
imageSetCmName, err := r.createImageSetConfigmap()
if err != nil {
return csiopv1.DriverSpec{}, errors.Wrapf(err, "failed to create ceph-CSI operator config ImageSetConfigmap for CR %s", opConfigCRName)
}
driverSpec := csiopv1.DriverSpec{
Log: &csiopv1.LogSpec{
Verbosity: int(CSIParam.LogLevel),
},
ImageSet: &corev1.LocalObjectReference{
Name: imageSetCmName,
},
ClusterName: &cluster.Name,
EnableMetadata: &CSIParam.CSIEnableMetadata,
GenerateOMapInfo: &CSIParam.EnableOMAPGenerator,
FsGroupPolicy: k8scsiv1.FileFSGroupPolicy,
NodePlugin: &csiopv1.NodePluginSpec{
PodCommonSpec: csiopv1.PodCommonSpec{
PrioritylClassName: &CSIParam.PluginPriorityClassName,
Affinity: &corev1.Affinity{
NodeAffinity: getNodeAffinity(pluginNodeAffinityEnv, &corev1.NodeAffinity{}),
},
Tolerations: getToleration(pluginTolerationsEnv, []corev1.Toleration{}),
},
Resources: csiopv1.NodePluginResourcesSpec{},
KubeletDirPath: CSIParam.KubeletDirPath,
EnableSeLinuxHostMount: &CSIParam.EnablePluginSelinuxHostMount,
},
ControllerPlugin: &csiopv1.ControllerPluginSpec{
PodCommonSpec: csiopv1.PodCommonSpec{
PrioritylClassName: &CSIParam.ProvisionerPriorityClassName,
Affinity: &corev1.Affinity{
NodeAffinity: getNodeAffinity(provisionerNodeAffinityEnv, &corev1.NodeAffinity{}),
},
Tolerations: getToleration(provisionerTolerationsEnv, []corev1.Toleration{}),
},
Replicas: &CSIParam.ProvisionerReplicas,
Resources: csiopv1.ControllerPluginResourcesSpec{},
},
DeployCsiAddons: &CSIParam.EnableCSIAddonsSideCar,
CephFsClientType: cephfsClientType,
}
// Apply Multus annotations if the cluster uses Multus networking
if cluster.Spec.Network.IsMultus() {
// Apply Multus to ControllerPlugin only
if driverSpec.ControllerPlugin.PodCommonSpec.Annotations == nil {
driverSpec.ControllerPlugin.PodCommonSpec.Annotations = make(map[string]string)
}
controllerObjectMeta := metav1.ObjectMeta{
Annotations: driverSpec.ControllerPlugin.PodCommonSpec.Annotations,
}
err = r.applyMultusToObjectMeta(cluster.Namespace, &cluster.Spec.Network, &controllerObjectMeta)
if err != nil {
return csiopv1.DriverSpec{}, errors.Wrap(err, "failed to apply multus configuration to ControllerPlugin")
}
driverSpec.ControllerPlugin.PodCommonSpec.Annotations = controllerObjectMeta.Annotations
}
return driverSpec, nil
}
// applyMultusToObjectMeta applies Multus network annotations to the given ObjectMeta
func (r *ReconcileCSI) applyMultusToObjectMeta(clusterNamespace string, netSpec *cephv1.NetworkSpec, objectMeta *metav1.ObjectMeta) error {
return k8sutil.ApplyMultus(clusterNamespace, netSpec, objectMeta)
}
func getPodVolumes(volumeEnvVar, volumeMountEnvVar string) []csiopv1.VolumeSpec {
volumes := extractVolumes(volumeEnvVar)
volumeMounts := extractVolumeMounts(volumeMountEnvVar)
return append(volumes, volumeMounts...)
}
func extractVolumes(volumeEnvVar string) []csiopv1.VolumeSpec {
volumesRaw := k8sutil.GetOperatorSetting(volumeEnvVar, "")
if volumesRaw == "" {
return nil
}
volumes, err := k8sutil.YamlToVolumes(volumesRaw)
if err != nil {
logger.Errorf("failed to parse %q for %q. %v", volumesRaw, volumeEnvVar, err)
return nil
}
// Convert the volumes to csiopv1.VolumeSpec, where the mounts are empty.
// The CSI volume does not enforce the volume and mount to be defined together,
// so we can just have volumes defined here without mounts.
csiVolumes := []csiopv1.VolumeSpec{}
for i := range volumes {
csiVolumes = append(csiVolumes, csiopv1.VolumeSpec{
Volume: volumes[i],
})
}
return csiVolumes
}
func extractVolumeMounts(volumeMountsEnvVar string) []csiopv1.VolumeSpec {
volumeMountsRaw := k8sutil.GetOperatorSetting(volumeMountsEnvVar, "")
if volumeMountsRaw == "" {
return nil
}
volumeMounts, err := k8sutil.YamlToVolumeMounts(volumeMountsRaw)
if err != nil {
logger.Errorf("failed to parse %q for %q. %v", volumeMountsRaw, configName, err)
return nil
}
// Convert the volume mounts to csiopv1.VolumeSpec, where the volumes are empty.
// The CSI volume does not enforce the volume and mount to be defined together,
// so we can just have mounts defined here without volumes.
csiVolumes := []csiopv1.VolumeSpec{}
for i := range volumeMounts {
csiVolumes = append(csiVolumes, csiopv1.VolumeSpec{
Mount: volumeMounts[i],
})
}
return csiVolumes
}
func createDriverControllerPluginResources(key string) csiopv1.ControllerPluginResourcesSpec {
controllerPluginResources := csiopv1.ControllerPluginResourcesSpec{}
resource := getComputeResource(key)
for _, r := range resource {
if !reflect.DeepEqual(r.Resource, corev1.ResourceRequirements{}) {
switch {
case strings.Contains(r.Name, "provisioner"):
controllerPluginResources.Provisioner = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
case strings.Contains(r.Name, "resizer"):
controllerPluginResources.Resizer = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
case strings.Contains(r.Name, "snapshotter"):
controllerPluginResources.Snapshotter = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
case strings.Contains(r.Name, "attacher"):
controllerPluginResources.Attacher = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
case strings.Contains(r.Name, "plugin"):
controllerPluginResources.Plugin = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
case strings.Contains(r.Name, "omap-generator"):
controllerPluginResources.OMapGenerator = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
case strings.Contains(r.Name, "addons"):
controllerPluginResources.Addons = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
}
}
}
return controllerPluginResources
}
func createDriverNodePluginResouces(key string) csiopv1.NodePluginResourcesSpec {
nodePluginResources := csiopv1.NodePluginResourcesSpec{}
resource := getComputeResource(key)
for _, r := range resource {
if !reflect.DeepEqual(r.Resource, corev1.ResourceRequirements{}) {
if strings.Contains(r.Name, "registrar") {
nodePluginResources.Registrar = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
} else if strings.Contains(r.Name, "plugin") {
nodePluginResources.Plugin = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
} else if strings.Contains(r.Name, "addons") {
nodePluginResources.Addons = &corev1.ResourceRequirements{
Limits: r.Resource.Limits,
Requests: r.Resource.Requests,
}
}
}
}
return nodePluginResources
}
// transferCSIDriverOwner update CSIDriver and returns the error if any
func (r *ReconcileCSI) transferCSIDriverOwner(ctx context.Context, name string) error {
logger.Info("adding annotation to CSIDriver resource for csi-operator to own it")
csiDriver, err := r.context.Clientset.StorageV1().CSIDrivers().Get(ctx, name, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
logger.Debugf("%s CSIDriver not found; skipping ownership transfer.", name)
return nil
}
}
key := "csi.ceph.io/ownerref"
ownerObjKey := client.ObjectKeyFromObject(csiDriver)
ownerObjKey.Namespace = r.opConfig.OperatorNamespace
val, err := json.Marshal(ownerObjKey)
if err != nil {
return errors.Wrapf(err, "failed to marshal owner object key %q", ownerObjKey.Name)
}
annotations := csiDriver.GetAnnotations()
if annotations == nil {
annotations = map[string]string{}
csiDriver.SetAnnotations(annotations)
}
if oldValue, exist := annotations[key]; !exist || oldValue != string(val) {
annotations[key] = string(val)
} else {
return nil
}
_, err = r.context.Clientset.StorageV1().CSIDrivers().Update(ctx, csiDriver, metav1.UpdateOptions{})
if err != nil {
return errors.Wrapf(err, "failed to update CSIDriver %s", name)
}
return nil
}