Unverified Commit 2f17d782 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #54752 from mtanino/pr/BlockVolumesSupport-iscsi

Automatic merge from submit-queue (batch tested with PRs 54230, 58100, 57861, 54752). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Block volumes Support: iSCSI plugin update **What this PR does / why we need it**: Add interface changes to iSCSI volume plugin to enable block volumes support feature. **Which issue this PR fixes**: Based on this proposal (kubernetes/community#805 & kubernetes/community#1265) and this feature issue: kubernetes/features#351 **Special notes for your reviewer**: This PR temporarily includes following changes except iSCSI plugin change for reviewing purpose. These changes will be removed from the PR once they are merged. - (#50457) API Change - (#51494) Container runtime interface change, volumemanager changes, operationexecutor changes There are another PRs related to this functionality. (#50457) API Change (#53385) VolumeMode PV-PVC Binding change (#51494) Container runtime interface change, volumemanager changes, operationexecutor changes (#55112) Block volume: Command line printer update Plugins (#51493) Block volumes Support: FC plugin update (#54752) Block volumes Support: iSCSI plugin update **Release note**: ``` NONE ```
parents 8c22277f 96509d4f
...@@ -17,14 +17,17 @@ go_library( ...@@ -17,14 +17,17 @@ go_library(
], ],
importpath = "k8s.io/kubernetes/pkg/volume/iscsi", importpath = "k8s.io/kubernetes/pkg/volume/iscsi",
deps = [ deps = [
"//pkg/features:go_default_library",
"//pkg/util/mount:go_default_library", "//pkg/util/mount:go_default_library",
"//pkg/util/strings:go_default_library", "//pkg/util/strings:go_default_library",
"//pkg/volume:go_default_library", "//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library", "//pkg/volume/util:go_default_library",
"//pkg/volume/util/volumehelper:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
], ],
) )
......
...@@ -19,16 +19,17 @@ package iscsi ...@@ -19,16 +19,17 @@ package iscsi
import ( import (
"fmt" "fmt"
"os" "os"
"strconv"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util" volumeutil "k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
) )
type iscsiAttacher struct { type iscsiAttacher struct {
...@@ -66,7 +67,7 @@ func (attacher *iscsiAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName ...@@ -66,7 +67,7 @@ func (attacher *iscsiAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName
} }
func (attacher *iscsiAttacher) WaitForAttach(spec *volume.Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error) { func (attacher *iscsiAttacher) WaitForAttach(spec *volume.Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error) {
mounter, err := attacher.volumeSpecToMounter(spec, attacher.host, pod) mounter, err := volumeSpecToMounter(spec, attacher.host, pod)
if err != nil { if err != nil {
glog.Warningf("failed to get iscsi mounter: %v", err) glog.Warningf("failed to get iscsi mounter: %v", err)
return "", err return "", err
...@@ -76,7 +77,7 @@ func (attacher *iscsiAttacher) WaitForAttach(spec *volume.Spec, devicePath strin ...@@ -76,7 +77,7 @@ func (attacher *iscsiAttacher) WaitForAttach(spec *volume.Spec, devicePath strin
func (attacher *iscsiAttacher) GetDeviceMountPath( func (attacher *iscsiAttacher) GetDeviceMountPath(
spec *volume.Spec) (string, error) { spec *volume.Spec) (string, error) {
mounter, err := attacher.volumeSpecToMounter(spec, attacher.host, nil) mounter, err := volumeSpecToMounter(spec, attacher.host, nil)
if err != nil { if err != nil {
glog.Warningf("failed to get iscsi mounter: %v", err) glog.Warningf("failed to get iscsi mounter: %v", err)
return "", err return "", err
...@@ -143,7 +144,7 @@ func (detacher *iscsiDetacher) Detach(volumeName string, nodeName types.NodeName ...@@ -143,7 +144,7 @@ func (detacher *iscsiDetacher) Detach(volumeName string, nodeName types.NodeName
} }
func (detacher *iscsiDetacher) UnmountDevice(deviceMountPath string) error { func (detacher *iscsiDetacher) UnmountDevice(deviceMountPath string) error {
unMounter := detacher.volumeSpecToUnmounter(detacher.mounter) unMounter := volumeSpecToUnmounter(detacher.mounter, detacher.host)
err := detacher.manager.DetachDisk(*unMounter, deviceMountPath) err := detacher.manager.DetachDisk(*unMounter, deviceMountPath)
if err != nil { if err != nil {
return fmt.Errorf("iscsi: failed to detach disk: %s\nError: %v", deviceMountPath, err) return fmt.Errorf("iscsi: failed to detach disk: %s\nError: %v", deviceMountPath, err)
...@@ -157,94 +158,49 @@ func (detacher *iscsiDetacher) UnmountDevice(deviceMountPath string) error { ...@@ -157,94 +158,49 @@ func (detacher *iscsiDetacher) UnmountDevice(deviceMountPath string) error {
return nil return nil
} }
func (attacher *iscsiAttacher) volumeSpecToMounter(spec *volume.Spec, host volume.VolumeHost, pod *v1.Pod) (*iscsiDiskMounter, error) { func volumeSpecToMounter(spec *volume.Spec, host volume.VolumeHost, pod *v1.Pod) (*iscsiDiskMounter, error) {
var secret map[string]string var secret map[string]string
var bkportal []string
readOnly, fsType, err := getISCSIVolumeInfo(spec) readOnly, fsType, err := getISCSIVolumeInfo(spec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var podUID types.UID
if pod != nil { if pod != nil {
chapDiscovery, err := getISCSIDiscoveryCHAPInfo(spec) secret, err = createSecretMap(spec, &iscsiPlugin{host: host}, pod.Namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
chapSession, err := getISCSISessionCHAPInfo(spec) podUID = pod.UID
if err != nil { }
return nil, err iscsiDisk, err := createISCSIDisk(spec,
} podUID,
if chapDiscovery || chapSession { &iscsiPlugin{host: host},
secretName, secretNamespace, err := getISCSISecretNameAndNamespace(spec, pod.Namespace) &ISCSIUtil{},
if err != nil { secret,
return nil, err )
}
if len(secretNamespace) == 0 || len(secretName) == 0 {
return nil, fmt.Errorf("CHAP enabled but secret name or namespace is empty")
}
// if secret is provided, retrieve it
kubeClient := host.GetKubeClient()
if kubeClient == nil {
return nil, fmt.Errorf("Cannot get kube client")
}
secretObj, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{})
if err != nil {
err = fmt.Errorf("Couldn't get secret %v/%v error: %v", secretNamespace, secretName, err)
return nil, err
}
secret = make(map[string]string)
for name, data := range secretObj.Data {
glog.V(6).Infof("retrieving CHAP secret name: %s", name)
secret[name] = string(data)
}
}
}
tp, portals, iqn, lunStr, err := getISCSITargetInfo(spec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
exec := host.GetExec(iscsiPluginName)
lun := strconv.Itoa(int(lunStr)) // TODO: remove feature gate check after no longer needed
portal := portalMounter(tp) if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
bkportal = append(bkportal, portal) volumeMode, err := volumehelper.GetVolumeMode(spec)
for _, p := range portals { if err != nil {
bkportal = append(bkportal, portalMounter(string(p))) return nil, err
} }
glog.V(5).Infof("iscsi: VolumeSpecToMounter volumeMode %s", volumeMode)
iface, initiatorNamePtr, err := getISCSIInitiatorInfo(spec) return &iscsiDiskMounter{
if err != nil { iscsiDisk: iscsiDisk,
return nil, err fsType: fsType,
} volumeMode: volumeMode,
readOnly: readOnly,
var initiatorName string mounter: &mount.SafeFormatAndMount{Interface: host.GetMounter(iscsiPluginName), Exec: exec},
if initiatorNamePtr != nil { exec: exec,
initiatorName = *initiatorNamePtr deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()),
} }, nil
chapDiscovery, err := getISCSIDiscoveryCHAPInfo(spec)
if err != nil {
return nil, err
}
chapSession, err := getISCSISessionCHAPInfo(spec)
if err != nil {
return nil, err
} }
exec := attacher.host.GetExec(iscsiPluginName)
return &iscsiDiskMounter{ return &iscsiDiskMounter{
iscsiDisk: &iscsiDisk{ iscsiDisk: iscsiDisk,
plugin: &iscsiPlugin{
host: host,
},
VolName: spec.Name(),
Portals: bkportal,
Iqn: iqn,
lun: lun,
Iface: iface,
chap_discovery: chapDiscovery,
chap_session: chapSession,
secret: secret,
InitiatorName: initiatorName,
manager: &ISCSIUtil{}},
fsType: fsType, fsType: fsType,
readOnly: readOnly, readOnly: readOnly,
mounter: &mount.SafeFormatAndMount{Interface: host.GetMounter(iscsiPluginName), Exec: exec}, mounter: &mount.SafeFormatAndMount{Interface: host.GetMounter(iscsiPluginName), Exec: exec},
...@@ -253,8 +209,8 @@ func (attacher *iscsiAttacher) volumeSpecToMounter(spec *volume.Spec, host volum ...@@ -253,8 +209,8 @@ func (attacher *iscsiAttacher) volumeSpecToMounter(spec *volume.Spec, host volum
}, nil }, nil
} }
func (detacher *iscsiDetacher) volumeSpecToUnmounter(mounter mount.Interface) *iscsiDiskUnmounter { func volumeSpecToUnmounter(mounter mount.Interface, host volume.VolumeHost) *iscsiDiskUnmounter {
exec := detacher.host.GetExec(iscsiPluginName) exec := host.GetExec(iscsiPluginName)
return &iscsiDiskUnmounter{ return &iscsiDiskUnmounter{
iscsiDisk: &iscsiDisk{ iscsiDisk: &iscsiDisk{
plugin: &iscsiPlugin{}, plugin: &iscsiPlugin{},
......
...@@ -27,15 +27,19 @@ import ( ...@@ -27,15 +27,19 @@ import (
// Abstract interface to disk operations. // Abstract interface to disk operations.
type diskManager interface { type diskManager interface {
MakeGlobalPDName(disk iscsiDisk) string MakeGlobalPDName(disk iscsiDisk) string
MakeGlobalVDPDName(disk iscsiDisk) string
// Attaches the disk to the kubelet's host machine. // Attaches the disk to the kubelet's host machine.
AttachDisk(b iscsiDiskMounter) (string, error) AttachDisk(b iscsiDiskMounter) (string, error)
// Detaches the disk from the kubelet's host machine. // Detaches the disk from the kubelet's host machine.
DetachDisk(disk iscsiDiskUnmounter, mntPath string) error DetachDisk(disk iscsiDiskUnmounter, mntPath string) error
// Detaches the block disk from the kubelet's host machine.
DetachBlockISCSIDisk(disk iscsiDiskUnmapper, mntPath string) error
} }
// utility to mount a disk based filesystem // utility to mount a disk based filesystem
// globalPDPath: global mount path like, /var/lib/kubelet/plugins/kubernetes.io/iscsi/{ifaceName}/{portal-some_iqn-lun-lun_id}
// volPath: pod volume dir path like, /var/lib/kubelet/pods/{podUID}/volumes/kubernetes.io~iscsi/{volumeName}
func diskSetUp(manager diskManager, b iscsiDiskMounter, volPath string, mounter mount.Interface, fsGroup *int64) error { func diskSetUp(manager diskManager, b iscsiDiskMounter, volPath string, mounter mount.Interface, fsGroup *int64) error {
// TODO: handle failed mounts here.
notMnt, err := mounter.IsLikelyNotMountPoint(volPath) notMnt, err := mounter.IsLikelyNotMountPoint(volPath)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
glog.Errorf("cannot validate mountpoint: %s", volPath) glog.Errorf("cannot validate mountpoint: %s", volPath)
......
...@@ -19,6 +19,7 @@ package iscsi ...@@ -19,6 +19,7 @@ package iscsi
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"testing" "testing"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
...@@ -80,7 +81,7 @@ type fakeDiskManager struct { ...@@ -80,7 +81,7 @@ type fakeDiskManager struct {
func NewFakeDiskManager() *fakeDiskManager { func NewFakeDiskManager() *fakeDiskManager {
return &fakeDiskManager{ return &fakeDiskManager{
tmpDir: utiltesting.MkTmpdirOrDie("fc_test"), tmpDir: utiltesting.MkTmpdirOrDie("iscsi_test"),
} }
} }
...@@ -91,6 +92,11 @@ func (fake *fakeDiskManager) Cleanup() { ...@@ -91,6 +92,11 @@ func (fake *fakeDiskManager) Cleanup() {
func (fake *fakeDiskManager) MakeGlobalPDName(disk iscsiDisk) string { func (fake *fakeDiskManager) MakeGlobalPDName(disk iscsiDisk) string {
return fake.tmpDir return fake.tmpDir
} }
func (fake *fakeDiskManager) MakeGlobalVDPDName(disk iscsiDisk) string {
return fake.tmpDir
}
func (fake *fakeDiskManager) AttachDisk(b iscsiDiskMounter) (string, error) { func (fake *fakeDiskManager) AttachDisk(b iscsiDiskMounter) (string, error) {
globalPath := b.manager.MakeGlobalPDName(*b.iscsiDisk) globalPath := b.manager.MakeGlobalPDName(*b.iscsiDisk)
err := os.MkdirAll(globalPath, 0750) err := os.MkdirAll(globalPath, 0750)
...@@ -113,6 +119,15 @@ func (fake *fakeDiskManager) DetachDisk(c iscsiDiskUnmounter, mntPath string) er ...@@ -113,6 +119,15 @@ func (fake *fakeDiskManager) DetachDisk(c iscsiDiskUnmounter, mntPath string) er
return nil return nil
} }
func (fake *fakeDiskManager) DetachBlockISCSIDisk(c iscsiDiskUnmapper, mntPath string) error {
globalPath := c.manager.MakeGlobalVDPDName(*c.iscsiDisk)
err := os.RemoveAll(globalPath)
if err != nil {
return err
}
return nil
}
func doTestPlugin(t *testing.T, spec *volume.Spec) { func doTestPlugin(t *testing.T, spec *volume.Spec) {
tmpDir, err := utiltesting.MkTmpdir("iscsi_test") tmpDir, err := utiltesting.MkTmpdir("iscsi_test")
if err != nil { if err != nil {
...@@ -289,10 +304,12 @@ type testcase struct { ...@@ -289,10 +304,12 @@ type testcase struct {
defaultNs string defaultNs string
spec *volume.Spec spec *volume.Spec
// Expected return of the test // Expected return of the test
expectedName string expectedName string
expectedNs string expectedNs string
expectedIface string expectedIface string
expectedError error expectedError error
expectedDiscoveryCHAP bool
expectedSessionCHAP bool
} }
func TestGetSecretNameAndNamespaceForPV(t *testing.T) { func TestGetSecretNameAndNamespaceForPV(t *testing.T) {
...@@ -424,5 +441,105 @@ func TestGetISCSIInitiatorInfo(t *testing.T) { ...@@ -424,5 +441,105 @@ func TestGetISCSIInitiatorInfo(t *testing.T) {
err, resultIface) err, resultIface)
} }
} }
}
func TestGetISCSICHAP(t *testing.T) {
tests := []testcase{
{
name: "persistent volume source",
spec: &volume.Spec{
PersistentVolume: &v1.PersistentVolume{
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
ISCSI: &v1.ISCSIPersistentVolumeSource{
DiscoveryCHAPAuth: true,
SessionCHAPAuth: true,
},
},
},
},
},
expectedDiscoveryCHAP: true,
expectedSessionCHAP: true,
expectedError: nil,
},
{
name: "pod volume source",
spec: &volume.Spec{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{
ISCSI: &v1.ISCSIVolumeSource{
DiscoveryCHAPAuth: true,
SessionCHAPAuth: true,
},
},
},
},
expectedDiscoveryCHAP: true,
expectedSessionCHAP: true,
expectedError: nil,
},
{
name: "no volume",
spec: &volume.Spec{},
expectedDiscoveryCHAP: false,
expectedSessionCHAP: false,
expectedError: fmt.Errorf("Spec does not reference an ISCSI volume type"),
},
}
for _, testcase := range tests {
resultDiscoveryCHAP, err := getISCSIDiscoveryCHAPInfo(testcase.spec)
resultSessionCHAP, err := getISCSISessionCHAPInfo(testcase.spec)
switch testcase.name {
case "no volume":
if err.Error() != testcase.expectedError.Error() || resultDiscoveryCHAP != testcase.expectedDiscoveryCHAP || resultSessionCHAP != testcase.expectedSessionCHAP {
t.Errorf("%s failed: expected err=%v DiscoveryCHAP=%v SessionCHAP=%v, got %v/%v/%v",
testcase.name, testcase.expectedError, testcase.expectedDiscoveryCHAP, testcase.expectedSessionCHAP,
err, resultDiscoveryCHAP, resultSessionCHAP)
}
default:
if err != testcase.expectedError || resultDiscoveryCHAP != testcase.expectedDiscoveryCHAP || resultSessionCHAP != testcase.expectedSessionCHAP {
t.Errorf("%s failed: expected err=%v DiscoveryCHAP=%v SessionCHAP=%v, got %v/%v/%v", testcase.name, testcase.expectedError, testcase.expectedDiscoveryCHAP, testcase.expectedSessionCHAP,
err, resultDiscoveryCHAP, resultSessionCHAP)
}
}
}
}
func TestGetVolumeSpec(t *testing.T) {
path := "plugins/kubernetes.io/iscsi/volumeDevices/iface-default/127.0.0.1:3260-iqn.2014-12.server:storage.target01-lun-0"
spec, _ := getVolumeSpecFromGlobalMapPath("test", path)
portal := spec.PersistentVolume.Spec.PersistentVolumeSource.ISCSI.TargetPortal
if portal != "127.0.0.1:3260" {
t.Errorf("wrong portal: %v", portal)
}
iqn := spec.PersistentVolume.Spec.PersistentVolumeSource.ISCSI.IQN
if iqn != "iqn.2014-12.server:storage.target01" {
t.Errorf("wrong iqn: %v", iqn)
}
lun := spec.PersistentVolume.Spec.PersistentVolumeSource.ISCSI.Lun
if lun != 0 {
t.Errorf("wrong lun: %v", lun)
}
iface := spec.PersistentVolume.Spec.PersistentVolumeSource.ISCSI.ISCSIInterface
if iface != "default" {
t.Errorf("wrong ISCSIInterface: %v", iface)
}
}
func TestGetVolumeSpec_no_lun(t *testing.T) {
path := "plugins/kubernetes.io/iscsi/volumeDevices/iface-default/127.0.0.1:3260-iqn.2014-12.server:storage.target01"
_, err := getVolumeSpecFromGlobalMapPath("test", path)
if !strings.Contains(err.Error(), "malformatted mnt path") {
t.Errorf("should get error: malformatted mnt path")
}
}
func TestGetVolumeSpec_no_iface(t *testing.T) {
path := "plugins/kubernetes.io/iscsi/volumeDevices/default/127.0.0.1:3260-iqn.2014-12.server:storage.target01-lun-0"
_, err := getVolumeSpecFromGlobalMapPath("test", path)
if !strings.Contains(err.Error(), "failed to retreive iface") {
t.Errorf("should get error: failed to retreive iface")
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment