...

Source file src/k8s.io/kubernetes/pkg/volume/fc/fc.go

Documentation: k8s.io/kubernetes/pkg/volume/fc

     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  }
   529  

View as plain text