...

Source file src/k8s.io/kubernetes/pkg/volume/util/util.go

Documentation: k8s.io/kubernetes/pkg/volume/util

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

View as plain text