k8s.io/kubernetes@v1.29.3/pkg/volume/azure_file/azure_file.go (about)

     1  //go:build !providerless
     2  // +build !providerless
     3  
     4  /*
     5  Copyright 2016 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package azure_file
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"runtime"
    27  	"time"
    28  
    29  	"k8s.io/klog/v2"
    30  	"k8s.io/mount-utils"
    31  	utilstrings "k8s.io/utils/strings"
    32  
    33  	v1 "k8s.io/api/core/v1"
    34  	"k8s.io/apimachinery/pkg/api/resource"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/util/wait"
    38  	cloudprovider "k8s.io/cloud-provider"
    39  	volumehelpers "k8s.io/cloud-provider/volume/helpers"
    40  	"k8s.io/kubernetes/pkg/volume"
    41  	volutil "k8s.io/kubernetes/pkg/volume/util"
    42  	"k8s.io/legacy-cloud-providers/azure"
    43  )
    44  
    45  // ProbeVolumePlugins is the primary endpoint for volume plugins
    46  func ProbeVolumePlugins() []volume.VolumePlugin {
    47  	return []volume.VolumePlugin{&azureFilePlugin{nil}}
    48  }
    49  
    50  type azureFilePlugin struct {
    51  	host volume.VolumeHost
    52  }
    53  
    54  var _ volume.VolumePlugin = &azureFilePlugin{}
    55  var _ volume.PersistentVolumePlugin = &azureFilePlugin{}
    56  var _ volume.ExpandableVolumePlugin = &azureFilePlugin{}
    57  
    58  const (
    59  	azureFilePluginName     = "kubernetes.io/azure-file"
    60  	minimumPremiumShareSize = 100 // GB
    61  )
    62  
    63  func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
    64  	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(azureFilePluginName), volName)
    65  }
    66  
    67  func (plugin *azureFilePlugin) Init(host volume.VolumeHost) error {
    68  	plugin.host = host
    69  	return nil
    70  }
    71  
    72  func (plugin *azureFilePlugin) GetPluginName() string {
    73  	return azureFilePluginName
    74  }
    75  
    76  func (plugin *azureFilePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
    77  	share, _, err := getVolumeSource(spec)
    78  	if err != nil {
    79  		return "", err
    80  	}
    81  
    82  	return share, nil
    83  }
    84  
    85  func (plugin *azureFilePlugin) CanSupport(spec *volume.Spec) bool {
    86  	//TODO: check if mount.cifs is there
    87  	return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.AzureFile != nil) ||
    88  		(spec.Volume != nil && spec.Volume.AzureFile != nil)
    89  }
    90  
    91  func (plugin *azureFilePlugin) RequiresRemount(spec *volume.Spec) bool {
    92  	return false
    93  }
    94  
    95  func (plugin *azureFilePlugin) SupportsMountOption() bool {
    96  	return true
    97  }
    98  
    99  func (plugin *azureFilePlugin) SupportsBulkVolumeVerification() bool {
   100  	return false
   101  }
   102  
   103  func (plugin *azureFilePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
   104  	return false, nil
   105  }
   106  
   107  func (plugin *azureFilePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
   108  	return []v1.PersistentVolumeAccessMode{
   109  		v1.ReadWriteOnce,
   110  		v1.ReadOnlyMany,
   111  		v1.ReadWriteMany,
   112  	}
   113  }
   114  
   115  func (plugin *azureFilePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
   116  	return plugin.newMounterInternal(spec, pod, &azureSvc{}, plugin.host.GetMounter(plugin.GetPluginName()))
   117  }
   118  
   119  func (plugin *azureFilePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, util azureUtil, mounter mount.Interface) (volume.Mounter, error) {
   120  	share, readOnly, err := getVolumeSource(spec)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	secretName, secretNamespace, err := getSecretNameAndNamespace(spec, pod.Namespace)
   125  	if err != nil {
   126  		// Log-and-continue instead of returning an error for now
   127  		// due to unspecified backwards compatibility concerns (a subject to revise)
   128  		klog.Errorf("get secret name and namespace from pod(%s) return with error: %v", pod.Name, err)
   129  	}
   130  	return &azureFileMounter{
   131  		azureFile: &azureFile{
   132  			volName:         spec.Name(),
   133  			mounter:         mounter,
   134  			pod:             pod,
   135  			plugin:          plugin,
   136  			MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, spec.Name(), plugin.host)),
   137  		},
   138  		util:            util,
   139  		secretNamespace: secretNamespace,
   140  		secretName:      secretName,
   141  		shareName:       share,
   142  		readOnly:        readOnly,
   143  		mountOptions:    volutil.MountOptionFromSpec(spec),
   144  	}, nil
   145  }
   146  
   147  func (plugin *azureFilePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
   148  	return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()))
   149  }
   150  
   151  func (plugin *azureFilePlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
   152  	return &azureFileUnmounter{&azureFile{
   153  		volName:         volName,
   154  		mounter:         mounter,
   155  		pod:             &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
   156  		plugin:          plugin,
   157  		MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
   158  	}}, nil
   159  }
   160  
   161  func (plugin *azureFilePlugin) RequiresFSResize() bool {
   162  	return false
   163  }
   164  
   165  func (plugin *azureFilePlugin) ExpandVolumeDevice(
   166  	spec *volume.Spec,
   167  	newSize resource.Quantity,
   168  	oldSize resource.Quantity) (resource.Quantity, error) {
   169  
   170  	if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.AzureFile == nil {
   171  		return oldSize, fmt.Errorf("invalid PV spec")
   172  	}
   173  	shareName := spec.PersistentVolume.Spec.AzureFile.ShareName
   174  	azure, resourceGroup, err := getAzureCloudProvider(plugin.host.GetCloudProvider())
   175  	if err != nil {
   176  		return oldSize, err
   177  	}
   178  	if spec.PersistentVolume.ObjectMeta.Annotations[resourceGroupAnnotation] != "" {
   179  		resourceGroup = spec.PersistentVolume.ObjectMeta.Annotations[resourceGroupAnnotation]
   180  	}
   181  
   182  	secretName, secretNamespace, err := getSecretNameAndNamespace(spec, spec.PersistentVolume.Spec.ClaimRef.Namespace)
   183  	if err != nil {
   184  		return oldSize, err
   185  	}
   186  
   187  	accountName, _, err := (&azureSvc{}).GetAzureCredentials(plugin.host, secretNamespace, secretName)
   188  	if err != nil {
   189  		return oldSize, err
   190  	}
   191  
   192  	requestGiB, err := volumehelpers.RoundUpToGiBInt(newSize)
   193  
   194  	if err != nil {
   195  		return oldSize, err
   196  	}
   197  
   198  	if err := azure.ResizeFileShare(resourceGroup, accountName, shareName, requestGiB); err != nil {
   199  		return oldSize, err
   200  	}
   201  
   202  	return newSize, nil
   203  }
   204  
   205  func (plugin *azureFilePlugin) ConstructVolumeSpec(volName, mountPath string) (volume.ReconstructedVolume, error) {
   206  	azureVolume := &v1.Volume{
   207  		Name: volName,
   208  		VolumeSource: v1.VolumeSource{
   209  			AzureFile: &v1.AzureFileVolumeSource{
   210  				SecretName: volName,
   211  				ShareName:  volName,
   212  			},
   213  		},
   214  	}
   215  	return volume.ReconstructedVolume{
   216  		Spec: volume.NewSpecFromVolume(azureVolume),
   217  	}, nil
   218  }
   219  
   220  // azureFile volumes represent mount of an AzureFile share.
   221  type azureFile struct {
   222  	volName string
   223  	podUID  types.UID
   224  	pod     *v1.Pod
   225  	mounter mount.Interface
   226  	plugin  *azureFilePlugin
   227  	volume.MetricsProvider
   228  }
   229  
   230  func (azureFileVolume *azureFile) GetPath() string {
   231  	return getPath(azureFileVolume.pod.UID, azureFileVolume.volName, azureFileVolume.plugin.host)
   232  }
   233  
   234  type azureFileMounter struct {
   235  	*azureFile
   236  	util            azureUtil
   237  	secretName      string
   238  	secretNamespace string
   239  	shareName       string
   240  	readOnly        bool
   241  	mountOptions    []string
   242  }
   243  
   244  var _ volume.Mounter = &azureFileMounter{}
   245  
   246  func (b *azureFileMounter) GetAttributes() volume.Attributes {
   247  	return volume.Attributes{
   248  		ReadOnly:       b.readOnly,
   249  		Managed:        !b.readOnly,
   250  		SELinuxRelabel: false,
   251  	}
   252  }
   253  
   254  // SetUp attaches the disk and bind mounts to the volume path.
   255  func (b *azureFileMounter) SetUp(mounterArgs volume.MounterArgs) error {
   256  	return b.SetUpAt(b.GetPath(), mounterArgs)
   257  }
   258  
   259  func (b *azureFileMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
   260  	notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
   261  	klog.V(4).Infof("AzureFile mount set up: %s %v %v", dir, !notMnt, err)
   262  	if err != nil && !os.IsNotExist(err) {
   263  		return err
   264  	}
   265  	if !notMnt {
   266  		// testing original mount point, make sure the mount link is valid
   267  		if _, err := ioutil.ReadDir(dir); err == nil {
   268  			klog.V(4).Infof("azureFile - already mounted to target %s", dir)
   269  			return nil
   270  		}
   271  		// mount link is invalid, now unmount and remount later
   272  		klog.Warningf("azureFile - ReadDir %s failed with %v, unmount this directory", dir, err)
   273  		if err := b.mounter.Unmount(dir); err != nil {
   274  			klog.Errorf("azureFile - Unmount directory %s failed with %v", dir, err)
   275  			return err
   276  		}
   277  	}
   278  
   279  	var accountKey, accountName string
   280  	if accountName, accountKey, err = b.util.GetAzureCredentials(b.plugin.host, b.secretNamespace, b.secretName); err != nil {
   281  		return err
   282  	}
   283  
   284  	var mountOptions []string
   285  	var sensitiveMountOptions []string
   286  	source := ""
   287  	osSeparator := string(os.PathSeparator)
   288  	source = fmt.Sprintf("%s%s%s.file.%s%s%s", osSeparator, osSeparator, accountName, getStorageEndpointSuffix(b.plugin.host.GetCloudProvider()), osSeparator, b.shareName)
   289  
   290  	if runtime.GOOS == "windows" {
   291  		mountOptions = []string{fmt.Sprintf("AZURE\\%s", accountName)}
   292  		sensitiveMountOptions = []string{accountKey}
   293  	} else {
   294  		if err := os.MkdirAll(dir, 0700); err != nil {
   295  			return err
   296  		}
   297  		// parameters suggested by https://azure.microsoft.com/en-us/documentation/articles/storage-how-to-use-files-linux/
   298  		options := []string{}
   299  		sensitiveMountOptions = []string{fmt.Sprintf("username=%s,password=%s", accountName, accountKey)}
   300  		if b.readOnly {
   301  			options = append(options, "ro")
   302  		}
   303  		mountOptions = volutil.JoinMountOptions(b.mountOptions, options)
   304  		mountOptions = appendDefaultMountOptions(mountOptions, mounterArgs.FsGroup)
   305  	}
   306  
   307  	mountComplete := false
   308  	err = wait.PollImmediate(1*time.Second, 2*time.Minute, func() (bool, error) {
   309  		err := b.mounter.MountSensitiveWithoutSystemd(source, dir, "cifs", mountOptions, sensitiveMountOptions)
   310  		mountComplete = true
   311  		return true, err
   312  	})
   313  	if !mountComplete {
   314  		return fmt.Errorf("volume(%s) mount on %s timeout(10m)", source, dir)
   315  	}
   316  	if err != nil {
   317  		notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
   318  		if mntErr != nil {
   319  			klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
   320  			return err
   321  		}
   322  		if !notMnt {
   323  			if mntErr = b.mounter.Unmount(dir); mntErr != nil {
   324  				klog.Errorf("Failed to unmount: %v", mntErr)
   325  				return err
   326  			}
   327  			notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
   328  			if mntErr != nil {
   329  				klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
   330  				return err
   331  			}
   332  			if !notMnt {
   333  				// This is very odd, we don't expect it.  We'll try again next sync loop.
   334  				klog.Errorf("%s is still mounted, despite call to unmount().  Will try again next sync loop.", dir)
   335  				return err
   336  			}
   337  		}
   338  		os.Remove(dir)
   339  		return err
   340  	}
   341  	return nil
   342  }
   343  
   344  var _ volume.Unmounter = &azureFileUnmounter{}
   345  
   346  type azureFileUnmounter struct {
   347  	*azureFile
   348  }
   349  
   350  func (c *azureFileUnmounter) TearDown() error {
   351  	return c.TearDownAt(c.GetPath())
   352  }
   353  
   354  func (c *azureFileUnmounter) TearDownAt(dir string) error {
   355  	return mount.CleanupMountPoint(dir, c.mounter, false)
   356  }
   357  
   358  func getVolumeSource(spec *volume.Spec) (string, bool, error) {
   359  	if spec.Volume != nil && spec.Volume.AzureFile != nil {
   360  		share := spec.Volume.AzureFile.ShareName
   361  		readOnly := spec.Volume.AzureFile.ReadOnly
   362  		return share, readOnly, nil
   363  	} else if spec.PersistentVolume != nil &&
   364  		spec.PersistentVolume.Spec.AzureFile != nil {
   365  		share := spec.PersistentVolume.Spec.AzureFile.ShareName
   366  		readOnly := spec.ReadOnly
   367  		return share, readOnly, nil
   368  	}
   369  	return "", false, fmt.Errorf("Spec does not reference an AzureFile volume type")
   370  }
   371  
   372  func getSecretNameAndNamespace(spec *volume.Spec, defaultNamespace string) (string, string, error) {
   373  	secretName := ""
   374  	secretNamespace := ""
   375  	if spec.Volume != nil && spec.Volume.AzureFile != nil {
   376  		secretName = spec.Volume.AzureFile.SecretName
   377  		secretNamespace = defaultNamespace
   378  
   379  	} else if spec.PersistentVolume != nil &&
   380  		spec.PersistentVolume.Spec.AzureFile != nil {
   381  		secretNamespace = defaultNamespace
   382  		if spec.PersistentVolume.Spec.AzureFile.SecretNamespace != nil {
   383  			secretNamespace = *spec.PersistentVolume.Spec.AzureFile.SecretNamespace
   384  		}
   385  		secretName = spec.PersistentVolume.Spec.AzureFile.SecretName
   386  	} else {
   387  		return "", "", fmt.Errorf("Spec does not reference an AzureFile volume type")
   388  	}
   389  
   390  	if len(secretNamespace) == 0 {
   391  		return "", "", fmt.Errorf("invalid Azure volume: nil namespace")
   392  	}
   393  	return secretName, secretNamespace, nil
   394  
   395  }
   396  
   397  func getAzureCloud(cloudProvider cloudprovider.Interface) (*azure.Cloud, error) {
   398  	azure, ok := cloudProvider.(*azure.Cloud)
   399  	if !ok || azure == nil {
   400  		return nil, fmt.Errorf("failed to get Azure Cloud Provider. GetCloudProvider returned %v instead", cloudProvider)
   401  	}
   402  
   403  	return azure, nil
   404  }
   405  
   406  func getStorageEndpointSuffix(cloudprovider cloudprovider.Interface) string {
   407  	const publicCloudStorageEndpointSuffix = "core.windows.net"
   408  	azure, err := getAzureCloud(cloudprovider)
   409  	if err != nil {
   410  		klog.Warningf("No Azure cloud provider found. Using the Azure public cloud endpoint: %s", publicCloudStorageEndpointSuffix)
   411  		return publicCloudStorageEndpointSuffix
   412  	}
   413  	return azure.Environment.StorageEndpointSuffix
   414  }