k8s.io/kubernetes@v1.29.3/pkg/volume/fc/fc.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 fc 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "strconv" 24 "strings" 25 26 utilfeature "k8s.io/apiserver/pkg/util/feature" 27 "k8s.io/klog/v2" 28 "k8s.io/kubernetes/pkg/features" 29 "k8s.io/mount-utils" 30 utilexec "k8s.io/utils/exec" 31 "k8s.io/utils/io" 32 utilstrings "k8s.io/utils/strings" 33 34 v1 "k8s.io/api/core/v1" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/kubernetes/pkg/volume" 38 "k8s.io/kubernetes/pkg/volume/util" 39 "k8s.io/kubernetes/pkg/volume/util/volumepathhandler" 40 ) 41 42 // ProbeVolumePlugins is the primary entrypoint for volume plugins. 43 func ProbeVolumePlugins() []volume.VolumePlugin { 44 return []volume.VolumePlugin{&fcPlugin{nil}} 45 } 46 47 type fcPlugin struct { 48 host volume.VolumeHost 49 } 50 51 var _ volume.VolumePlugin = &fcPlugin{} 52 var _ volume.PersistentVolumePlugin = &fcPlugin{} 53 var _ volume.BlockVolumePlugin = &fcPlugin{} 54 55 const ( 56 fcPluginName = "kubernetes.io/fc" 57 ) 58 59 func (plugin *fcPlugin) Init(host volume.VolumeHost) error { 60 plugin.host = host 61 return nil 62 } 63 64 func (plugin *fcPlugin) GetPluginName() string { 65 return fcPluginName 66 } 67 68 func (plugin *fcPlugin) GetVolumeName(spec *volume.Spec) (string, error) { 69 volumeSource, _, err := getVolumeSource(spec) 70 if err != nil { 71 return "", err 72 } 73 74 // API server validates these parameters beforehand but attach/detach 75 // controller creates volumespec without validation. They may be nil 76 // or zero length. We should check again to avoid unexpected conditions. 77 if len(volumeSource.TargetWWNs) != 0 && volumeSource.Lun != nil { 78 // TargetWWNs are the FibreChannel target worldwide names 79 return fmt.Sprintf("%v:%v", volumeSource.TargetWWNs, *volumeSource.Lun), nil 80 } else if len(volumeSource.WWIDs) != 0 { 81 // WWIDs are the FibreChannel World Wide Identifiers 82 return fmt.Sprintf("%v", volumeSource.WWIDs), nil 83 } 84 85 return "", err 86 } 87 88 func (plugin *fcPlugin) CanSupport(spec *volume.Spec) bool { 89 return (spec.Volume != nil && spec.Volume.FC != nil) || (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.FC != nil) 90 } 91 92 func (plugin *fcPlugin) RequiresRemount(spec *volume.Spec) bool { 93 return false 94 } 95 96 func (plugin *fcPlugin) SupportsMountOption() bool { 97 return true 98 } 99 100 func (plugin *fcPlugin) SupportsBulkVolumeVerification() bool { 101 return false 102 } 103 104 func (plugin *fcPlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) { 105 return true, nil 106 } 107 108 func (plugin *fcPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { 109 return []v1.PersistentVolumeAccessMode{ 110 v1.ReadWriteOnce, 111 v1.ReadOnlyMany, 112 } 113 } 114 115 func (plugin *fcPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { 116 // Inject real implementations here, test through the internal function. 117 return plugin.newMounterInternal(spec, pod.UID, &fcUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName())) 118 } 119 120 func (plugin *fcPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, exec utilexec.Interface) (volume.Mounter, error) { 121 // fc volumes used directly in a pod have a ReadOnly flag set by the pod author. 122 // fc volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV 123 fc, readOnly, err := getVolumeSource(spec) 124 if err != nil { 125 return nil, err 126 } 127 128 wwns, lun, wwids, err := getWwnsLunWwids(fc) 129 if err != nil { 130 return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mounter") 131 } 132 fcDisk := &fcDisk{ 133 podUID: podUID, 134 volName: spec.Name(), 135 wwns: wwns, 136 lun: lun, 137 wwids: wwids, 138 manager: manager, 139 io: &osIOHandler{}, 140 plugin: plugin, 141 } 142 143 volumeMode, err := util.GetVolumeMode(spec) 144 if err != nil { 145 return nil, err 146 } 147 148 klog.V(5).Infof("fc: newMounterInternal volumeMode %s", volumeMode) 149 return &fcDiskMounter{ 150 fcDisk: fcDisk, 151 fsType: fc.FSType, 152 volumeMode: volumeMode, 153 readOnly: readOnly, 154 mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, 155 deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), 156 mountOptions: util.MountOptionFromSpec(spec), 157 }, nil 158 } 159 160 func (plugin *fcPlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) { 161 // If this called via GenerateUnmapDeviceFunc(), pod is nil. 162 // Pass empty string as dummy uid since uid isn't used in the case. 163 var uid types.UID 164 if pod != nil { 165 uid = pod.UID 166 } 167 return plugin.newBlockVolumeMapperInternal(spec, uid, &fcUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName())) 168 } 169 170 func (plugin *fcPlugin) newBlockVolumeMapperInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, exec utilexec.Interface) (volume.BlockVolumeMapper, error) { 171 fc, readOnly, err := getVolumeSource(spec) 172 if err != nil { 173 return nil, err 174 } 175 176 wwns, lun, wwids, err := getWwnsLunWwids(fc) 177 if err != nil { 178 return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mapper") 179 } 180 181 mapper := &fcDiskMapper{ 182 fcDisk: &fcDisk{ 183 podUID: podUID, 184 volName: spec.Name(), 185 wwns: wwns, 186 lun: lun, 187 wwids: wwids, 188 manager: manager, 189 io: &osIOHandler{}, 190 plugin: plugin}, 191 readOnly: readOnly, 192 mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, 193 deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), 194 } 195 196 blockPath, err := mapper.GetGlobalMapPath(spec) 197 if err != nil { 198 return nil, fmt.Errorf("failed to get device path: %v", err) 199 } 200 mapper.MetricsProvider = volume.NewMetricsBlock(filepath.Join(blockPath, string(podUID))) 201 202 return mapper, nil 203 } 204 205 func (plugin *fcPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { 206 // Inject real implementations here, test through the internal function. 207 return plugin.newUnmounterInternal(volName, podUID, &fcUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName())) 208 } 209 210 func (plugin *fcPlugin) newUnmounterInternal(volName string, podUID types.UID, manager diskManager, mounter mount.Interface, exec utilexec.Interface) (volume.Unmounter, error) { 211 return &fcDiskUnmounter{ 212 fcDisk: &fcDisk{ 213 podUID: podUID, 214 volName: volName, 215 manager: manager, 216 plugin: plugin, 217 io: &osIOHandler{}, 218 }, 219 mounter: mounter, 220 deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), 221 exec: exec, 222 }, nil 223 } 224 225 func (plugin *fcPlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) { 226 return plugin.newUnmapperInternal(volName, podUID, &fcUtil{}, plugin.host.GetExec(plugin.GetPluginName())) 227 } 228 229 func (plugin *fcPlugin) newUnmapperInternal(volName string, podUID types.UID, manager diskManager, exec utilexec.Interface) (volume.BlockVolumeUnmapper, error) { 230 return &fcDiskUnmapper{ 231 fcDisk: &fcDisk{ 232 podUID: podUID, 233 volName: volName, 234 manager: manager, 235 plugin: plugin, 236 io: &osIOHandler{}, 237 }, 238 exec: exec, 239 deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), 240 }, nil 241 } 242 243 func (plugin *fcPlugin) ConstructVolumeSpec(volumeName, mountPath string) (volume.ReconstructedVolume, error) { 244 // Find globalPDPath from pod volume directory(mountPath) 245 // examples: 246 // mountPath: pods/{podUid}/volumes/kubernetes.io~fc/{volumeName} 247 // globalPDPath : plugins/kubernetes.io/fc/50060e801049cfd1-lun-0 248 var globalPDPath string 249 250 mounter := plugin.host.GetMounter(plugin.GetPluginName()) 251 // Try really hard to get the global mount of the volume, an error returned from here would 252 // leave the global mount still mounted, while marking the volume as unused. 253 // The volume can then be mounted on several nodes, resulting in volume 254 // corruption. 255 paths, err := util.GetReliableMountRefs(mounter, mountPath) 256 if io.IsInconsistentReadError(err) { 257 klog.Errorf("Failed to read mount refs from /proc/mounts for %s: %s", mountPath, err) 258 klog.Errorf("Kubelet cannot unmount volume at %s, please unmount it manually", mountPath) 259 return volume.ReconstructedVolume{}, err 260 } 261 if err != nil { 262 return volume.ReconstructedVolume{}, err 263 } 264 for _, path := range paths { 265 if strings.Contains(path, plugin.host.GetPluginDir(fcPluginName)) { 266 globalPDPath = path 267 break 268 } 269 } 270 // Couldn't fetch globalPDPath 271 if len(globalPDPath) == 0 { 272 return volume.ReconstructedVolume{}, fmt.Errorf("couldn't fetch globalPDPath. failed to obtain volume spec") 273 } 274 275 wwns, lun, wwids, err := parsePDName(globalPDPath) 276 if err != nil { 277 return volume.ReconstructedVolume{}, fmt.Errorf("failed to retrieve volume plugin information from globalPDPath: %s", err) 278 } 279 // Create volume from wwn+lun or wwid 280 fcVolume := &v1.Volume{ 281 Name: volumeName, 282 VolumeSource: v1.VolumeSource{ 283 FC: &v1.FCVolumeSource{WWIDs: wwids, Lun: &lun, TargetWWNs: wwns}, 284 }, 285 } 286 287 var mountContext string 288 if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { 289 kvh, ok := plugin.host.(volume.KubeletVolumeHost) 290 if !ok { 291 return volume.ReconstructedVolume{}, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") 292 } 293 hu := kvh.GetHostUtil() 294 mountContext, err = hu.GetSELinuxMountContext(mountPath) 295 if err != nil { 296 return volume.ReconstructedVolume{}, err 297 } 298 } 299 300 klog.V(5).Infof("ConstructVolumeSpec: TargetWWNs: %v, Lun: %v, WWIDs: %v", 301 fcVolume.VolumeSource.FC.TargetWWNs, *fcVolume.VolumeSource.FC.Lun, fcVolume.VolumeSource.FC.WWIDs) 302 return volume.ReconstructedVolume{ 303 Spec: volume.NewSpecFromVolume(fcVolume), 304 SELinuxMountContext: mountContext, 305 }, nil 306 } 307 308 // ConstructBlockVolumeSpec creates a new volume.Spec with following steps. 309 // - Searches a file whose name is {pod uuid} under volume plugin directory. 310 // - If a file is found, then retrieves volumePluginDependentPath from globalMapPathUUID. 311 // - Once volumePluginDependentPath is obtained, store volume information to VolumeSource 312 // 313 // examples: 314 // 315 // mapPath: pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName} 316 // globalMapPathUUID : plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid} 317 func (plugin *fcPlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mapPath string) (*volume.Spec, error) { 318 pluginDir := plugin.host.GetVolumeDevicePluginDir(fcPluginName) 319 blkutil := volumepathhandler.NewBlockVolumePathHandler() 320 globalMapPathUUID, err := blkutil.FindGlobalMapPathUUIDFromPod(pluginDir, mapPath, podUID) 321 if err != nil { 322 return nil, err 323 } 324 klog.V(5).Infof("globalMapPathUUID: %v, err: %v", globalMapPathUUID, err) 325 326 // Retrieve globalPDPath from globalMapPathUUID 327 // globalMapPathUUID examples: 328 // wwn+lun: plugins/kubernetes.io/fc/volumeDevices/50060e801049cfd1-lun-0/{pod uuid} 329 // wwid: plugins/kubernetes.io/fc/volumeDevices/3600508b400105e210000900000490000/{pod uuid} 330 globalPDPath := filepath.Dir(globalMapPathUUID) 331 // Create volume from wwn+lun or wwid 332 wwns, lun, wwids, err := parsePDName(globalPDPath) 333 if err != nil { 334 return nil, fmt.Errorf("failed to retrieve volume plugin information from globalPDPath: %s", err) 335 } 336 fcPV := createPersistentVolumeFromFCVolumeSource(volumeName, 337 v1.FCVolumeSource{TargetWWNs: wwns, Lun: &lun, WWIDs: wwids}) 338 klog.V(5).Infof("ConstructBlockVolumeSpec: TargetWWNs: %v, Lun: %v, WWIDs: %v", 339 fcPV.Spec.PersistentVolumeSource.FC.TargetWWNs, 340 *fcPV.Spec.PersistentVolumeSource.FC.Lun, 341 fcPV.Spec.PersistentVolumeSource.FC.WWIDs) 342 343 return volume.NewSpecFromPersistentVolume(fcPV, false), nil 344 } 345 346 type fcDisk struct { 347 volName string 348 podUID types.UID 349 wwns []string 350 lun string 351 wwids []string 352 plugin *fcPlugin 353 // Utility interface that provides API calls to the provider to attach/detach disks. 354 manager diskManager 355 // io handler interface 356 io ioHandler 357 volume.MetricsNil 358 } 359 360 func (fc *fcDisk) GetPath() string { 361 // safe to use PodVolumeDir now: volume teardown occurs before pod is cleaned up 362 return fc.plugin.host.GetPodVolumeDir(fc.podUID, utilstrings.EscapeQualifiedName(fcPluginName), fc.volName) 363 } 364 365 func (fc *fcDisk) fcGlobalMapPath(spec *volume.Spec) (string, error) { 366 mounter, err := volumeSpecToMounter(spec, fc.plugin.host) 367 if err != nil { 368 klog.Warningf("failed to get fc mounter: %v", err) 369 return "", err 370 } 371 return fc.manager.MakeGlobalVDPDName(*mounter.fcDisk), nil 372 } 373 374 func (fc *fcDisk) fcPodDeviceMapPath() (string, string) { 375 return fc.plugin.host.GetPodVolumeDeviceDir(fc.podUID, utilstrings.EscapeQualifiedName(fcPluginName)), fc.volName 376 } 377 378 type fcDiskMounter struct { 379 *fcDisk 380 readOnly bool 381 fsType string 382 volumeMode v1.PersistentVolumeMode 383 mounter *mount.SafeFormatAndMount 384 deviceUtil util.DeviceUtil 385 mountOptions []string 386 mountedWithSELinuxContext bool 387 } 388 389 var _ volume.Mounter = &fcDiskMounter{} 390 391 func (b *fcDiskMounter) GetAttributes() volume.Attributes { 392 return volume.Attributes{ 393 ReadOnly: b.readOnly, 394 Managed: !b.readOnly, 395 SELinuxRelabel: !b.mountedWithSELinuxContext, 396 } 397 } 398 399 func (b *fcDiskMounter) SetUp(mounterArgs volume.MounterArgs) error { 400 return b.SetUpAt(b.GetPath(), mounterArgs) 401 } 402 403 func (b *fcDiskMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 404 // diskSetUp checks mountpoints and prevent repeated calls 405 err := diskSetUp(b.manager, *b, dir, b.mounter, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy) 406 if err != nil { 407 klog.Errorf("fc: failed to setup") 408 } 409 410 if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) { 411 // The volume must have been mounted in MountDevice with -o context. 412 b.mountedWithSELinuxContext = mounterArgs.SELinuxLabel != "" 413 } 414 return err 415 } 416 417 type fcDiskUnmounter struct { 418 *fcDisk 419 mounter mount.Interface 420 deviceUtil util.DeviceUtil 421 exec utilexec.Interface 422 } 423 424 var _ volume.Unmounter = &fcDiskUnmounter{} 425 426 // Unmounts the bind mount, and detaches the disk only if the disk 427 // resource was the last reference to that disk on the kubelet. 428 func (c *fcDiskUnmounter) TearDown() error { 429 return c.TearDownAt(c.GetPath()) 430 } 431 432 func (c *fcDiskUnmounter) TearDownAt(dir string) error { 433 return mount.CleanupMountPoint(dir, c.mounter, false) 434 } 435 436 // Block Volumes Support 437 type fcDiskMapper struct { 438 *fcDisk 439 volume.MetricsProvider 440 readOnly bool 441 mounter mount.Interface 442 deviceUtil util.DeviceUtil 443 } 444 445 var _ volume.BlockVolumeMapper = &fcDiskMapper{} 446 447 type fcDiskUnmapper struct { 448 *fcDisk 449 deviceUtil util.DeviceUtil 450 exec utilexec.Interface 451 } 452 453 var _ volume.BlockVolumeUnmapper = &fcDiskUnmapper{} 454 var _ volume.CustomBlockVolumeUnmapper = &fcDiskUnmapper{} 455 456 func (c *fcDiskUnmapper) TearDownDevice(mapPath, devicePath string) error { 457 err := c.manager.DetachBlockFCDisk(*c, mapPath, devicePath) 458 if err != nil { 459 return fmt.Errorf("fc: failed to detach disk: %s\nError: %v", mapPath, err) 460 } 461 klog.V(4).Infof("fc: %s is unmounted, deleting the directory", mapPath) 462 if err = os.RemoveAll(mapPath); err != nil { 463 return fmt.Errorf("fc: failed to delete the directory: %s\nError: %v", mapPath, err) 464 } 465 klog.V(4).Infof("fc: successfully detached disk: %s", mapPath) 466 return nil 467 } 468 469 func (c *fcDiskUnmapper) UnmapPodDevice() error { 470 return nil 471 } 472 473 // GetGlobalMapPath returns global map path and error 474 // path: plugins/kubernetes.io/{PluginName}/volumeDevices/{WWID}/{podUid} 475 func (fc *fcDisk) GetGlobalMapPath(spec *volume.Spec) (string, error) { 476 return fc.fcGlobalMapPath(spec) 477 } 478 479 // GetPodDeviceMapPath returns pod device map path and volume name 480 // path: pods/{podUid}/volumeDevices/kubernetes.io~fc 481 // volumeName: pv0001 482 func (fc *fcDisk) GetPodDeviceMapPath() (string, string) { 483 return fc.fcPodDeviceMapPath() 484 } 485 486 func getVolumeSource(spec *volume.Spec) (*v1.FCVolumeSource, bool, error) { 487 // fc volumes used directly in a pod have a ReadOnly flag set by the pod author. 488 // fc volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV 489 if spec.Volume != nil && spec.Volume.FC != nil { 490 return spec.Volume.FC, spec.Volume.FC.ReadOnly, nil 491 } else if spec.PersistentVolume != nil && 492 spec.PersistentVolume.Spec.FC != nil { 493 return spec.PersistentVolume.Spec.FC, spec.ReadOnly, nil 494 } 495 496 return nil, false, fmt.Errorf("Spec does not reference a FibreChannel volume type") 497 } 498 499 func createPersistentVolumeFromFCVolumeSource(volumeName string, fc v1.FCVolumeSource) *v1.PersistentVolume { 500 block := v1.PersistentVolumeBlock 501 return &v1.PersistentVolume{ 502 ObjectMeta: metav1.ObjectMeta{ 503 Name: volumeName, 504 }, 505 Spec: v1.PersistentVolumeSpec{ 506 PersistentVolumeSource: v1.PersistentVolumeSource{ 507 FC: &fc, 508 }, 509 VolumeMode: &block, 510 }, 511 } 512 } 513 514 func getWwnsLunWwids(fc *v1.FCVolumeSource) ([]string, string, []string, error) { 515 var lun string 516 var wwids []string 517 if fc.Lun != nil && len(fc.TargetWWNs) != 0 { 518 lun = strconv.Itoa(int(*fc.Lun)) 519 return fc.TargetWWNs, lun, wwids, nil 520 } 521 if len(fc.WWIDs) != 0 { 522 for _, wwid := range fc.WWIDs { 523 wwids = append(wwids, strings.Replace(wwid, " ", "_", -1)) 524 } 525 return fc.TargetWWNs, lun, wwids, nil 526 } 527 return nil, "", nil, fmt.Errorf("fc: no fc disk information found") 528 }