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 }