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 }