k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/volume/attachdetach/util/util.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     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 util
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"strings"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	corelisters "k8s.io/client-go/listers/core/v1"
    28  	"k8s.io/component-helpers/storage/ephemeral"
    29  	"k8s.io/klog/v2"
    30  	"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
    31  	"k8s.io/kubernetes/pkg/volume"
    32  	"k8s.io/kubernetes/pkg/volume/csimigration"
    33  	"k8s.io/kubernetes/pkg/volume/util"
    34  )
    35  
    36  // CreateVolumeSpec creates and returns a mutatable volume.Spec object for the
    37  // specified volume. It dereference any PVC to get PV objects, if needed.
    38  // A volume.Spec that refers to an in-tree plugin spec is translated to refer
    39  // to a migrated CSI plugin spec if all conditions for CSI migration on a node
    40  // for the in-tree plugin is satisfied.
    41  func CreateVolumeSpec(logger klog.Logger, podVolume v1.Volume, pod *v1.Pod, nodeName types.NodeName, vpm *volume.VolumePluginMgr, pvcLister corelisters.PersistentVolumeClaimLister, pvLister corelisters.PersistentVolumeLister, csiMigratedPluginManager csimigration.PluginManager, csiTranslator csimigration.InTreeToCSITranslator) (*volume.Spec, error) {
    42  	claimName := ""
    43  	readOnly := false
    44  	if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
    45  		claimName = pvcSource.ClaimName
    46  		readOnly = pvcSource.ReadOnly
    47  	}
    48  	isEphemeral := podVolume.VolumeSource.Ephemeral != nil
    49  	if isEphemeral {
    50  		claimName = ephemeral.VolumeClaimName(pod, &podVolume)
    51  	}
    52  	if claimName != "" {
    53  		logger.V(10).Info("Found PVC", "PVC", klog.KRef(pod.Namespace, claimName))
    54  
    55  		// If podVolume is a PVC, fetch the real PV behind the claim
    56  		pvc, err := getPVCFromCache(pod.Namespace, claimName, pvcLister)
    57  		if err != nil {
    58  			return nil, fmt.Errorf(
    59  				"error processing PVC %q/%q: %v",
    60  				pod.Namespace,
    61  				claimName,
    62  				err)
    63  		}
    64  		if isEphemeral {
    65  			if err := ephemeral.VolumeIsForPod(pod, pvc); err != nil {
    66  				return nil, err
    67  			}
    68  		}
    69  
    70  		pvName, pvcUID := pvc.Spec.VolumeName, pvc.UID
    71  		logger.V(10).Info("Found bound PV for PVC", "PVC", klog.KRef(pod.Namespace, claimName), "pvcUID", pvcUID, "PV", klog.KRef("", pvName))
    72  
    73  		// Fetch actual PV object
    74  		volumeSpec, err := getPVSpecFromCache(
    75  			pvName, readOnly, pvcUID, pvLister)
    76  		if err != nil {
    77  			return nil, fmt.Errorf(
    78  				"error processing PVC %q/%q: %v",
    79  				pod.Namespace,
    80  				claimName,
    81  				err)
    82  		}
    83  
    84  		volumeSpec, err = translateInTreeSpecToCSIIfNeeded(volumeSpec, nodeName, vpm, csiMigratedPluginManager, csiTranslator, pod.Namespace)
    85  		if err != nil {
    86  			return nil, fmt.Errorf(
    87  				"error performing CSI migration checks and translation for PVC %q/%q: %v",
    88  				pod.Namespace,
    89  				claimName,
    90  				err)
    91  		}
    92  
    93  		logger.V(10).Info("Extracted volumeSpec from bound PV and PVC", "PVC", klog.KRef(pod.Namespace, claimName), "pvcUID", pvcUID, "PV", klog.KRef("", pvName), "volumeSpecName", volumeSpec.Name())
    94  
    95  		return volumeSpec, nil
    96  	}
    97  
    98  	// Do not return the original volume object, since it's from the shared
    99  	// informer it may be mutated by another consumer.
   100  	clonedPodVolume := podVolume.DeepCopy()
   101  
   102  	origspec := volume.NewSpecFromVolume(clonedPodVolume)
   103  	spec, err := translateInTreeSpecToCSIIfNeeded(origspec, nodeName, vpm, csiMigratedPluginManager, csiTranslator, pod.Namespace)
   104  	if err != nil {
   105  		return nil, fmt.Errorf(
   106  			"error performing CSI migration checks and translation for inline volume %q: %v",
   107  			podVolume.Name,
   108  			err)
   109  	}
   110  	return spec, nil
   111  }
   112  
   113  // getPVCFromCache fetches the PVC object with the given namespace and
   114  // name from the shared internal PVC store.
   115  // This method returns an error if a PVC object does not exist in the cache
   116  // with the given namespace/name.
   117  // This method returns an error if the PVC object's phase is not "Bound".
   118  func getPVCFromCache(namespace string, name string, pvcLister corelisters.PersistentVolumeClaimLister) (*v1.PersistentVolumeClaim, error) {
   119  	pvc, err := pvcLister.PersistentVolumeClaims(namespace).Get(name)
   120  	if err != nil {
   121  		return nil, fmt.Errorf("failed to find PVC %s/%s in PVCInformer cache: %v", namespace, name, err)
   122  	}
   123  
   124  	if pvc.Status.Phase != v1.ClaimBound || pvc.Spec.VolumeName == "" {
   125  		return nil, fmt.Errorf(
   126  			"PVC %s/%s has non-bound phase (%q) or empty pvc.Spec.VolumeName (%q)",
   127  			namespace,
   128  			name,
   129  			pvc.Status.Phase,
   130  			pvc.Spec.VolumeName)
   131  	}
   132  
   133  	return pvc, nil
   134  }
   135  
   136  // getPVSpecFromCache fetches the PV object with the given name from the shared
   137  // internal PV store and returns a volume.Spec representing it.
   138  // This method returns an error if a PV object does not exist in the cache with
   139  // the given name.
   140  // This method deep copies the PV object so the caller may use the returned
   141  // volume.Spec object without worrying about it mutating unexpectedly.
   142  func getPVSpecFromCache(name string, pvcReadOnly bool, expectedClaimUID types.UID, pvLister corelisters.PersistentVolumeLister) (*volume.Spec, error) {
   143  	pv, err := pvLister.Get(name)
   144  	if err != nil {
   145  		return nil, fmt.Errorf("failed to find PV %q in PVInformer cache: %v", name, err)
   146  	}
   147  
   148  	if pv.Spec.ClaimRef == nil {
   149  		return nil, fmt.Errorf(
   150  			"found PV object %q but it has a nil pv.Spec.ClaimRef indicating it is not yet bound to the claim",
   151  			name)
   152  	}
   153  
   154  	if pv.Spec.ClaimRef.UID != expectedClaimUID {
   155  		return nil, fmt.Errorf(
   156  			"found PV object %q but its pv.Spec.ClaimRef.UID (%q) does not point to claim.UID (%q)",
   157  			name,
   158  			pv.Spec.ClaimRef.UID,
   159  			expectedClaimUID)
   160  	}
   161  
   162  	// Do not return the object from the informer, since the store is shared it
   163  	// may be mutated by another consumer.
   164  	clonedPV := pv.DeepCopy()
   165  
   166  	return volume.NewSpecFromPersistentVolume(clonedPV, pvcReadOnly), nil
   167  }
   168  
   169  // DetermineVolumeAction returns true if volume and pod needs to be added to dswp
   170  // and it returns false if volume and pod needs to be removed from dswp
   171  func DetermineVolumeAction(pod *v1.Pod, desiredStateOfWorld cache.DesiredStateOfWorld, defaultAction bool) bool {
   172  	if pod == nil || len(pod.Spec.Volumes) <= 0 {
   173  		return defaultAction
   174  	}
   175  
   176  	if util.IsPodTerminated(pod, pod.Status) {
   177  		return false
   178  	}
   179  	return defaultAction
   180  }
   181  
   182  // ProcessPodVolumes processes the volumes in the given pod and adds them to the
   183  // desired state of the world if addVolumes is true, otherwise it removes them.
   184  func ProcessPodVolumes(logger klog.Logger, pod *v1.Pod, addVolumes bool, desiredStateOfWorld cache.DesiredStateOfWorld, volumePluginMgr *volume.VolumePluginMgr, pvcLister corelisters.PersistentVolumeClaimLister, pvLister corelisters.PersistentVolumeLister, csiMigratedPluginManager csimigration.PluginManager, csiTranslator csimigration.InTreeToCSITranslator) {
   185  	if pod == nil {
   186  		return
   187  	}
   188  	if len(pod.Spec.Volumes) <= 0 {
   189  		logger.V(10).Info("Skipping processing of pod, it has no volumes", "pod", klog.KObj(pod))
   190  		return
   191  	}
   192  
   193  	nodeName := types.NodeName(pod.Spec.NodeName)
   194  	if nodeName == "" {
   195  		logger.V(10).Info("Skipping processing of pod, it is not scheduled to a node", "pod", klog.KObj(pod))
   196  		return
   197  	} else if !desiredStateOfWorld.NodeExists(nodeName) {
   198  		// If the node the pod is scheduled to does not exist in the desired
   199  		// state of the world data structure, that indicates the node is not
   200  		// yet managed by the controller. Therefore, ignore the pod.
   201  		logger.V(4).Info("Skipping processing of pod, it is scheduled to node which is not managed by the controller", "node", klog.KRef("", string(nodeName)), "pod", klog.KObj(pod))
   202  		return
   203  	}
   204  
   205  	// Process volume spec for each volume defined in pod
   206  	for _, podVolume := range pod.Spec.Volumes {
   207  		volumeSpec, err := CreateVolumeSpec(logger, podVolume, pod, nodeName, volumePluginMgr, pvcLister, pvLister, csiMigratedPluginManager, csiTranslator)
   208  		if err != nil {
   209  			logger.V(10).Info("Error processing volume for pod", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "err", err)
   210  			continue
   211  		}
   212  
   213  		attachableVolumePlugin, err :=
   214  			volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
   215  		if err != nil || attachableVolumePlugin == nil {
   216  			logger.V(10).Info("Skipping volume for pod, it does not implement attacher interface", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "err", err)
   217  			continue
   218  		}
   219  
   220  		uniquePodName := util.GetUniquePodName(pod)
   221  		if addVolumes {
   222  			// Add volume to desired state of world
   223  			_, err := desiredStateOfWorld.AddPod(
   224  				uniquePodName, pod, volumeSpec, nodeName)
   225  			if err != nil {
   226  				logger.V(10).Info("Failed to add volume for pod to desiredStateOfWorld", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "err", err)
   227  			}
   228  
   229  		} else {
   230  			// Remove volume from desired state of world
   231  			uniqueVolumeName, err := util.GetUniqueVolumeNameFromSpec(
   232  				attachableVolumePlugin, volumeSpec)
   233  			if err != nil {
   234  				logger.V(10).Info("Failed to delete volume for pod from desiredStateOfWorld. GetUniqueVolumeNameFromSpec failed", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "err", err)
   235  				continue
   236  			}
   237  			desiredStateOfWorld.DeletePod(
   238  				uniquePodName, uniqueVolumeName, nodeName)
   239  		}
   240  	}
   241  	return
   242  }
   243  
   244  func translateInTreeSpecToCSIIfNeeded(spec *volume.Spec, nodeName types.NodeName, vpm *volume.VolumePluginMgr, csiMigratedPluginManager csimigration.PluginManager, csiTranslator csimigration.InTreeToCSITranslator, podNamespace string) (*volume.Spec, error) {
   245  	translatedSpec := spec
   246  	migratable, err := csiMigratedPluginManager.IsMigratable(spec)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	if !migratable {
   251  		// Jump out of translation fast so we don't check the node if the spec itself is not migratable
   252  		return spec, nil
   253  	}
   254  	migrationSupportedOnNode, err := isCSIMigrationSupportedOnNode(nodeName, spec, vpm, csiMigratedPluginManager)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	if migratable && migrationSupportedOnNode {
   259  		translatedSpec, err = csimigration.TranslateInTreeSpecToCSI(spec, podNamespace, csiTranslator)
   260  		if err != nil {
   261  			return nil, err
   262  		}
   263  	}
   264  	return translatedSpec, nil
   265  }
   266  
   267  func isCSIMigrationSupportedOnNode(nodeName types.NodeName, spec *volume.Spec, vpm *volume.VolumePluginMgr, csiMigratedPluginManager csimigration.PluginManager) (bool, error) {
   268  	pluginName, err := csiMigratedPluginManager.GetInTreePluginNameFromSpec(spec.PersistentVolume, spec.Volume)
   269  	if err != nil {
   270  		return false, err
   271  	}
   272  
   273  	if len(pluginName) == 0 {
   274  		// Could not find a plugin name from translation directory, assume not translated
   275  		return false, nil
   276  	}
   277  
   278  	if csiMigratedPluginManager.IsMigrationCompleteForPlugin(pluginName) {
   279  		// All nodes are expected to have migrated CSI plugin installed and
   280  		// configured when CSI Migration Complete flag is enabled for a plugin.
   281  		// CSI migration is supported even if there is version skew between
   282  		// managers and node.
   283  		return true, nil
   284  	}
   285  
   286  	if len(nodeName) == 0 {
   287  		return false, errors.New("nodeName is empty")
   288  	}
   289  
   290  	kubeClient := vpm.Host.GetKubeClient()
   291  	if kubeClient == nil {
   292  		// Don't handle the controller/kubelet version skew check and fallback
   293  		// to just checking the feature gates. This can happen if
   294  		// we are in a standalone (headless) Kubelet
   295  		return true, nil
   296  	}
   297  
   298  	adcHost, ok := vpm.Host.(volume.AttachDetachVolumeHost)
   299  	if !ok {
   300  		// Don't handle the controller/kubelet version skew check and fallback
   301  		// to just checking the feature gates. This can happen if
   302  		// "enableControllerAttachDetach" is set to true on kubelet
   303  		return true, nil
   304  	}
   305  
   306  	if adcHost.CSINodeLister() == nil {
   307  		return false, errors.New("could not find CSINodeLister in attachDetachController")
   308  	}
   309  
   310  	csiNode, err := adcHost.CSINodeLister().Get(string(nodeName))
   311  	if err != nil {
   312  		return false, err
   313  	}
   314  
   315  	ann := csiNode.GetAnnotations()
   316  	if ann == nil {
   317  		return false, nil
   318  	}
   319  
   320  	mpa := ann[v1.MigratedPluginsAnnotationKey]
   321  	tok := strings.Split(mpa, ",")
   322  	mpaSet := sets.NewString(tok...)
   323  
   324  	isMigratedOnNode := mpaSet.Has(pluginName)
   325  
   326  	return isMigratedOnNode, nil
   327  }