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