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  }