k8s.io/kubernetes@v1.29.3/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  		nodeName := types.NodeName(pod.Spec.NodeName)
   178  		keepTerminatedPodVolume := desiredStateOfWorld.GetKeepTerminatedPodVolumesForNode(nodeName)
   179  		// if pod is terminate we let kubelet policy dictate if volume
   180  		// should be detached or not
   181  		return keepTerminatedPodVolume
   182  	}
   183  	return defaultAction
   184  }
   185  
   186  // ProcessPodVolumes processes the volumes in the given pod and adds them to the
   187  // desired state of the world if addVolumes is true, otherwise it removes them.
   188  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) {
   189  	if pod == nil {
   190  		return
   191  	}
   192  	if len(pod.Spec.Volumes) <= 0 {
   193  		logger.V(10).Info("Skipping processing of pod, it has no volumes", "pod", klog.KObj(pod))
   194  		return
   195  	}
   196  
   197  	nodeName := types.NodeName(pod.Spec.NodeName)
   198  	if nodeName == "" {
   199  		logger.V(10).Info("Skipping processing of pod, it is not scheduled to a node", "pod", klog.KObj(pod))
   200  		return
   201  	} else if !desiredStateOfWorld.NodeExists(nodeName) {
   202  		// If the node the pod is scheduled to does not exist in the desired
   203  		// state of the world data structure, that indicates the node is not
   204  		// yet managed by the controller. Therefore, ignore the pod.
   205  		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))
   206  		return
   207  	}
   208  
   209  	// Process volume spec for each volume defined in pod
   210  	for _, podVolume := range pod.Spec.Volumes {
   211  		volumeSpec, err := CreateVolumeSpec(logger, podVolume, pod, nodeName, volumePluginMgr, pvcLister, pvLister, csiMigratedPluginManager, csiTranslator)
   212  		if err != nil {
   213  			logger.V(10).Info("Error processing volume for pod", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "err", err)
   214  			continue
   215  		}
   216  
   217  		attachableVolumePlugin, err :=
   218  			volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
   219  		if err != nil || attachableVolumePlugin == nil {
   220  			logger.V(10).Info("Skipping volume for pod, it does not implement attacher interface", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "err", err)
   221  			continue
   222  		}
   223  
   224  		uniquePodName := util.GetUniquePodName(pod)
   225  		if addVolumes {
   226  			// Add volume to desired state of world
   227  			_, err := desiredStateOfWorld.AddPod(
   228  				uniquePodName, pod, volumeSpec, nodeName)
   229  			if err != nil {
   230  				logger.V(10).Info("Failed to add volume for pod to desiredStateOfWorld", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "err", err)
   231  			}
   232  
   233  		} else {
   234  			// Remove volume from desired state of world
   235  			uniqueVolumeName, err := util.GetUniqueVolumeNameFromSpec(
   236  				attachableVolumePlugin, volumeSpec)
   237  			if err != nil {
   238  				logger.V(10).Info("Failed to delete volume for pod from desiredStateOfWorld. GetUniqueVolumeNameFromSpec failed", "pod", klog.KObj(pod), "volumeName", podVolume.Name, "err", err)
   239  				continue
   240  			}
   241  			desiredStateOfWorld.DeletePod(
   242  				uniquePodName, uniqueVolumeName, nodeName)
   243  		}
   244  	}
   245  	return
   246  }
   247  
   248  func translateInTreeSpecToCSIIfNeeded(spec *volume.Spec, nodeName types.NodeName, vpm *volume.VolumePluginMgr, csiMigratedPluginManager csimigration.PluginManager, csiTranslator csimigration.InTreeToCSITranslator, podNamespace string) (*volume.Spec, error) {
   249  	translatedSpec := spec
   250  	migratable, err := csiMigratedPluginManager.IsMigratable(spec)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	if !migratable {
   255  		// Jump out of translation fast so we don't check the node if the spec itself is not migratable
   256  		return spec, nil
   257  	}
   258  	migrationSupportedOnNode, err := isCSIMigrationSupportedOnNode(nodeName, spec, vpm, csiMigratedPluginManager)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  	if migratable && migrationSupportedOnNode {
   263  		translatedSpec, err = csimigration.TranslateInTreeSpecToCSI(spec, podNamespace, csiTranslator)
   264  		if err != nil {
   265  			return nil, err
   266  		}
   267  	}
   268  	return translatedSpec, nil
   269  }
   270  
   271  func isCSIMigrationSupportedOnNode(nodeName types.NodeName, spec *volume.Spec, vpm *volume.VolumePluginMgr, csiMigratedPluginManager csimigration.PluginManager) (bool, error) {
   272  	pluginName, err := csiMigratedPluginManager.GetInTreePluginNameFromSpec(spec.PersistentVolume, spec.Volume)
   273  	if err != nil {
   274  		return false, err
   275  	}
   276  
   277  	if len(pluginName) == 0 {
   278  		// Could not find a plugin name from translation directory, assume not translated
   279  		return false, nil
   280  	}
   281  
   282  	if csiMigratedPluginManager.IsMigrationCompleteForPlugin(pluginName) {
   283  		// All nodes are expected to have migrated CSI plugin installed and
   284  		// configured when CSI Migration Complete flag is enabled for a plugin.
   285  		// CSI migration is supported even if there is version skew between
   286  		// managers and node.
   287  		return true, nil
   288  	}
   289  
   290  	if len(nodeName) == 0 {
   291  		return false, errors.New("nodeName is empty")
   292  	}
   293  
   294  	kubeClient := vpm.Host.GetKubeClient()
   295  	if kubeClient == nil {
   296  		// Don't handle the controller/kubelet version skew check and fallback
   297  		// to just checking the feature gates. This can happen if
   298  		// we are in a standalone (headless) Kubelet
   299  		return true, nil
   300  	}
   301  
   302  	adcHost, ok := vpm.Host.(volume.AttachDetachVolumeHost)
   303  	if !ok {
   304  		// Don't handle the controller/kubelet version skew check and fallback
   305  		// to just checking the feature gates. This can happen if
   306  		// "enableControllerAttachDetach" is set to true on kubelet
   307  		return true, nil
   308  	}
   309  
   310  	if adcHost.CSINodeLister() == nil {
   311  		return false, errors.New("could not find CSINodeLister in attachDetachController")
   312  	}
   313  
   314  	csiNode, err := adcHost.CSINodeLister().Get(string(nodeName))
   315  	if err != nil {
   316  		return false, err
   317  	}
   318  
   319  	ann := csiNode.GetAnnotations()
   320  	if ann == nil {
   321  		return false, nil
   322  	}
   323  
   324  	mpa := ann[v1.MigratedPluginsAnnotationKey]
   325  	tok := strings.Split(mpa, ",")
   326  	mpaSet := sets.NewString(tok...)
   327  
   328  	isMigratedOnNode := mpaSet.Has(pluginName)
   329  
   330  	return isMigratedOnNode, nil
   331  }