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 }