k8s.io/kubernetes@v1.29.3/pkg/volume/util/util.go (about) 1 /* 2 Copyright 2015 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 "context" 21 "fmt" 22 "os" 23 "path/filepath" 24 "reflect" 25 "runtime" 26 "strings" 27 "time" 28 29 v1 "k8s.io/api/core/v1" 30 storage "k8s.io/api/storage/v1" 31 "k8s.io/apimachinery/pkg/api/resource" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 apiruntime "k8s.io/apimachinery/pkg/runtime" 34 utypes "k8s.io/apimachinery/pkg/types" 35 "k8s.io/apimachinery/pkg/util/sets" 36 "k8s.io/apimachinery/pkg/util/wait" 37 utilfeature "k8s.io/apiserver/pkg/util/feature" 38 clientset "k8s.io/client-go/kubernetes" 39 storagehelpers "k8s.io/component-helpers/storage/volume" 40 "k8s.io/klog/v2" 41 "k8s.io/kubernetes/pkg/api/legacyscheme" 42 podutil "k8s.io/kubernetes/pkg/api/v1/pod" 43 "k8s.io/kubernetes/pkg/features" 44 "k8s.io/kubernetes/pkg/securitycontext" 45 "k8s.io/kubernetes/pkg/volume" 46 "k8s.io/kubernetes/pkg/volume/util/types" 47 "k8s.io/kubernetes/pkg/volume/util/volumepathhandler" 48 "k8s.io/mount-utils" 49 utilexec "k8s.io/utils/exec" 50 "k8s.io/utils/io" 51 utilstrings "k8s.io/utils/strings" 52 ) 53 54 const ( 55 readyFileName = "ready" 56 57 // ControllerManagedAttachAnnotation is the key of the annotation on Node 58 // objects that indicates attach/detach operations for the node should be 59 // managed by the attach/detach controller 60 ControllerManagedAttachAnnotation string = "volumes.kubernetes.io/controller-managed-attach-detach" 61 62 // KeepTerminatedPodVolumesAnnotation is the key of the annotation on Node 63 // that decides if pod volumes are unmounted when pod is terminated 64 KeepTerminatedPodVolumesAnnotation string = "volumes.kubernetes.io/keep-terminated-pod-volumes" 65 66 // MountsInGlobalPDPath is name of the directory appended to a volume plugin 67 // name to create the place for volume mounts in the global PD path. 68 MountsInGlobalPDPath = "mounts" 69 70 // VolumeGidAnnotationKey is the of the annotation on the PersistentVolume 71 // object that specifies a supplemental GID. 72 VolumeGidAnnotationKey = "pv.beta.kubernetes.io/gid" 73 74 // VolumeDynamicallyCreatedByKey is the key of the annotation on PersistentVolume 75 // object created dynamically 76 VolumeDynamicallyCreatedByKey = "kubernetes.io/createdby" 77 78 // kubernetesPluginPathPrefix is the prefix of kubernetes plugin mount paths. 79 kubernetesPluginPathPrefix = "/plugins/kubernetes.io/" 80 ) 81 82 // IsReady checks for the existence of a regular file 83 // called 'ready' in the given directory and returns 84 // true if that file exists. 85 func IsReady(dir string) bool { 86 readyFile := filepath.Join(dir, readyFileName) 87 s, err := os.Stat(readyFile) 88 if err != nil { 89 return false 90 } 91 92 if !s.Mode().IsRegular() { 93 klog.Errorf("ready-file is not a file: %s", readyFile) 94 return false 95 } 96 97 return true 98 } 99 100 // SetReady creates a file called 'ready' in the given 101 // directory. It logs an error if the file cannot be 102 // created. 103 func SetReady(dir string) { 104 if err := os.MkdirAll(dir, 0750); err != nil && !os.IsExist(err) { 105 klog.Errorf("Can't mkdir %s: %v", dir, err) 106 return 107 } 108 109 readyFile := filepath.Join(dir, readyFileName) 110 file, err := os.Create(readyFile) 111 if err != nil { 112 klog.Errorf("Can't touch %s: %v", readyFile, err) 113 return 114 } 115 file.Close() 116 } 117 118 // GetSecretForPod locates secret by name in the pod's namespace and returns secret map 119 func GetSecretForPod(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (map[string]string, error) { 120 secret := make(map[string]string) 121 if kubeClient == nil { 122 return secret, fmt.Errorf("cannot get kube client") 123 } 124 secrets, err := kubeClient.CoreV1().Secrets(pod.Namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) 125 if err != nil { 126 return secret, err 127 } 128 for name, data := range secrets.Data { 129 secret[name] = string(data) 130 } 131 return secret, nil 132 } 133 134 // GetSecretForPV locates secret by name and namespace, verifies the secret type, and returns secret map 135 func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeClient clientset.Interface) (map[string]string, error) { 136 secret := make(map[string]string) 137 if kubeClient == nil { 138 return secret, fmt.Errorf("cannot get kube client") 139 } 140 secrets, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) 141 if err != nil { 142 return secret, err 143 } 144 if secrets.Type != v1.SecretType(volumePluginName) { 145 return secret, fmt.Errorf("cannot get secret of type %s", volumePluginName) 146 } 147 for name, data := range secrets.Data { 148 secret[name] = string(data) 149 } 150 return secret, nil 151 } 152 153 // GetClassForVolume locates storage class by persistent volume 154 func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) (*storage.StorageClass, error) { 155 if kubeClient == nil { 156 return nil, fmt.Errorf("cannot get kube client") 157 } 158 className := storagehelpers.GetPersistentVolumeClass(pv) 159 if className == "" { 160 return nil, fmt.Errorf("volume has no storage class") 161 } 162 163 class, err := kubeClient.StorageV1().StorageClasses().Get(context.TODO(), className, metav1.GetOptions{}) 164 if err != nil { 165 return nil, err 166 } 167 return class, nil 168 } 169 170 // LoadPodFromFile will read, decode, and return a Pod from a file. 171 func LoadPodFromFile(filePath string) (*v1.Pod, error) { 172 if filePath == "" { 173 return nil, fmt.Errorf("file path not specified") 174 } 175 podDef, err := os.ReadFile(filePath) 176 if err != nil { 177 return nil, fmt.Errorf("failed to read file path %s: %+v", filePath, err) 178 } 179 if len(podDef) == 0 { 180 return nil, fmt.Errorf("file was empty: %s", filePath) 181 } 182 pod := &v1.Pod{} 183 184 codec := legacyscheme.Codecs.UniversalDecoder() 185 if err := apiruntime.DecodeInto(codec, podDef, pod); err != nil { 186 return nil, fmt.Errorf("failed decoding file: %v", err) 187 } 188 return pod, nil 189 } 190 191 // CalculateTimeoutForVolume calculates time for a Recycler pod to complete a 192 // recycle operation. The calculation and return value is either the 193 // minimumTimeout or the timeoutIncrement per Gi of storage size, whichever is 194 // greater. 195 func CalculateTimeoutForVolume(minimumTimeout, timeoutIncrement int, pv *v1.PersistentVolume) int64 { 196 giQty := resource.MustParse("1Gi") 197 pvQty := pv.Spec.Capacity[v1.ResourceStorage] 198 giSize := giQty.Value() 199 pvSize := pvQty.Value() 200 timeout := (pvSize / giSize) * int64(timeoutIncrement) 201 if timeout < int64(minimumTimeout) { 202 return int64(minimumTimeout) 203 } 204 return timeout 205 } 206 207 // GenerateVolumeName returns a PV name with clusterName prefix. The function 208 // should be used to generate a name of GCE PD or Cinder volume. It basically 209 // adds "<clusterName>-dynamic-" before the PV name, making sure the resulting 210 // string fits given length and cuts "dynamic" if not. 211 func GenerateVolumeName(clusterName, pvName string, maxLength int) string { 212 prefix := clusterName + "-dynamic" 213 pvLen := len(pvName) 214 215 // cut the "<clusterName>-dynamic" to fit full pvName into maxLength 216 // +1 for the '-' dash 217 if pvLen+1+len(prefix) > maxLength { 218 prefix = prefix[:maxLength-pvLen-1] 219 } 220 return prefix + "-" + pvName 221 } 222 223 // GetPath checks if the path from the mounter is empty. 224 func GetPath(mounter volume.Mounter) (string, error) { 225 path := mounter.GetPath() 226 if path == "" { 227 return "", fmt.Errorf("path is empty %s", reflect.TypeOf(mounter).String()) 228 } 229 return path, nil 230 } 231 232 // UnmountViaEmptyDir delegates the tear down operation for secret, configmap, git_repo and downwardapi 233 // to empty_dir 234 func UnmountViaEmptyDir(dir string, host volume.VolumeHost, volName string, volSpec volume.Spec, podUID utypes.UID) error { 235 klog.V(3).Infof("Tearing down volume %v for pod %v at %v", volName, podUID, dir) 236 237 // Wrap EmptyDir, let it do the teardown. 238 wrapped, err := host.NewWrapperUnmounter(volName, volSpec, podUID) 239 if err != nil { 240 return err 241 } 242 return wrapped.TearDownAt(dir) 243 } 244 245 // MountOptionFromSpec extracts and joins mount options from volume spec with supplied options 246 func MountOptionFromSpec(spec *volume.Spec, options ...string) []string { 247 pv := spec.PersistentVolume 248 249 if pv != nil { 250 // Use beta annotation first 251 if mo, ok := pv.Annotations[v1.MountOptionAnnotation]; ok { 252 moList := strings.Split(mo, ",") 253 return JoinMountOptions(moList, options) 254 } 255 256 if len(pv.Spec.MountOptions) > 0 { 257 return JoinMountOptions(pv.Spec.MountOptions, options) 258 } 259 } 260 261 return options 262 } 263 264 // JoinMountOptions joins mount options eliminating duplicates 265 func JoinMountOptions(userOptions []string, systemOptions []string) []string { 266 allMountOptions := sets.NewString() 267 268 for _, mountOption := range userOptions { 269 if len(mountOption) > 0 { 270 allMountOptions.Insert(mountOption) 271 } 272 } 273 274 for _, mountOption := range systemOptions { 275 allMountOptions.Insert(mountOption) 276 } 277 return allMountOptions.List() 278 } 279 280 // ContainsAccessMode returns whether the requested mode is contained by modes 281 func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { 282 for _, m := range modes { 283 if m == mode { 284 return true 285 } 286 } 287 return false 288 } 289 290 // ContainsAllAccessModes returns whether all of the requested modes are contained by modes 291 func ContainsAllAccessModes(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool { 292 for _, mode := range requestedModes { 293 if !ContainsAccessMode(indexedModes, mode) { 294 return false 295 } 296 } 297 return true 298 } 299 300 // GetWindowsPath get a windows path 301 func GetWindowsPath(path string) string { 302 windowsPath := strings.Replace(path, "/", "\\", -1) 303 if strings.HasPrefix(windowsPath, "\\") { 304 windowsPath = "c:" + windowsPath 305 } 306 return windowsPath 307 } 308 309 // GetUniquePodName returns a unique identifier to reference a pod by 310 func GetUniquePodName(pod *v1.Pod) types.UniquePodName { 311 return types.UniquePodName(pod.UID) 312 } 313 314 // GetUniqueVolumeName returns a unique name representing the volume/plugin. 315 // Caller should ensure that volumeName is a name/ID uniquely identifying the 316 // actual backing device, directory, path, etc. for a particular volume. 317 // The returned name can be used to uniquely reference the volume, for example, 318 // to prevent operations (attach/detach or mount/unmount) from being triggered 319 // on the same volume. 320 func GetUniqueVolumeName(pluginName, volumeName string) v1.UniqueVolumeName { 321 return v1.UniqueVolumeName(fmt.Sprintf("%s/%s", pluginName, volumeName)) 322 } 323 324 // GetUniqueVolumeNameFromSpecWithPod returns a unique volume name with pod 325 // name included. This is useful to generate different names for different pods 326 // on same volume. 327 func GetUniqueVolumeNameFromSpecWithPod( 328 podName types.UniquePodName, volumePlugin volume.VolumePlugin, volumeSpec *volume.Spec) v1.UniqueVolumeName { 329 return v1.UniqueVolumeName( 330 fmt.Sprintf("%s/%v-%s", volumePlugin.GetPluginName(), podName, volumeSpec.Name())) 331 } 332 333 // GetUniqueVolumeNameFromSpec uses the given VolumePlugin to generate a unique 334 // name representing the volume defined in the specified volume spec. 335 // This returned name can be used to uniquely reference the actual backing 336 // device, directory, path, etc. referenced by the given volumeSpec. 337 // If the given plugin does not support the volume spec, this returns an error. 338 func GetUniqueVolumeNameFromSpec( 339 volumePlugin volume.VolumePlugin, 340 volumeSpec *volume.Spec) (v1.UniqueVolumeName, error) { 341 if volumePlugin == nil { 342 return "", fmt.Errorf( 343 "volumePlugin should not be nil. volumeSpec.Name=%q", 344 volumeSpec.Name()) 345 } 346 347 volumeName, err := volumePlugin.GetVolumeName(volumeSpec) 348 if err != nil || volumeName == "" { 349 return "", fmt.Errorf( 350 "failed to GetVolumeName from volumePlugin for volumeSpec %q err=%v", 351 volumeSpec.Name(), 352 err) 353 } 354 355 return GetUniqueVolumeName( 356 volumePlugin.GetPluginName(), 357 volumeName), 358 nil 359 } 360 361 // IsPodTerminated checks if pod is terminated 362 func IsPodTerminated(pod *v1.Pod, podStatus v1.PodStatus) bool { 363 // TODO: the guarantees provided by kubelet status are not sufficient to guarantee it's safe to ignore a deleted pod, 364 // even if everything is notRunning (kubelet does not guarantee that when pod status is waiting that it isn't trying 365 // to start a container). 366 return podStatus.Phase == v1.PodFailed || podStatus.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(podStatus.InitContainerStatuses) && notRunning(podStatus.ContainerStatuses) && notRunning(podStatus.EphemeralContainerStatuses)) 367 } 368 369 // notRunning returns true if every status is terminated or waiting, or the status list 370 // is empty. 371 func notRunning(statuses []v1.ContainerStatus) bool { 372 for _, status := range statuses { 373 if status.State.Terminated == nil && status.State.Waiting == nil { 374 return false 375 } 376 } 377 return true 378 } 379 380 // SplitUniqueName splits the unique name to plugin name and volume name strings. It expects the uniqueName to follow 381 // the format plugin_name/volume_name and the plugin name must be namespaced as described by the plugin interface, 382 // i.e. namespace/plugin containing exactly one '/'. This means the unique name will always be in the form of 383 // plugin_namespace/plugin/volume_name, see k8s.io/kubernetes/pkg/volume/plugins.go VolumePlugin interface 384 // description and pkg/volume/util/volumehelper/volumehelper.go GetUniqueVolumeNameFromSpec that constructs 385 // the unique volume names. 386 func SplitUniqueName(uniqueName v1.UniqueVolumeName) (string, string, error) { 387 components := strings.SplitN(string(uniqueName), "/", 3) 388 if len(components) != 3 { 389 return "", "", fmt.Errorf("cannot split volume unique name %s to plugin/volume components", uniqueName) 390 } 391 pluginName := fmt.Sprintf("%s/%s", components[0], components[1]) 392 return pluginName, components[2], nil 393 } 394 395 // NewSafeFormatAndMountFromHost creates a new SafeFormatAndMount with Mounter 396 // and Exec taken from given VolumeHost. 397 func NewSafeFormatAndMountFromHost(pluginName string, host volume.VolumeHost) *mount.SafeFormatAndMount { 398 mounter := host.GetMounter(pluginName) 399 exec := host.GetExec(pluginName) 400 return &mount.SafeFormatAndMount{Interface: mounter, Exec: exec} 401 } 402 403 // GetVolumeMode retrieves VolumeMode from pv. 404 // If the volume doesn't have PersistentVolume, it's an inline volume, 405 // should return volumeMode as filesystem to keep existing behavior. 406 func GetVolumeMode(volumeSpec *volume.Spec) (v1.PersistentVolumeMode, error) { 407 if volumeSpec == nil || volumeSpec.PersistentVolume == nil { 408 return v1.PersistentVolumeFilesystem, nil 409 } 410 if volumeSpec.PersistentVolume.Spec.VolumeMode != nil { 411 return *volumeSpec.PersistentVolume.Spec.VolumeMode, nil 412 } 413 return "", fmt.Errorf("cannot get volumeMode for volume: %v", volumeSpec.Name()) 414 } 415 416 // GetPersistentVolumeClaimQualifiedName returns a qualified name for pvc. 417 func GetPersistentVolumeClaimQualifiedName(claim *v1.PersistentVolumeClaim) string { 418 return utilstrings.JoinQualifiedName(claim.GetNamespace(), claim.GetName()) 419 } 420 421 // CheckVolumeModeFilesystem checks VolumeMode. 422 // If the mode is Filesystem, return true otherwise return false. 423 func CheckVolumeModeFilesystem(volumeSpec *volume.Spec) (bool, error) { 424 volumeMode, err := GetVolumeMode(volumeSpec) 425 if err != nil { 426 return true, err 427 } 428 if volumeMode == v1.PersistentVolumeBlock { 429 return false, nil 430 } 431 return true, nil 432 } 433 434 // CheckPersistentVolumeClaimModeBlock checks VolumeMode. 435 // If the mode is Block, return true otherwise return false. 436 func CheckPersistentVolumeClaimModeBlock(pvc *v1.PersistentVolumeClaim) bool { 437 return pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == v1.PersistentVolumeBlock 438 } 439 440 // IsWindowsUNCPath checks if path is prefixed with \\ 441 // This can be used to skip any processing of paths 442 // that point to SMB shares, local named pipes and local UNC path 443 func IsWindowsUNCPath(goos, path string) bool { 444 if goos != "windows" { 445 return false 446 } 447 // Check for UNC prefix \\ 448 if strings.HasPrefix(path, `\\`) { 449 return true 450 } 451 return false 452 } 453 454 // IsWindowsLocalPath checks if path is a local path 455 // prefixed with "/" or "\" like "/foo/bar" or "\foo\bar" 456 func IsWindowsLocalPath(goos, path string) bool { 457 if goos != "windows" { 458 return false 459 } 460 if IsWindowsUNCPath(goos, path) { 461 return false 462 } 463 if strings.Contains(path, ":") { 464 return false 465 } 466 if !(strings.HasPrefix(path, `/`) || strings.HasPrefix(path, `\`)) { 467 return false 468 } 469 return true 470 } 471 472 // MakeAbsolutePath convert path to absolute path according to GOOS 473 func MakeAbsolutePath(goos, path string) string { 474 if goos != "windows" { 475 return filepath.Clean("/" + path) 476 } 477 // These are all for windows 478 // If there is a colon, give up. 479 if strings.Contains(path, ":") { 480 return path 481 } 482 // If there is a slash, but no drive, add 'c:' 483 if strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\") { 484 return "c:" + path 485 } 486 // Otherwise, add 'c:\' 487 return "c:\\" + path 488 } 489 490 // MapBlockVolume is a utility function to provide a common way of mapping 491 // block device path for a specified volume and pod. This function should be 492 // called by volume plugins that implements volume.BlockVolumeMapper.Map() method. 493 func MapBlockVolume( 494 blkUtil volumepathhandler.BlockVolumePathHandler, 495 devicePath, 496 globalMapPath, 497 podVolumeMapPath, 498 volumeMapName string, 499 podUID utypes.UID, 500 ) error { 501 // map devicePath to global node path as bind mount 502 mapErr := blkUtil.MapDevice(devicePath, globalMapPath, string(podUID), true /* bindMount */) 503 if mapErr != nil { 504 return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, globalMapPath:%s, podUID: %s, bindMount: %v: %v", 505 devicePath, globalMapPath, string(podUID), true, mapErr) 506 } 507 508 // map devicePath to pod volume path 509 mapErr = blkUtil.MapDevice(devicePath, podVolumeMapPath, volumeMapName, false /* bindMount */) 510 if mapErr != nil { 511 return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, podVolumeMapPath:%s, volumeMapName: %s, bindMount: %v: %v", 512 devicePath, podVolumeMapPath, volumeMapName, false, mapErr) 513 } 514 515 // Take file descriptor lock to keep a block device opened. Otherwise, there is a case 516 // that the block device is silently removed and attached another device with the same name. 517 // Container runtime can't handle this problem. To avoid unexpected condition fd lock 518 // for the block device is required. 519 _, mapErr = blkUtil.AttachFileDevice(filepath.Join(globalMapPath, string(podUID))) 520 if mapErr != nil { 521 return fmt.Errorf("blkUtil.AttachFileDevice failed. globalMapPath:%s, podUID: %s: %v", 522 globalMapPath, string(podUID), mapErr) 523 } 524 525 return nil 526 } 527 528 // UnmapBlockVolume is a utility function to provide a common way of unmapping 529 // block device path for a specified volume and pod. This function should be 530 // called by volume plugins that implements volume.BlockVolumeMapper.Map() method. 531 func UnmapBlockVolume( 532 blkUtil volumepathhandler.BlockVolumePathHandler, 533 globalUnmapPath, 534 podDeviceUnmapPath, 535 volumeMapName string, 536 podUID utypes.UID, 537 ) error { 538 // Release file descriptor lock. 539 err := blkUtil.DetachFileDevice(filepath.Join(globalUnmapPath, string(podUID))) 540 if err != nil { 541 return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s: %v", 542 globalUnmapPath, string(podUID), err) 543 } 544 545 // unmap devicePath from pod volume path 546 unmapDeviceErr := blkUtil.UnmapDevice(podDeviceUnmapPath, volumeMapName, false /* bindMount */) 547 if unmapDeviceErr != nil { 548 return fmt.Errorf("blkUtil.DetachFileDevice failed. podDeviceUnmapPath:%s, volumeMapName: %s, bindMount: %v: %v", 549 podDeviceUnmapPath, volumeMapName, false, unmapDeviceErr) 550 } 551 552 // unmap devicePath from global node path 553 unmapDeviceErr = blkUtil.UnmapDevice(globalUnmapPath, string(podUID), true /* bindMount */) 554 if unmapDeviceErr != nil { 555 return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s, bindMount: %v: %v", 556 globalUnmapPath, string(podUID), true, unmapDeviceErr) 557 } 558 return nil 559 } 560 561 // GetPluginMountDir returns the global mount directory name appended 562 // to the given plugin name's plugin directory 563 func GetPluginMountDir(host volume.VolumeHost, name string) string { 564 mntDir := filepath.Join(host.GetPluginDir(name), MountsInGlobalPDPath) 565 return mntDir 566 } 567 568 // IsLocalEphemeralVolume determines whether the argument is a local ephemeral 569 // volume vs. some other type 570 // Local means the volume is using storage from the local disk that is managed by kubelet. 571 // Ephemeral means the lifecycle of the volume is the same as the Pod. 572 func IsLocalEphemeralVolume(volume v1.Volume) bool { 573 return volume.GitRepo != nil || 574 (volume.EmptyDir != nil && volume.EmptyDir.Medium == v1.StorageMediumDefault) || 575 volume.ConfigMap != nil 576 } 577 578 // GetLocalPersistentVolumeNodeNames returns the node affinity node name(s) for 579 // local PersistentVolumes. nil is returned if the PV does not have any 580 // specific node affinity node selector terms and match expressions. 581 // PersistentVolume with node affinity has select and match expressions 582 // in the form of: 583 // 584 // nodeAffinity: 585 // required: 586 // nodeSelectorTerms: 587 // - matchExpressions: 588 // - key: kubernetes.io/hostname 589 // operator: In 590 // values: 591 // - <node1> 592 // - <node2> 593 func GetLocalPersistentVolumeNodeNames(pv *v1.PersistentVolume) []string { 594 if pv == nil || pv.Spec.NodeAffinity == nil || pv.Spec.NodeAffinity.Required == nil { 595 return nil 596 } 597 598 var result sets.Set[string] 599 for _, term := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms { 600 var nodes sets.Set[string] 601 for _, matchExpr := range term.MatchExpressions { 602 if matchExpr.Key == v1.LabelHostname && matchExpr.Operator == v1.NodeSelectorOpIn { 603 if nodes == nil { 604 nodes = sets.New(matchExpr.Values...) 605 } else { 606 nodes = nodes.Intersection(sets.New(matchExpr.Values...)) 607 } 608 } 609 } 610 result = result.Union(nodes) 611 } 612 613 return sets.List(result) 614 } 615 616 // GetPodVolumeNames returns names of volumes that are used in a pod, 617 // either as filesystem mount or raw block device, together with list 618 // of all SELinux contexts of all containers that use the volumes. 619 func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String, seLinuxContainerContexts map[string][]*v1.SELinuxOptions) { 620 mounts = sets.NewString() 621 devices = sets.NewString() 622 seLinuxContainerContexts = make(map[string][]*v1.SELinuxOptions) 623 624 podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(container *v1.Container, containerType podutil.ContainerType) bool { 625 var seLinuxOptions *v1.SELinuxOptions 626 if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { 627 effectiveContainerSecurity := securitycontext.DetermineEffectiveSecurityContext(pod, container) 628 if effectiveContainerSecurity != nil { 629 // No DeepCopy, SELinuxOptions is already a copy of Pod's or container's SELinuxOptions 630 seLinuxOptions = effectiveContainerSecurity.SELinuxOptions 631 } 632 } 633 634 if container.VolumeMounts != nil { 635 for _, mount := range container.VolumeMounts { 636 mounts.Insert(mount.Name) 637 if seLinuxOptions != nil { 638 seLinuxContainerContexts[mount.Name] = append(seLinuxContainerContexts[mount.Name], seLinuxOptions.DeepCopy()) 639 } 640 } 641 } 642 if container.VolumeDevices != nil { 643 for _, device := range container.VolumeDevices { 644 devices.Insert(device.Name) 645 } 646 } 647 return true 648 }) 649 return 650 } 651 652 // FsUserFrom returns FsUser of pod, which is determined by the runAsUser 653 // attributes. 654 func FsUserFrom(pod *v1.Pod) *int64 { 655 var fsUser *int64 656 // Exclude ephemeral containers because SecurityContext is not allowed. 657 podutil.VisitContainers(&pod.Spec, podutil.InitContainers|podutil.Containers, func(container *v1.Container, containerType podutil.ContainerType) bool { 658 runAsUser, ok := securitycontext.DetermineEffectiveRunAsUser(pod, container) 659 // One container doesn't specify user or there are more than one 660 // non-root UIDs. 661 if !ok || (fsUser != nil && *fsUser != *runAsUser) { 662 fsUser = nil 663 return false 664 } 665 if fsUser == nil { 666 fsUser = runAsUser 667 } 668 return true 669 }) 670 return fsUser 671 } 672 673 // HasMountRefs checks if the given mountPath has mountRefs. 674 // TODO: this is a workaround for the unmount device issue caused by gci mounter. 675 // In GCI cluster, if gci mounter is used for mounting, the container started by mounter 676 // script will cause additional mounts created in the container. Since these mounts are 677 // irrelevant to the original mounts, they should be not considered when checking the 678 // mount references. The current solution is to filter out those mount paths that contain 679 // the k8s plugin suffix of original mount path. 680 func HasMountRefs(mountPath string, mountRefs []string) bool { 681 // A mountPath typically is like 682 // /var/lib/kubelet/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX 683 // Mount refs can look like 684 // /home/somewhere/var/lib/kubelet/plugins/kubernetes.io/some-plugin/... 685 // but if /var/lib/kubelet is mounted to a different device a ref might be like 686 // /mnt/some-other-place/kubelet/plugins/kubernetes.io/some-plugin/... 687 // Neither of the above should be counted as a mount ref as those are handled 688 // by the kubelet. What we're concerned about is a path like 689 // /data/local/some/manual/mount 690 // As unmounting could interrupt usage from that mountpoint. 691 // 692 // So instead of looking for the entire /var/lib/... path, the plugins/kubernetes.io/ 693 // suffix is trimmed off and searched for. 694 // 695 // If there isn't a /plugins/... path, the whole mountPath is used instead. 696 pathToFind := mountPath 697 if i := strings.Index(mountPath, kubernetesPluginPathPrefix); i > -1 { 698 pathToFind = mountPath[i:] 699 } 700 for _, ref := range mountRefs { 701 if !strings.Contains(ref, pathToFind) { 702 return true 703 } 704 } 705 return false 706 } 707 708 // WriteVolumeCache flush disk data given the specified mount path 709 func WriteVolumeCache(deviceMountPath string, exec utilexec.Interface) error { 710 // If runtime os is windows, execute Write-VolumeCache powershell command on the disk 711 if runtime.GOOS == "windows" { 712 cmdString := "Get-Volume -FilePath $env:mountpath | Write-Volumecache" 713 cmd := exec.Command("powershell", "/c", cmdString) 714 env := append(os.Environ(), fmt.Sprintf("mountpath=%s", deviceMountPath)) 715 cmd.SetEnv(env) 716 klog.V(8).Infof("Executing command: %q", cmdString) 717 output, err := cmd.CombinedOutput() 718 klog.Infof("command (%q) execeuted: %v, output: %q", cmdString, err, string(output)) 719 if err != nil { 720 return fmt.Errorf("command (%q) failed: %v, output: %q", cmdString, err, string(output)) 721 } 722 } 723 // For linux runtime, it skips because unmount will automatically flush disk data 724 return nil 725 } 726 727 // IsMultiAttachAllowed checks if attaching this volume to multiple nodes is definitely not allowed/possible. 728 // In its current form, this function can only reliably say for which volumes it's definitely forbidden. If it returns 729 // false, it is not guaranteed that multi-attach is actually supported by the volume type and we must rely on the 730 // attacher to fail fast in such cases. 731 // Please see https://github.com/kubernetes/kubernetes/issues/40669 and https://github.com/kubernetes/kubernetes/pull/40148#discussion_r98055047 732 func IsMultiAttachAllowed(volumeSpec *volume.Spec) bool { 733 if volumeSpec == nil { 734 // we don't know if it's supported or not and let the attacher fail later in cases it's not supported 735 return true 736 } 737 738 if volumeSpec.Volume != nil { 739 // Check for volume types which are known to fail slow or cause trouble when trying to multi-attach 740 if volumeSpec.Volume.AzureDisk != nil || 741 volumeSpec.Volume.Cinder != nil { 742 return false 743 } 744 } 745 746 // Only if this volume is a persistent volume, we have reliable information on whether it's allowed or not to 747 // multi-attach. We trust in the individual volume implementations to not allow unsupported access modes 748 if volumeSpec.PersistentVolume != nil { 749 // Check for persistent volume types which do not fail when trying to multi-attach 750 if len(volumeSpec.PersistentVolume.Spec.AccessModes) == 0 { 751 // No access mode specified so we don't know for sure. Let the attacher fail if needed 752 return true 753 } 754 755 // check if this volume is allowed to be attached to multiple PODs/nodes, if yes, return false 756 for _, accessMode := range volumeSpec.PersistentVolume.Spec.AccessModes { 757 if accessMode == v1.ReadWriteMany || accessMode == v1.ReadOnlyMany { 758 return true 759 } 760 } 761 return false 762 } 763 764 // we don't know if it's supported or not and let the attacher fail later in cases it's not supported 765 return true 766 } 767 768 // IsAttachableVolume checks if the given volumeSpec is an attachable volume or not 769 func IsAttachableVolume(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) bool { 770 attachableVolumePlugin, _ := volumePluginMgr.FindAttachablePluginBySpec(volumeSpec) 771 if attachableVolumePlugin != nil { 772 volumeAttacher, err := attachableVolumePlugin.NewAttacher() 773 if err == nil && volumeAttacher != nil { 774 return true 775 } 776 } 777 778 return false 779 } 780 781 // IsDeviceMountableVolume checks if the given volumeSpec is an device mountable volume or not 782 func IsDeviceMountableVolume(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) bool { 783 deviceMountableVolumePlugin, _ := volumePluginMgr.FindDeviceMountablePluginBySpec(volumeSpec) 784 if deviceMountableVolumePlugin != nil { 785 volumeDeviceMounter, err := deviceMountableVolumePlugin.NewDeviceMounter() 786 if err == nil && volumeDeviceMounter != nil { 787 return true 788 } 789 } 790 791 return false 792 } 793 794 // GetReliableMountRefs calls mounter.GetMountRefs and retries on IsInconsistentReadError. 795 // To be used in volume reconstruction of volume plugins that don't have any protection 796 // against mounting a single volume on multiple nodes (such as attach/detach). 797 func GetReliableMountRefs(mounter mount.Interface, mountPath string) ([]string, error) { 798 var paths []string 799 var lastErr error 800 err := wait.PollImmediate(10*time.Millisecond, time.Minute, func() (bool, error) { 801 var err error 802 paths, err = mounter.GetMountRefs(mountPath) 803 if io.IsInconsistentReadError(err) { 804 lastErr = err 805 return false, nil 806 } 807 if err != nil { 808 return false, err 809 } 810 return true, nil 811 }) 812 if err == wait.ErrWaitTimeout { 813 return nil, lastErr 814 } 815 return paths, err 816 }