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