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  }