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