mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 14:07:14 +00:00
FC plugin: Support WWID for volume identifier
This PR adds World Wide Identifier (WWID) parameter to FCVolumeSource as an unique volume identifier. fixes #48639
This commit is contained in:
parent
4dc4c3c4a4
commit
03e28476c4
@ -715,9 +715,11 @@ type ISCSIVolumeSource struct {
|
|||||||
// Fibre Channel volumes can only be mounted as read/write once.
|
// Fibre Channel volumes can only be mounted as read/write once.
|
||||||
// Fibre Channel volumes support ownership management and SELinux relabeling.
|
// Fibre Channel volumes support ownership management and SELinux relabeling.
|
||||||
type FCVolumeSource struct {
|
type FCVolumeSource struct {
|
||||||
// Required: FC target worldwide names (WWNs)
|
// Optional: FC target worldwide names (WWNs)
|
||||||
|
// +optional
|
||||||
TargetWWNs []string
|
TargetWWNs []string
|
||||||
// Required: FC target lun number
|
// Optional: FC target lun number
|
||||||
|
// +optional
|
||||||
Lun *int32
|
Lun *int32
|
||||||
// Filesystem type to mount.
|
// Filesystem type to mount.
|
||||||
// Must be a filesystem type supported by the host operating system.
|
// Must be a filesystem type supported by the host operating system.
|
||||||
@ -729,6 +731,10 @@ type FCVolumeSource struct {
|
|||||||
// the ReadOnly setting in VolumeMounts.
|
// the ReadOnly setting in VolumeMounts.
|
||||||
// +optional
|
// +optional
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
|
// Optional: FC volume World Wide Identifiers (WWIDs)
|
||||||
|
// Either WWIDs or TargetWWNs and Lun must be set, but not both simultaneously.
|
||||||
|
// +optional
|
||||||
|
WWIDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlexVolume represents a generic volume resource that is
|
// FlexVolume represents a generic volume resource that is
|
||||||
|
@ -648,15 +648,21 @@ func validateISCSIVolumeSource(iscsi *api.ISCSIVolumeSource, fldPath *field.Path
|
|||||||
|
|
||||||
func validateFCVolumeSource(fc *api.FCVolumeSource, fldPath *field.Path) field.ErrorList {
|
func validateFCVolumeSource(fc *api.FCVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
if len(fc.TargetWWNs) < 1 {
|
if len(fc.TargetWWNs) < 1 && len(fc.WWIDs) < 1 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("targetWWNs"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("targetWWNs"), "must specify either targetWWNs or wwids, but not both"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if fc.Lun == nil {
|
if len(fc.TargetWWNs) != 0 && len(fc.WWIDs) != 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("lun"), ""))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("targetWWNs"), fc.TargetWWNs, "targetWWNs and wwids can not be specified simultaneously"))
|
||||||
} else {
|
}
|
||||||
if *fc.Lun < 0 || *fc.Lun > 255 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), fc.Lun, validation.InclusiveRangeError(0, 255)))
|
if len(fc.TargetWWNs) != 0 {
|
||||||
|
if fc.Lun == nil {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("lun"), "lun is required if targetWWNs is specified"))
|
||||||
|
} else {
|
||||||
|
if *fc.Lun < 0 || *fc.Lun > 255 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("lun"), fc.Lun, validation.InclusiveRangeError(0, 255)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
@ -2099,7 +2100,7 @@ func TestValidateVolumes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
// FC
|
// FC
|
||||||
{
|
{
|
||||||
name: "valid FC",
|
name: "FC valid targetWWNs and lun",
|
||||||
vol: api.Volume{
|
vol: api.Volume{
|
||||||
Name: "fc",
|
Name: "fc",
|
||||||
VolumeSource: api.VolumeSource{
|
VolumeSource: api.VolumeSource{
|
||||||
@ -2113,23 +2114,56 @@ func TestValidateVolumes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fc empty wwn",
|
name: "FC valid wwids",
|
||||||
|
vol: api.Volume{
|
||||||
|
Name: "fc",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
FC: &api.FCVolumeSource{
|
||||||
|
WWIDs: []string{"some_wwid"},
|
||||||
|
FSType: "ext4",
|
||||||
|
ReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FC empty targetWWNs and wwids",
|
||||||
vol: api.Volume{
|
vol: api.Volume{
|
||||||
Name: "fc",
|
Name: "fc",
|
||||||
VolumeSource: api.VolumeSource{
|
VolumeSource: api.VolumeSource{
|
||||||
FC: &api.FCVolumeSource{
|
FC: &api.FCVolumeSource{
|
||||||
TargetWWNs: []string{},
|
TargetWWNs: []string{},
|
||||||
Lun: newInt32(1),
|
Lun: newInt32(1),
|
||||||
|
WWIDs: []string{},
|
||||||
FSType: "ext4",
|
FSType: "ext4",
|
||||||
ReadOnly: false,
|
ReadOnly: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
errtype: field.ErrorTypeRequired,
|
errtype: field.ErrorTypeRequired,
|
||||||
errfield: "fc.targetWWNs",
|
errfield: "fc.targetWWNs",
|
||||||
|
errdetail: "must specify either targetWWNs or wwids",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fc empty lun",
|
name: "FC invalid: both targetWWNs and wwids simultaneously",
|
||||||
|
vol: api.Volume{
|
||||||
|
Name: "fc",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
FC: &api.FCVolumeSource{
|
||||||
|
TargetWWNs: []string{"some_wwn"},
|
||||||
|
Lun: newInt32(1),
|
||||||
|
WWIDs: []string{"some_wwid"},
|
||||||
|
FSType: "ext4",
|
||||||
|
ReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errtype: field.ErrorTypeInvalid,
|
||||||
|
errfield: "fc.targetWWNs",
|
||||||
|
errdetail: "targetWWNs and wwids can not be specified simultaneously",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FC valid targetWWNs and empty lun",
|
||||||
vol: api.Volume{
|
vol: api.Volume{
|
||||||
Name: "fc",
|
Name: "fc",
|
||||||
VolumeSource: api.VolumeSource{
|
VolumeSource: api.VolumeSource{
|
||||||
@ -2141,8 +2175,26 @@ func TestValidateVolumes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
errtype: field.ErrorTypeRequired,
|
errtype: field.ErrorTypeRequired,
|
||||||
errfield: "fc.lun",
|
errfield: "fc.lun",
|
||||||
|
errdetail: "lun is required if targetWWNs is specified",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FC valid targetWWNs and invalid lun",
|
||||||
|
vol: api.Volume{
|
||||||
|
Name: "fc",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
FC: &api.FCVolumeSource{
|
||||||
|
TargetWWNs: []string{"wwn"},
|
||||||
|
Lun: newInt32(256),
|
||||||
|
FSType: "ext4",
|
||||||
|
ReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errtype: field.ErrorTypeInvalid,
|
||||||
|
errfield: "fc.lun",
|
||||||
|
errdetail: validation.InclusiveRangeError(0, 255),
|
||||||
},
|
},
|
||||||
// FlexVolume
|
// FlexVolume
|
||||||
{
|
{
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
@ -167,18 +168,27 @@ func volumeSpecToMounter(spec *volume.Spec, host volume.VolumeHost) (*fcDiskMoun
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if fc.Lun == nil {
|
var lun string
|
||||||
return nil, fmt.Errorf("empty lun")
|
var wwids []string
|
||||||
|
if fc.Lun != nil && len(fc.TargetWWNs) != 0 {
|
||||||
|
lun = strconv.Itoa(int(*fc.Lun))
|
||||||
|
} else if len(fc.WWIDs) != 0 {
|
||||||
|
for _, wwid := range fc.WWIDs {
|
||||||
|
wwids = append(wwids, strings.Replace(wwid, " ", "_", -1))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mounter")
|
||||||
}
|
}
|
||||||
lun := strconv.Itoa(int(*fc.Lun))
|
|
||||||
return &fcDiskMounter{
|
return &fcDiskMounter{
|
||||||
fcDisk: &fcDisk{
|
fcDisk: &fcDisk{
|
||||||
plugin: &fcPlugin{
|
plugin: &fcPlugin{
|
||||||
host: host,
|
host: host,
|
||||||
},
|
},
|
||||||
wwns: fc.TargetWWNs,
|
wwns: fc.TargetWWNs,
|
||||||
lun: lun,
|
lun: lun,
|
||||||
io: &osIOHandler{},
|
wwids: wwids,
|
||||||
|
io: &osIOHandler{},
|
||||||
},
|
},
|
||||||
fsType: fc.FSType,
|
fsType: fc.FSType,
|
||||||
readOnly: readOnly,
|
readOnly: readOnly,
|
||||||
|
@ -19,12 +19,13 @@ package fc
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/kubernetes/pkg/util/mount"
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
"k8s.io/kubernetes/pkg/util/strings"
|
utilstrings "k8s.io/kubernetes/pkg/util/strings"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
"k8s.io/kubernetes/pkg/volume/util"
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
"k8s.io/utils/exec"
|
"k8s.io/utils/exec"
|
||||||
@ -62,8 +63,15 @@ func (plugin *fcPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TargetWWNs are the FibreChannel target worldwide names
|
if len(volumeSource.TargetWWNs) != 0 {
|
||||||
return fmt.Sprintf("%v", volumeSource.TargetWWNs), nil
|
// TargetWWNs are the FibreChannel target worldwide names
|
||||||
|
return fmt.Sprintf("%v", volumeSource.TargetWWNs), nil
|
||||||
|
} else if len(volumeSource.WWIDs) != 0 {
|
||||||
|
// WWIDs are the FibreChannel World Wide Identifiers
|
||||||
|
return fmt.Sprintf("%v", volumeSource.WWIDs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (plugin *fcPlugin) CanSupport(spec *volume.Spec) bool {
|
func (plugin *fcPlugin) CanSupport(spec *volume.Spec) bool {
|
||||||
@ -106,18 +114,25 @@ func (plugin *fcPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fc.Lun == nil {
|
var lun string
|
||||||
return nil, fmt.Errorf("empty lun")
|
var wwids []string
|
||||||
|
if fc.Lun != nil && len(fc.TargetWWNs) != 0 {
|
||||||
|
lun = strconv.Itoa(int(*fc.Lun))
|
||||||
|
} else if len(fc.WWIDs) != 0 {
|
||||||
|
for _, wwid := range fc.WWIDs {
|
||||||
|
wwids = append(wwids, strings.Replace(wwid, " ", "_", -1))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mounter")
|
||||||
}
|
}
|
||||||
|
|
||||||
lun := strconv.Itoa(int(*fc.Lun))
|
|
||||||
|
|
||||||
return &fcDiskMounter{
|
return &fcDiskMounter{
|
||||||
fcDisk: &fcDisk{
|
fcDisk: &fcDisk{
|
||||||
podUID: podUID,
|
podUID: podUID,
|
||||||
volName: spec.Name(),
|
volName: spec.Name(),
|
||||||
wwns: fc.TargetWWNs,
|
wwns: fc.TargetWWNs,
|
||||||
lun: lun,
|
lun: lun,
|
||||||
|
wwids: wwids,
|
||||||
manager: manager,
|
manager: manager,
|
||||||
io: &osIOHandler{},
|
io: &osIOHandler{},
|
||||||
plugin: plugin},
|
plugin: plugin},
|
||||||
@ -166,6 +181,7 @@ type fcDisk struct {
|
|||||||
portal string
|
portal string
|
||||||
wwns []string
|
wwns []string
|
||||||
lun string
|
lun string
|
||||||
|
wwids []string
|
||||||
plugin *fcPlugin
|
plugin *fcPlugin
|
||||||
// Utility interface that provides API calls to the provider to attach/detach disks.
|
// Utility interface that provides API calls to the provider to attach/detach disks.
|
||||||
manager diskManager
|
manager diskManager
|
||||||
@ -177,7 +193,7 @@ type fcDisk struct {
|
|||||||
func (fc *fcDisk) GetPath() string {
|
func (fc *fcDisk) GetPath() string {
|
||||||
name := fcPluginName
|
name := fcPluginName
|
||||||
// safe to use PodVolumeDir now: volume teardown occurs before pod is cleaned up
|
// safe to use PodVolumeDir now: volume teardown occurs before pod is cleaned up
|
||||||
return fc.plugin.host.GetPodVolumeDir(fc.podUID, strings.EscapeQualifiedNameForDisk(name), fc.volName)
|
return fc.plugin.host.GetPodVolumeDir(fc.podUID, utilstrings.EscapeQualifiedNameForDisk(name), fc.volName)
|
||||||
}
|
}
|
||||||
|
|
||||||
type fcDiskMounter struct {
|
type fcDiskMounter struct {
|
||||||
|
@ -193,13 +193,39 @@ func doTestPlugin(t *testing.T, spec *volume.Spec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doTestPluginNilMounter(t *testing.T, spec *volume.Spec) {
|
||||||
|
tmpDir, err := utiltesting.MkTmpdir("fc_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
plugMgr := volume.VolumePluginMgr{}
|
||||||
|
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||||
|
|
||||||
|
plug, err := plugMgr.FindPluginByName("kubernetes.io/fc")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Can't find the plugin by name")
|
||||||
|
}
|
||||||
|
fakeManager := NewFakeDiskManager()
|
||||||
|
defer fakeManager.Cleanup()
|
||||||
|
fakeMounter := &mount.FakeMounter{}
|
||||||
|
mounter, err := plug.(*fcPlugin).newMounterInternal(spec, types.UID("poduid"), fakeManager, fakeMounter)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Error failed to make a new Mounter is expected: %v", err)
|
||||||
|
}
|
||||||
|
if mounter != nil {
|
||||||
|
t.Errorf("A nil Mounter is expected: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPluginVolume(t *testing.T) {
|
func TestPluginVolume(t *testing.T) {
|
||||||
lun := int32(0)
|
lun := int32(0)
|
||||||
vol := &v1.Volume{
|
vol := &v1.Volume{
|
||||||
Name: "vol1",
|
Name: "vol1",
|
||||||
VolumeSource: v1.VolumeSource{
|
VolumeSource: v1.VolumeSource{
|
||||||
FC: &v1.FCVolumeSource{
|
FC: &v1.FCVolumeSource{
|
||||||
TargetWWNs: []string{"some_wwn"},
|
TargetWWNs: []string{"500a0981891b8dc5"},
|
||||||
FSType: "ext4",
|
FSType: "ext4",
|
||||||
Lun: &lun,
|
Lun: &lun,
|
||||||
},
|
},
|
||||||
@ -217,7 +243,7 @@ func TestPluginPersistentVolume(t *testing.T) {
|
|||||||
Spec: v1.PersistentVolumeSpec{
|
Spec: v1.PersistentVolumeSpec{
|
||||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||||
FC: &v1.FCVolumeSource{
|
FC: &v1.FCVolumeSource{
|
||||||
TargetWWNs: []string{"some_wwn"},
|
TargetWWNs: []string{"500a0981891b8dc5"},
|
||||||
FSType: "ext4",
|
FSType: "ext4",
|
||||||
Lun: &lun,
|
Lun: &lun,
|
||||||
},
|
},
|
||||||
@ -227,6 +253,64 @@ func TestPluginPersistentVolume(t *testing.T) {
|
|||||||
doTestPlugin(t, volume.NewSpecFromPersistentVolume(vol, false))
|
doTestPlugin(t, volume.NewSpecFromPersistentVolume(vol, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPluginVolumeWWIDs(t *testing.T) {
|
||||||
|
vol := &v1.Volume{
|
||||||
|
Name: "vol1",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
FC: &v1.FCVolumeSource{
|
||||||
|
WWIDs: []string{"3600508b400105e210000900000490000"},
|
||||||
|
FSType: "ext4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
doTestPlugin(t, volume.NewSpecFromVolume(vol))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPluginPersistentVolumeWWIDs(t *testing.T) {
|
||||||
|
vol := &v1.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "vol1",
|
||||||
|
},
|
||||||
|
Spec: v1.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||||
|
FC: &v1.FCVolumeSource{
|
||||||
|
WWIDs: []string{"3600508b400105e21 000900000490000"},
|
||||||
|
FSType: "ext4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
doTestPlugin(t, volume.NewSpecFromPersistentVolume(vol, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPluginVolumeNoDiskInfo(t *testing.T) {
|
||||||
|
vol := &v1.Volume{
|
||||||
|
Name: "vol1",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
FC: &v1.FCVolumeSource{
|
||||||
|
FSType: "ext4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
doTestPluginNilMounter(t, volume.NewSpecFromVolume(vol))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPluginPersistentVolumeNoDiskInfo(t *testing.T) {
|
||||||
|
vol := &v1.PersistentVolume{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "vol1",
|
||||||
|
},
|
||||||
|
Spec: v1.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||||
|
FC: &v1.FCVolumeSource{
|
||||||
|
FSType: "ext4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
doTestPluginNilMounter(t, volume.NewSpecFromPersistentVolume(vol, false))
|
||||||
|
}
|
||||||
|
|
||||||
func TestPersistentClaimReadOnlyFlag(t *testing.T) {
|
func TestPersistentClaimReadOnlyFlag(t *testing.T) {
|
||||||
tmpDir, err := utiltesting.MkTmpdir("fc_test")
|
tmpDir, err := utiltesting.MkTmpdir("fc_test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -89,6 +89,40 @@ func findDisk(wwn, lun string, io ioHandler) (string, string) {
|
|||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// given a wwid, find the device and associated devicemapper parent
|
||||||
|
func findDiskWWIDs(wwid string, io ioHandler) (string, string) {
|
||||||
|
// Example wwid format:
|
||||||
|
// 3600508b400105e210000900000490000
|
||||||
|
// <VENDOR NAME> <IDENTIFIER NUMBER>
|
||||||
|
// Example of symlink under by-id:
|
||||||
|
// /dev/by-id/scsi-3600508b400105e210000900000490000
|
||||||
|
// /dev/by-id/scsi-<VENDOR NAME>_<IDENTIFIER NUMBER>
|
||||||
|
// The wwid could contain white space and it will be replaced
|
||||||
|
// underscore when wwid is exposed under /dev/by-id.
|
||||||
|
|
||||||
|
fc_path := "scsi-" + wwid
|
||||||
|
dev_id := "/dev/disk/by-id/"
|
||||||
|
if dirs, err := io.ReadDir(dev_id); err == nil {
|
||||||
|
for _, f := range dirs {
|
||||||
|
name := f.Name()
|
||||||
|
if name == fc_path {
|
||||||
|
disk, err := io.EvalSymlinks(dev_id + name)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(2).Infof("fc: failed to find a corresponding disk from symlink[%s], error %v", dev_id+name, err)
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
arr := strings.Split(disk, "/")
|
||||||
|
l := len(arr) - 1
|
||||||
|
dev := arr[l]
|
||||||
|
dm := findMultipathDeviceMapper(dev, io)
|
||||||
|
return disk, dm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glog.V(2).Infof("fc: failed to find a disk [%s]", dev_id+fc_path)
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
// Removes a scsi device based upon /dev/sdX name
|
// Removes a scsi device based upon /dev/sdX name
|
||||||
func removeFromScsiSubsystem(deviceName string, io ioHandler) {
|
func removeFromScsiSubsystem(deviceName string, io ioHandler) {
|
||||||
fileName := "/sys/block/" + deviceName + "/device/delete"
|
fileName := "/sys/block/" + deviceName + "/device/delete"
|
||||||
@ -110,27 +144,46 @@ func scsiHostRescan(io ioHandler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make a directory like /var/lib/kubelet/plugins/kubernetes.io/pod/fc/target-lun-0
|
// make a directory like /var/lib/kubelet/plugins/kubernetes.io/pod/fc/target-lun-0
|
||||||
func makePDNameInternal(host volume.VolumeHost, wwns []string, lun string) string {
|
func makePDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string {
|
||||||
return path.Join(host.GetPluginDir(fcPluginName), wwns[0]+"-lun-"+lun)
|
if len(wwns) != 0 {
|
||||||
|
return path.Join(host.GetPluginDir(fcPluginName), wwns[0]+"-lun-"+lun)
|
||||||
|
} else {
|
||||||
|
return path.Join(host.GetPluginDir(fcPluginName), wwids[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FCUtil struct{}
|
type FCUtil struct{}
|
||||||
|
|
||||||
func (util *FCUtil) MakeGlobalPDName(fc fcDisk) string {
|
func (util *FCUtil) MakeGlobalPDName(fc fcDisk) string {
|
||||||
return makePDNameInternal(fc.plugin.host, fc.wwns, fc.lun)
|
return makePDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchDisk(wwns []string, lun string, io ioHandler) (string, string) {
|
func searchDisk(b fcDiskMounter) (string, string) {
|
||||||
disk := ""
|
var diskIds []string
|
||||||
dm := ""
|
var disk string
|
||||||
|
var dm string
|
||||||
|
io := b.io
|
||||||
|
wwids := b.wwids
|
||||||
|
wwns := b.wwns
|
||||||
|
lun := b.lun
|
||||||
|
|
||||||
|
if len(wwns) != 0 {
|
||||||
|
diskIds = wwns
|
||||||
|
} else {
|
||||||
|
diskIds = wwids
|
||||||
|
}
|
||||||
|
|
||||||
rescaned := false
|
rescaned := false
|
||||||
// two-phase search:
|
// two-phase search:
|
||||||
// first phase, search existing device path, if a multipath dm is found, exit loop
|
// first phase, search existing device path, if a multipath dm is found, exit loop
|
||||||
// otherwise, in second phase, rescan scsi bus and search again, return with any findings
|
// otherwise, in second phase, rescan scsi bus and search again, return with any findings
|
||||||
for true {
|
for true {
|
||||||
for _, wwn := range wwns {
|
for _, diskId := range diskIds {
|
||||||
disk, dm = findDisk(wwn, lun, io)
|
if len(wwns) != 0 {
|
||||||
|
disk, dm = findDisk(diskId, lun, io)
|
||||||
|
} else {
|
||||||
|
disk, dm = findDiskWWIDs(diskId, io)
|
||||||
|
}
|
||||||
// if multipath device is found, break
|
// if multipath device is found, break
|
||||||
if dm != "" {
|
if dm != "" {
|
||||||
break
|
break
|
||||||
@ -150,10 +203,9 @@ func searchDisk(wwns []string, lun string, io ioHandler) (string, string) {
|
|||||||
|
|
||||||
func (util *FCUtil) AttachDisk(b fcDiskMounter) (string, error) {
|
func (util *FCUtil) AttachDisk(b fcDiskMounter) (string, error) {
|
||||||
devicePath := ""
|
devicePath := ""
|
||||||
wwns := b.wwns
|
var disk, dm string
|
||||||
lun := b.lun
|
|
||||||
io := b.io
|
disk, dm = searchDisk(b)
|
||||||
disk, dm := searchDisk(wwns, lun, io)
|
|
||||||
// if no disk matches input wwn and lun, exit
|
// if no disk matches input wwn and lun, exit
|
||||||
if disk == "" && dm == "" {
|
if disk == "" && dm == "" {
|
||||||
return "", fmt.Errorf("no fc disk found")
|
return "", fmt.Errorf("no fc disk found")
|
||||||
|
@ -63,6 +63,11 @@ func (handler *fakeIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) {
|
|||||||
name: "dm-1",
|
name: "dm-1",
|
||||||
}
|
}
|
||||||
return []os.FileInfo{f}, nil
|
return []os.FileInfo{f}, nil
|
||||||
|
case "/dev/disk/by-id/":
|
||||||
|
f := &fakeFileInfo{
|
||||||
|
name: "scsi-3600508b400105e210000900000490000",
|
||||||
|
}
|
||||||
|
return []os.FileInfo{f}, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -79,13 +84,31 @@ func (handler *fakeIOHandler) WriteFile(filename string, data []byte, perm os.Fi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIoHandler(t *testing.T) {
|
func TestSearchDisk(t *testing.T) {
|
||||||
io := &fakeIOHandler{}
|
fakeMounter := fcDiskMounter{
|
||||||
wwns := []string{"500a0981891b8dc5"}
|
fcDisk: &fcDisk{
|
||||||
lun := "0"
|
wwns: []string{"500a0981891b8dc5"},
|
||||||
disk, dm := searchDisk(wwns, lun, io)
|
lun: "0",
|
||||||
|
io: &fakeIOHandler{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
disk, dm := searchDisk(fakeMounter)
|
||||||
// if no disk matches input wwn and lun, exit
|
// if no disk matches input wwn and lun, exit
|
||||||
if disk == "" && dm == "" {
|
if disk == "" && dm == "" {
|
||||||
t.Errorf("no fc disk found")
|
t.Errorf("no fc disk found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchDiskWWID(t *testing.T) {
|
||||||
|
fakeMounter := fcDiskMounter{
|
||||||
|
fcDisk: &fcDisk{
|
||||||
|
wwids: []string{"3600508b400105e210000900000490000"},
|
||||||
|
io: &fakeIOHandler{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
disk, dm := searchDisk(fakeMounter)
|
||||||
|
// if no disk matches input wwid, exit
|
||||||
|
if disk == "" && dm == "" {
|
||||||
|
t.Errorf("no fc disk found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1102,10 +1102,12 @@ type ISCSIVolumeSource struct {
|
|||||||
// Fibre Channel volumes can only be mounted as read/write once.
|
// Fibre Channel volumes can only be mounted as read/write once.
|
||||||
// Fibre Channel volumes support ownership management and SELinux relabeling.
|
// Fibre Channel volumes support ownership management and SELinux relabeling.
|
||||||
type FCVolumeSource struct {
|
type FCVolumeSource struct {
|
||||||
// Required: FC target worldwide names (WWNs)
|
// Optional: FC target worldwide names (WWNs)
|
||||||
TargetWWNs []string `json:"targetWWNs" protobuf:"bytes,1,rep,name=targetWWNs"`
|
// +optional
|
||||||
// Required: FC target lun number
|
TargetWWNs []string `json:"targetWWNs,omitempty" protobuf:"bytes,1,rep,name=targetWWNs"`
|
||||||
Lun *int32 `json:"lun" protobuf:"varint,2,opt,name=lun"`
|
// Optional: FC target lun number
|
||||||
|
// +optional
|
||||||
|
Lun *int32 `json:"lun,omitempty" protobuf:"varint,2,opt,name=lun"`
|
||||||
// Filesystem type to mount.
|
// Filesystem type to mount.
|
||||||
// Must be a filesystem type supported by the host operating system.
|
// Must be a filesystem type supported by the host operating system.
|
||||||
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
|
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
|
||||||
@ -1116,6 +1118,10 @@ type FCVolumeSource struct {
|
|||||||
// the ReadOnly setting in VolumeMounts.
|
// the ReadOnly setting in VolumeMounts.
|
||||||
// +optional
|
// +optional
|
||||||
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,4,opt,name=readOnly"`
|
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,4,opt,name=readOnly"`
|
||||||
|
// Optional: FC volume world wide identifiers (wwids)
|
||||||
|
// Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.
|
||||||
|
// +optional
|
||||||
|
WWIDs []string `json:"wwids,omitempty" protobuf:"bytes,5,rep,name=wwids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
|
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
|
||||||
|
Loading…
Reference in New Issue
Block a user