CSI Inline Volume - Kubelet/Driver code impl

pull/564/head
Vladimir Vivien 2019-02-15 17:56:26 -05:00
parent d998fc8f0f
commit 923ad369c8
9 changed files with 1686 additions and 276 deletions

View File

@ -63,15 +63,15 @@ func (c *csiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string
return "", errors.New("missing spec") return "", errors.New("missing spec")
} }
csiSource, err := getCSISourceFromSpec(spec) pvSrc, err := getPVSourceFromSpec(spec)
if err != nil { if err != nil {
klog.Error(log("attacher.Attach failed to get CSI persistent source: %v", err)) klog.Error(log("attacher.Attach failed to get CSIPersistentVolumeSource: %v", err))
return "", err return "", err
} }
node := string(nodeName) node := string(nodeName)
pvName := spec.PersistentVolume.GetName() pvName := spec.PersistentVolume.GetName()
attachID := getAttachmentName(csiSource.VolumeHandle, csiSource.Driver, node) attachID := getAttachmentName(pvSrc.VolumeHandle, pvSrc.Driver, node)
attachment := &storage.VolumeAttachment{ attachment := &storage.VolumeAttachment{
ObjectMeta: meta.ObjectMeta{ ObjectMeta: meta.ObjectMeta{
@ -79,7 +79,7 @@ func (c *csiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string
}, },
Spec: storage.VolumeAttachmentSpec{ Spec: storage.VolumeAttachmentSpec{
NodeName: node, NodeName: node,
Attacher: csiSource.Driver, Attacher: pvSrc.Driver,
Source: storage.VolumeAttachmentSource{ Source: storage.VolumeAttachmentSource{
PersistentVolumeName: &pvName, PersistentVolumeName: &pvName,
}, },
@ -97,12 +97,12 @@ func (c *csiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string
} }
if alreadyExist { if alreadyExist {
klog.V(4).Info(log("attachment [%v] for volume [%v] already exists (will not be recreated)", attachID, csiSource.VolumeHandle)) klog.V(4).Info(log("attachment [%v] for volume [%v] already exists (will not be recreated)", attachID, pvSrc.VolumeHandle))
} else { } else {
klog.V(4).Info(log("attachment [%v] for volume [%v] created successfully", attachID, csiSource.VolumeHandle)) klog.V(4).Info(log("attachment [%v] for volume [%v] created successfully", attachID, pvSrc.VolumeHandle))
} }
if _, err := c.waitForVolumeAttachment(csiSource.VolumeHandle, attachID, csiTimeout); err != nil { if _, err := c.waitForVolumeAttachment(pvSrc.VolumeHandle, attachID, csiTimeout); err != nil {
return "", err return "", err
} }
@ -113,7 +113,7 @@ func (c *csiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string
} }
func (c *csiAttacher) WaitForAttach(spec *volume.Spec, _ string, pod *v1.Pod, timeout time.Duration) (string, error) { func (c *csiAttacher) WaitForAttach(spec *volume.Spec, _ string, pod *v1.Pod, timeout time.Duration) (string, error) {
source, err := getCSISourceFromSpec(spec) source, err := getPVSourceFromSpec(spec)
if err != nil { if err != nil {
klog.Error(log("attacher.WaitForAttach failed to extract CSI volume source: %v", err)) klog.Error(log("attacher.WaitForAttach failed to extract CSI volume source: %v", err))
return "", err return "", err
@ -220,14 +220,18 @@ func (c *csiAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.No
klog.Error(log("attacher.VolumesAreAttached missing volume.Spec")) klog.Error(log("attacher.VolumesAreAttached missing volume.Spec"))
return nil, errors.New("missing spec") return nil, errors.New("missing spec")
} }
source, err := getCSISourceFromSpec(spec) pvSrc, err := getPVSourceFromSpec(spec)
if err != nil { if err != nil {
klog.Error(log("attacher.VolumesAreAttached failed: %v", err)) attached[spec] = false
klog.Error(log("attacher.VolumesAreAttached failed to get CSIPersistentVolumeSource: %v", err))
continue continue
} }
skip, err := c.plugin.skipAttach(source.Driver) driverName := pvSrc.Driver
volumeHandle := pvSrc.VolumeHandle
skip, err := c.plugin.skipAttach(driverName)
if err != nil { if err != nil {
klog.Error(log("Failed to check CSIDriver for %s: %s", source.Driver, err)) klog.Error(log("Failed to check CSIDriver for %s: %s", driverName, err))
} else { } else {
if skip { if skip {
// This volume is not attachable, pretend it's attached // This volume is not attachable, pretend it's attached
@ -236,7 +240,7 @@ func (c *csiAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.No
} }
} }
attachID := getAttachmentName(source.VolumeHandle, source.Driver, string(nodeName)) attachID := getAttachmentName(volumeHandle, driverName, string(nodeName))
klog.V(4).Info(log("probing attachment status for VolumeAttachment %v", attachID)) klog.V(4).Info(log("probing attachment status for VolumeAttachment %v", attachID))
attach, err := c.k8s.StorageV1().VolumeAttachments().Get(attachID, meta.GetOptions{}) attach, err := c.k8s.StorageV1().VolumeAttachments().Get(attachID, meta.GetOptions{})
if err != nil { if err != nil {
@ -285,9 +289,9 @@ func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMo
if spec == nil { if spec == nil {
return fmt.Errorf("attacher.MountDevice failed, spec is nil") return fmt.Errorf("attacher.MountDevice failed, spec is nil")
} }
csiSource, err := getCSISourceFromSpec(spec) csiSource, err := getPVSourceFromSpec(spec)
if err != nil { if err != nil {
klog.Error(log("attacher.MountDevice failed to get CSI persistent source: %v", err)) klog.Error(log("attacher.MountDevice failed to get CSIPersistentVolumeSource: %v", err))
return err return err
} }

View File

@ -37,7 +37,6 @@ import (
fakeclient "k8s.io/client-go/kubernetes/fake" fakeclient "k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing" core "k8s.io/client-go/testing"
utiltesting "k8s.io/client-go/util/testing" utiltesting "k8s.io/client-go/util/testing"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing" volumetest "k8s.io/kubernetes/pkg/volume/testing"
@ -84,16 +83,17 @@ func markVolumeAttached(t *testing.T, client clientset.Interface, watch *watch.R
t.Error(err) t.Error(err)
} }
if attach != nil { if attach != nil {
klog.Infof("stopping wait") t.Logf("attachment found on try %d, stopping wait...", i)
break break
} }
} }
klog.Infof("stopped wait") t.Logf("stopped waiting for attachment")
if attach == nil { if attach == nil {
t.Logf("attachment not found for id:%v", attachID) t.Logf("attachment not found for id:%v", attachID)
} else { } else {
attach.Status = status attach.Status = status
t.Logf("updating attachment %s with attach status %v", attachID, status)
_, err := client.StorageV1().VolumeAttachments().Update(attach) _, err := client.StorageV1().VolumeAttachments().Update(attach)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -103,13 +103,13 @@ func markVolumeAttached(t *testing.T, client clientset.Interface, watch *watch.R
} }
func TestAttacherAttach(t *testing.T) { func TestAttacherAttach(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
nodeName string nodeName string
driverName string driverName string
volumeName string volumeName string
attachID string attachID string
spec *volume.Spec
injectAttacherError bool injectAttacherError bool
shouldFail bool shouldFail bool
}{ }{
@ -119,6 +119,7 @@ func TestAttacherAttach(t *testing.T) {
driverName: "testdriver-01", driverName: "testdriver-01",
volumeName: "testvol-01", volumeName: "testvol-01",
attachID: getAttachmentName("testvol-01", "testdriver-01", "testnode-01"), attachID: getAttachmentName("testvol-01", "testdriver-01", "testnode-01"),
spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "testdriver-01", "testvol-01"), false),
}, },
{ {
name: "test ok 2", name: "test ok 2",
@ -126,6 +127,7 @@ func TestAttacherAttach(t *testing.T) {
driverName: "driver02", driverName: "driver02",
volumeName: "vol02", volumeName: "vol02",
attachID: getAttachmentName("vol02", "driver02", "node02"), attachID: getAttachmentName("vol02", "driver02", "node02"),
spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver02", "vol02"), false),
}, },
{ {
name: "mismatch vol", name: "mismatch vol",
@ -133,6 +135,7 @@ func TestAttacherAttach(t *testing.T) {
driverName: "driver02", driverName: "driver02",
volumeName: "vol01", volumeName: "vol01",
attachID: getAttachmentName("vol02", "driver02", "node02"), attachID: getAttachmentName("vol02", "driver02", "node02"),
spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver02", "vol01"), false),
shouldFail: true, shouldFail: true,
}, },
{ {
@ -141,6 +144,7 @@ func TestAttacherAttach(t *testing.T) {
driverName: "driver000", driverName: "driver000",
volumeName: "vol02", volumeName: "vol02",
attachID: getAttachmentName("vol02", "driver02", "node02"), attachID: getAttachmentName("vol02", "driver02", "node02"),
spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver01", "vol02"), false),
shouldFail: true, shouldFail: true,
}, },
{ {
@ -149,6 +153,7 @@ func TestAttacherAttach(t *testing.T) {
driverName: "driver000", driverName: "driver000",
volumeName: "vol02", volumeName: "vol02",
attachID: getAttachmentName("vol02", "driver02", "node02"), attachID: getAttachmentName("vol02", "driver02", "node02"),
spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver02", "vol02"), false),
shouldFail: true, shouldFail: true,
}, },
{ {
@ -157,13 +162,31 @@ func TestAttacherAttach(t *testing.T) {
driverName: "driver02", driverName: "driver02",
volumeName: "vol02", volumeName: "vol02",
attachID: getAttachmentName("vol02", "driver02", "node02"), attachID: getAttachmentName("vol02", "driver02", "node02"),
spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver02", "vol02"), false),
injectAttacherError: true, injectAttacherError: true,
shouldFail: true, shouldFail: true,
}, },
{
name: "test with volume source",
nodeName: "node000",
driverName: "driver000",
volumeName: "vol02",
attachID: getAttachmentName("vol02", "driver02", "node02"),
spec: volume.NewSpecFromVolume(makeTestVol("pv01", "driver02")),
shouldFail: true, // csi not enabled
},
{
name: "missing spec",
nodeName: "node000",
driverName: "driver000",
volumeName: "vol02",
attachID: getAttachmentName("vol02", "driver02", "node02"),
shouldFail: true, // csi not enabled
},
} }
// attacher loop // attacher loop
for i, tc := range testCases { for _, tc := range testCases {
t.Logf("test case: %s", tc.name) t.Logf("test case: %s", tc.name)
plug, fakeWatcher, tmpDir, _ := newTestWatchPlugin(t, nil) plug, fakeWatcher, tmpDir, _ := newTestWatchPlugin(t, nil)
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
@ -175,9 +198,7 @@ func TestAttacherAttach(t *testing.T) {
csiAttacher := attacher.(*csiAttacher) csiAttacher := attacher.(*csiAttacher)
spec := volume.NewSpecFromPersistentVolume(makeTestPV(fmt.Sprintf("test-pv%d", i), 10, tc.driverName, tc.volumeName), false) go func(spec *volume.Spec, id, nodename string, fail bool) {
go func(id, nodename string, fail bool) {
attachID, err := csiAttacher.Attach(spec, types.NodeName(nodename)) attachID, err := csiAttacher.Attach(spec, types.NodeName(nodename))
if !fail && err != nil { if !fail && err != nil {
t.Errorf("expecting no failure, but got err: %v", err) t.Errorf("expecting no failure, but got err: %v", err)
@ -188,7 +209,83 @@ func TestAttacherAttach(t *testing.T) {
if attachID != id && !fail { if attachID != id && !fail {
t.Errorf("expecting attachID %v, got %v", id, attachID) t.Errorf("expecting attachID %v, got %v", id, attachID)
} }
}(tc.attachID, tc.nodeName, tc.shouldFail) }(tc.spec, tc.attachID, tc.nodeName, tc.shouldFail)
var status storage.VolumeAttachmentStatus
if tc.injectAttacherError {
status.Attached = false
status.AttachError = &storage.VolumeError{
Message: "attacher error",
}
} else {
status.Attached = true
}
markVolumeAttached(t, csiAttacher.k8s, fakeWatcher, tc.attachID, status)
}
}
func TestAttacherAttachWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
testCases := []struct {
name string
nodeName string
driverName string
volumeName string
attachID string
spec *volume.Spec
injectAttacherError bool
shouldFail bool
}{
{
name: "test ok 1 with PV",
nodeName: "node01",
attachID: getAttachmentName("vol01", "driver01", "node01"),
spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv01", 10, "driver01", "vol01"), false),
},
{
name: "test failure, attach with volSrc",
nodeName: "node01",
attachID: getAttachmentName("vol01", "driver01", "node01"),
spec: volume.NewSpecFromVolume(makeTestVol("vol01", "driver01")),
shouldFail: true,
},
{
name: "attacher error",
nodeName: "node02",
attachID: getAttachmentName("vol02", "driver02", "node02"),
spec: volume.NewSpecFromPersistentVolume(makeTestPV("pv02", 10, "driver02", "vol02"), false),
injectAttacherError: true,
shouldFail: true,
},
{
name: "missing spec",
nodeName: "node02",
attachID: getAttachmentName("vol02", "driver02", "node02"),
shouldFail: true,
},
}
// attacher loop
for _, tc := range testCases {
t.Logf("test case: %s", tc.name)
plug, fakeWatcher, tmpDir, _ := newTestWatchPlugin(t, nil)
defer os.RemoveAll(tmpDir)
attacher, err := plug.NewAttacher()
if err != nil {
t.Fatalf("failed to create new attacher: %v", err)
}
csiAttacher := attacher.(*csiAttacher)
go func(spec *volume.Spec, id, nodename string, fail bool) {
attachID, err := csiAttacher.Attach(spec, types.NodeName(nodename))
if fail != (err != nil) {
t.Errorf("expecting no failure, but got err: %v", err)
}
if attachID != id && !fail {
t.Errorf("expecting attachID %v, got %v", id, attachID)
}
}(tc.spec, tc.attachID, tc.nodeName, tc.shouldFail)
var status storage.VolumeAttachmentStatus var status storage.VolumeAttachmentStatus
if tc.injectAttacherError { if tc.injectAttacherError {
@ -260,23 +357,25 @@ func TestAttacherWithCSIDriver(t *testing.T) {
return return
} }
go func(volSpec *volume.Spec, expectAttach bool) {
attachID, err := csiAttacher.Attach(volSpec, types.NodeName("node"))
if err != nil {
t.Errorf("Attach() failed: %s", err)
}
if expectAttach && attachID == "" {
t.Errorf("Expected attachID, got nothing")
}
if !expectAttach && attachID != "" {
t.Errorf("Expected empty attachID, got %q", attachID)
}
}(spec, test.expectVolumeAttachment)
if test.expectVolumeAttachment {
expectedAttachID := getAttachmentName("test-vol", test.driver, "node") expectedAttachID := getAttachmentName("test-vol", test.driver, "node")
status := storage.VolumeAttachmentStatus{ status := storage.VolumeAttachmentStatus{
Attached: true, Attached: true,
} }
if test.expectVolumeAttachment { markVolumeAttached(t, csiAttacher.k8s, fakeWatcher, expectedAttachID, status)
go markVolumeAttached(t, csiAttacher.k8s, fakeWatcher, expectedAttachID, status)
}
attachID, err := csiAttacher.Attach(spec, types.NodeName("node"))
if err != nil {
t.Errorf("Attach() failed: %s", err)
}
if test.expectVolumeAttachment && attachID == "" {
t.Errorf("Expected attachID, got nothing")
}
if !test.expectVolumeAttachment && attachID != "" {
t.Errorf("Expected empty attachID, got %q", attachID)
} }
}) })
} }
@ -354,6 +453,7 @@ func TestAttacherWaitForAttach(t *testing.T) {
name string name string
driver string driver string
makeAttachment func() *storage.VolumeAttachment makeAttachment func() *storage.VolumeAttachment
spec *volume.Spec
expectedAttachID string expectedAttachID string
expectError bool expectError bool
}{ }{
@ -367,9 +467,22 @@ func TestAttacherWaitForAttach(t *testing.T) {
successfulAttachment.Status.Attached = true successfulAttachment.Status.Attached = true
return successfulAttachment return successfulAttachment
}, },
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "attachable", "test-vol"), false),
expectedAttachID: getAttachmentName("test-vol", "attachable", "node"), expectedAttachID: getAttachmentName("test-vol", "attachable", "node"),
expectError: false, expectError: false,
}, },
{
name: "failed attach with vol source",
makeAttachment: func() *storage.VolumeAttachment {
testAttachID := getAttachmentName("test-vol", "attachable", "node")
successfulAttachment := makeTestAttachment(testAttachID, "node", "volSrc01")
successfulAttachment.Status.Attached = true
return successfulAttachment
},
spec: volume.NewSpecFromVolume(makeTestVol("volSrc01", "attachable")),
expectError: true,
},
{ {
name: "failed attach", name: "failed attach",
driver: "attachable", driver: "attachable",
@ -387,7 +500,6 @@ func TestAttacherWaitForAttach(t *testing.T) {
t.Fatalf("failed to create new attacher: %v", err) t.Fatalf("failed to create new attacher: %v", err)
} }
csiAttacher := attacher.(*csiAttacher) csiAttacher := attacher.(*csiAttacher)
spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, test.driver, "test-vol"), false)
if test.makeAttachment != nil { if test.makeAttachment != nil {
attachment := test.makeAttachment() attachment := test.makeAttachment()
@ -402,7 +514,7 @@ func TestAttacherWaitForAttach(t *testing.T) {
t.Logf("created test VolumeAttachment %+v", gotAttachment) t.Logf("created test VolumeAttachment %+v", gotAttachment)
} }
attachID, err := csiAttacher.WaitForAttach(spec, "", nil, time.Second) attachID, err := csiAttacher.WaitForAttach(test.spec, "", nil, time.Second)
if err != nil && !test.expectError { if err != nil && !test.expectError {
t.Errorf("Unexpected error: %s", err) t.Errorf("Unexpected error: %s", err)
} }
@ -416,6 +528,86 @@ func TestAttacherWaitForAttach(t *testing.T) {
} }
} }
func TestAttacherWaitForAttachWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
tests := []struct {
name string
driver string
makeAttachment func() *storage.VolumeAttachment
spec *volume.Spec
expectedAttachID string
expectError bool
}{
{
name: "successful attach with PV",
makeAttachment: func() *storage.VolumeAttachment {
testAttachID := getAttachmentName("test-vol", "attachable", "node")
successfulAttachment := makeTestAttachment(testAttachID, "node", "test-pv")
successfulAttachment.Status.Attached = true
return successfulAttachment
},
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "attachable", "test-vol"), false),
expectedAttachID: getAttachmentName("test-vol", "attachable", "node"),
expectError: false,
},
{
name: "failed attach with volSrc",
makeAttachment: func() *storage.VolumeAttachment {
testAttachID := getAttachmentName("test-vol", "attachable", "node")
successfulAttachment := makeTestAttachment(testAttachID, "node", "volSrc01")
successfulAttachment.Status.Attached = true
return successfulAttachment
},
spec: volume.NewSpecFromVolume(makeTestVol("volSrc01", "attachable")),
expectError: true,
},
{
name: "failed attach",
driver: "non-attachable",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "non-attachable", "test-vol"), false),
expectError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
plug, _, tmpDir, _ := newTestWatchPlugin(t, nil)
defer os.RemoveAll(tmpDir)
attacher, err := plug.NewAttacher()
if err != nil {
t.Fatalf("failed to create new attacher: %v", err)
}
csiAttacher := attacher.(*csiAttacher)
if test.makeAttachment != nil {
attachment := test.makeAttachment()
_, err = csiAttacher.k8s.StorageV1().VolumeAttachments().Create(attachment)
if err != nil {
t.Fatalf("failed to create VolumeAttachment: %v", err)
}
gotAttachment, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Get(attachment.Name, meta.GetOptions{})
if err != nil {
t.Fatalf("failed to get created VolumeAttachment: %v", err)
}
t.Logf("created test VolumeAttachment %+v", gotAttachment)
}
attachID, err := csiAttacher.WaitForAttach(test.spec, "", nil, time.Second)
if test.expectError != (err != nil) {
t.Errorf("Unexpected error: %s", err)
return
}
if attachID != test.expectedAttachID {
t.Errorf("Expected attachID %q, got %q", test.expectedAttachID, attachID)
}
})
}
}
func TestAttacherWaitForVolumeAttachment(t *testing.T) { func TestAttacherWaitForVolumeAttachment(t *testing.T) {
nodeName := "test-node" nodeName := "test-node"
testCases := []struct { testCases := []struct {
@ -518,6 +710,51 @@ func TestAttacherWaitForVolumeAttachment(t *testing.T) {
} }
func TestAttacherVolumesAreAttached(t *testing.T) { func TestAttacherVolumesAreAttached(t *testing.T) {
type attachedSpec struct {
volName string
spec *volume.Spec
attached bool
}
testCases := []struct {
name string
attachedSpecs []attachedSpec
}{
{
"attach and detach",
[]attachedSpec{
{"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), true},
{"vol1", volume.NewSpecFromPersistentVolume(makeTestPV("pv1", 20, testDriver, "vol1"), false), true},
{"vol2", volume.NewSpecFromPersistentVolume(makeTestPV("pv2", 10, testDriver, "vol2"), false), false},
{"vol3", volume.NewSpecFromPersistentVolume(makeTestPV("pv3", 10, testDriver, "vol3"), false), false},
{"vol4", volume.NewSpecFromPersistentVolume(makeTestPV("pv4", 20, testDriver, "vol4"), false), true},
},
},
{
"all detached",
[]attachedSpec{
{"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), false},
{"vol1", volume.NewSpecFromPersistentVolume(makeTestPV("pv1", 20, testDriver, "vol1"), false), false},
{"vol2", volume.NewSpecFromPersistentVolume(makeTestPV("pv2", 10, testDriver, "vol2"), false), false},
},
},
{
"all attached",
[]attachedSpec{
{"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), true},
{"vol1", volume.NewSpecFromPersistentVolume(makeTestPV("pv1", 20, testDriver, "vol1"), false), true},
},
},
{
"include non-attable",
[]attachedSpec{
{"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), true},
{"vol1", volume.NewSpecFromVolume(makeTestVol("pv1", testDriver)), false},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
plug, tmpDir := newTestPlugin(t, nil) plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
@ -528,25 +765,13 @@ func TestAttacherVolumesAreAttached(t *testing.T) {
csiAttacher := attacher.(*csiAttacher) csiAttacher := attacher.(*csiAttacher)
nodeName := "test-node" nodeName := "test-node"
testCases := []struct {
name string
attachedStats map[string]bool
}{
{"attach + detach", map[string]bool{"vol-01": true, "vol-02": true, "vol-03": false, "vol-04": false, "vol-05": true}},
{"all detached", map[string]bool{"vol-11": false, "vol-12": false, "vol-13": false, "vol-14": false, "vol-15": false}},
{"all attached", map[string]bool{"vol-21": true, "vol-22": true, "vol-23": true, "vol-24": true, "vol-25": true}},
}
for _, tc := range testCases {
var specs []*volume.Spec var specs []*volume.Spec
// create and save volume attchments // create and save volume attchments
for volName, stat := range tc.attachedStats { for _, attachedSpec := range tc.attachedSpecs {
pv := makeTestPV("test-pv", 10, testDriver, volName) specs = append(specs, attachedSpec.spec)
spec := volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly) attachID := getAttachmentName(attachedSpec.volName, testDriver, nodeName)
specs = append(specs, spec) attachment := makeTestAttachment(attachID, nodeName, attachedSpec.spec.Name())
attachID := getAttachmentName(volName, testDriver, nodeName) attachment.Status.Attached = attachedSpec.attached
attachment := makeTestAttachment(attachID, nodeName, pv.GetName())
attachment.Status.Attached = stat
_, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(attachment) _, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(attachment)
if err != nil { if err != nil {
t.Fatalf("failed to attach: %v", err) t.Fatalf("failed to attach: %v", err)
@ -558,20 +783,92 @@ func TestAttacherVolumesAreAttached(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(tc.attachedStats) != len(stats) { if len(tc.attachedSpecs) != len(stats) {
t.Errorf("expecting %d attachment status, got %d", len(tc.attachedStats), len(stats)) t.Errorf("expecting %d attachment status, got %d", len(tc.attachedSpecs), len(stats))
} }
// compare attachment status for each spec // compare attachment status for each spec
for spec, stat := range stats { for _, attached := range tc.attachedSpecs {
source, err := getCSISourceFromSpec(spec) stat, ok := stats[attached.spec]
if attached.attached && !ok {
t.Error("failed to retrieve attached status for:", attached.spec)
}
if attached.attached != stat {
t.Errorf("expecting volume attachment %t, got %t", attached.attached, stat)
}
}
})
}
}
func TestAttacherVolumesAreAttachedWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
type attachedSpec struct {
volName string
spec *volume.Spec
attached bool
}
testCases := []struct {
name string
attachedSpecs []attachedSpec
}{
{
"attach and detach with volume sources",
[]attachedSpec{
{"vol0", volume.NewSpecFromPersistentVolume(makeTestPV("pv0", 10, testDriver, "vol0"), false), true},
{"vol1", volume.NewSpecFromVolume(makeTestVol("pv1", testDriver)), false},
{"vol2", volume.NewSpecFromPersistentVolume(makeTestPV("pv2", 10, testDriver, "vol2"), false), true},
{"vol3", volume.NewSpecFromVolume(makeTestVol("pv3", testDriver)), false},
{"vol4", volume.NewSpecFromPersistentVolume(makeTestPV("pv4", 20, testDriver, "vol4"), false), true},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir)
attacher, err := plug.NewAttacher()
if err != nil { if err != nil {
t.Error(err) t.Fatalf("failed to create new attacher: %v", err)
} }
if stat != tc.attachedStats[source.VolumeHandle] { csiAttacher := attacher.(*csiAttacher)
t.Errorf("expecting volume attachment %t, got %t", tc.attachedStats[source.VolumeHandle], stat) nodeName := "test-node"
var specs []*volume.Spec
// create and save volume attchments
for _, attachedSpec := range tc.attachedSpecs {
specs = append(specs, attachedSpec.spec)
attachID := getAttachmentName(attachedSpec.volName, testDriver, nodeName)
attachment := makeTestAttachment(attachID, nodeName, attachedSpec.spec.Name())
attachment.Status.Attached = attachedSpec.attached
_, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(attachment)
if err != nil {
t.Fatalf("failed to attach: %v", err)
} }
} }
// retrieve attached status
stats, err := csiAttacher.VolumesAreAttached(specs, types.NodeName(nodeName))
if err != nil {
t.Fatal(err)
}
if len(tc.attachedSpecs) != len(stats) {
t.Errorf("expecting %d attachment status, got %d", len(tc.attachedSpecs), len(stats))
}
// compare attachment status for each spec
for _, attached := range tc.attachedSpecs {
stat, ok := stats[attached.spec]
if attached.attached && !ok {
t.Error("failed to retrieve attached status for:", attached.spec)
}
if attached.attached != stat {
t.Errorf("expecting volume attachment %t, got %t", attached.attached, stat)
}
}
})
} }
} }
@ -708,6 +1005,7 @@ func TestAttacherGetDeviceMountPath(t *testing.T) {
} }
func TestAttacherMountDevice(t *testing.T) { func TestAttacherMountDevice(t *testing.T) {
pvName := "test-pv"
testCases := []struct { testCases := []struct {
testName string testName string
volName string volName string
@ -715,13 +1013,15 @@ func TestAttacherMountDevice(t *testing.T) {
deviceMountPath string deviceMountPath string
stageUnstageSet bool stageUnstageSet bool
shouldFail bool shouldFail bool
spec *volume.Spec
}{ }{
{ {
testName: "normal", testName: "normal PV",
volName: "test-vol1", volName: "test-vol1",
devicePath: "path1", devicePath: "path1",
deviceMountPath: "path2", deviceMountPath: "path2",
stageUnstageSet: true, stageUnstageSet: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
}, },
{ {
testName: "no vol name", testName: "no vol name",
@ -730,6 +1030,7 @@ func TestAttacherMountDevice(t *testing.T) {
deviceMountPath: "path2", deviceMountPath: "path2",
stageUnstageSet: true, stageUnstageSet: true,
shouldFail: true, shouldFail: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, ""), false),
}, },
{ {
testName: "no device path", testName: "no device path",
@ -738,6 +1039,7 @@ func TestAttacherMountDevice(t *testing.T) {
deviceMountPath: "path2", deviceMountPath: "path2",
stageUnstageSet: true, stageUnstageSet: true,
shouldFail: false, shouldFail: false,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
}, },
{ {
testName: "no device mount path", testName: "no device mount path",
@ -746,6 +1048,7 @@ func TestAttacherMountDevice(t *testing.T) {
deviceMountPath: "", deviceMountPath: "",
stageUnstageSet: true, stageUnstageSet: true,
shouldFail: true, shouldFail: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
}, },
{ {
testName: "stage_unstage cap not set", testName: "stage_unstage cap not set",
@ -753,13 +1056,20 @@ func TestAttacherMountDevice(t *testing.T) {
devicePath: "path1", devicePath: "path1",
deviceMountPath: "path2", deviceMountPath: "path2",
stageUnstageSet: false, stageUnstageSet: false,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
{
testName: "failure with volume source",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
shouldFail: true,
spec: volume.NewSpecFromVolume(makeTestVol(pvName, testDriver)),
}, },
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Logf("Running test case: %s", tc.testName) t.Logf("Running test case: %s", tc.testName)
var spec *volume.Spec
pvName := "test-pv"
// Setup // Setup
// Create a new attacher // Create a new attacher
@ -777,11 +1087,6 @@ func TestAttacherMountDevice(t *testing.T) {
} }
nodeName := string(csiAttacher.plugin.host.GetNodeName()) nodeName := string(csiAttacher.plugin.host.GetNodeName())
// Create spec
pv := makeTestPV(pvName, 10, testDriver, tc.volName)
spec = volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly)
attachID := getAttachmentName(tc.volName, testDriver, nodeName) attachID := getAttachmentName(tc.volName, testDriver, nodeName)
// Set up volume attachment // Set up volume attachment
@ -795,7 +1100,147 @@ func TestAttacherMountDevice(t *testing.T) {
}() }()
// Run // Run
err = csiAttacher.MountDevice(spec, tc.devicePath, tc.deviceMountPath) err = csiAttacher.MountDevice(tc.spec, tc.devicePath, tc.deviceMountPath)
// Verify
if err != nil {
if !tc.shouldFail {
t.Errorf("test should not fail, but error occurred: %v", err)
}
continue
}
if err == nil && tc.shouldFail {
t.Errorf("test should fail, but no error occurred")
}
// Verify call goes through all the way
numStaged := 1
if !tc.stageUnstageSet {
numStaged = 0
}
cdc := csiAttacher.csiClient.(*fakeCsiDriverClient)
staged := cdc.nodeClient.GetNodeStagedVolumes()
if len(staged) != numStaged {
t.Errorf("got wrong number of staged volumes, expecting %v got: %v", numStaged, len(staged))
}
if tc.stageUnstageSet {
vol, ok := staged[tc.volName]
if !ok {
t.Errorf("could not find staged volume: %s", tc.volName)
}
if vol.Path != tc.deviceMountPath {
t.Errorf("expected mount path: %s. got: %s", tc.deviceMountPath, vol.Path)
}
}
}
}
func TestAttacherMountDeviceWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
pvName := "test-pv"
testCases := []struct {
testName string
volName string
devicePath string
deviceMountPath string
stageUnstageSet bool
shouldFail bool
spec *volume.Spec
}{
{
testName: "normal PV",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
stageUnstageSet: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
{
testName: "failure with volSrc",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
shouldFail: true,
spec: volume.NewSpecFromVolume(makeTestVol(pvName, testDriver)),
},
{
testName: "no vol name",
volName: "",
devicePath: "path1",
deviceMountPath: "path2",
stageUnstageSet: true,
shouldFail: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, ""), false),
},
{
testName: "no device path",
volName: "test-vol1",
devicePath: "",
deviceMountPath: "path2",
stageUnstageSet: true,
shouldFail: false,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
{
testName: "no device mount path",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "",
stageUnstageSet: true,
shouldFail: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
{
testName: "stage_unstage cap not set",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
stageUnstageSet: false,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
{
testName: "missing spec",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
shouldFail: true,
},
}
for _, tc := range testCases {
t.Logf("Running test case: %s", tc.testName)
// Setup
// Create a new attacher
plug, fakeWatcher, tmpDir, _ := newTestWatchPlugin(t, nil)
defer os.RemoveAll(tmpDir)
attacher, err0 := plug.NewAttacher()
if err0 != nil {
t.Fatalf("failed to create new attacher: %v", err0)
}
csiAttacher := attacher.(*csiAttacher)
csiAttacher.csiClient = setupClient(t, tc.stageUnstageSet)
if tc.deviceMountPath != "" {
tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath)
}
nodeName := string(csiAttacher.plugin.host.GetNodeName())
attachID := getAttachmentName(tc.volName, testDriver, nodeName)
// Set up volume attachment
attachment := makeTestAttachment(attachID, nodeName, pvName)
_, err := csiAttacher.k8s.StorageV1().VolumeAttachments().Create(attachment)
if err != nil {
t.Fatalf("failed to attach: %v", err)
}
go func() {
fakeWatcher.Delete(attachment)
}()
// Run
err = csiAttacher.MountDevice(tc.spec, tc.devicePath, tc.deviceMountPath)
// Verify // Verify
if err != nil { if err != nil {

View File

@ -78,6 +78,7 @@ func (c *fakeCsiDriverClient) NodePublishVolume(
req := &csipbv1.NodePublishVolumeRequest{ req := &csipbv1.NodePublishVolumeRequest{
VolumeId: volID, VolumeId: volID,
TargetPath: targetPath, TargetPath: targetPath,
StagingTargetPath: stagingTargetPath,
Readonly: readOnly, Readonly: readOnly,
PublishContext: publishContext, PublishContext: publishContext,
VolumeContext: volumeContext, VolumeContext: volumeContext,

View File

@ -18,6 +18,7 @@ package csi
import ( import (
"context" "context"
"crypto/sha256"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -42,13 +43,15 @@ var (
volHandle, volHandle,
driverName, driverName,
nodeName, nodeName,
attachmentID string attachmentID,
driverMode string
}{ }{
"specVolID", "specVolID",
"volumeHandle", "volumeHandle",
"driverName", "driverName",
"nodeName", "nodeName",
"attachmentID", "attachmentID",
"driverMode",
} }
) )
@ -57,6 +60,7 @@ type csiMountMgr struct {
k8s kubernetes.Interface k8s kubernetes.Interface
plugin *csiPlugin plugin *csiPlugin
driverName csiDriverName driverName csiDriverName
driverMode driverMode
volumeID string volumeID string
specVolumeID string specVolumeID string
readOnly bool readOnly bool
@ -107,18 +111,71 @@ func (c *csiMountMgr) SetUpAt(dir string, fsGroup *int64) error {
return nil return nil
} }
csiSource, err := getCSISourceFromSpec(c.spec) csi := c.csiClient
ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
defer cancel()
volSrc, pvSrc, err := getSourceFromSpec(c.spec)
if err != nil { if err != nil {
klog.Error(log("mounter.SetupAt failed to get CSI persistent source: %v", err)) klog.Error(log("mounter.SetupAt failed to get CSI persistent source: %v", err))
return err return err
} }
csi := c.csiClient driverName := c.driverName
ctx, cancel := context.WithTimeout(context.Background(), csiTimeout) volumeHandle := c.volumeID
defer cancel() readOnly := c.readOnly
accessMode := api.ReadWriteOnce
var (
fsType string
volAttribs map[string]string
nodePublishSecrets map[string]string
publishContext map[string]string
mountOptions []string
deviceMountPath string
secretRef *api.SecretReference
)
switch {
case volSrc != nil:
if !utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
return fmt.Errorf("CSIInlineVolume feature required")
}
if c.driverMode != ephemeralDriverMode {
return fmt.Errorf("unexpected driver mode: %s", c.driverMode)
}
if volSrc.FSType != nil {
fsType = *volSrc.FSType
}
volAttribs = volSrc.VolumeAttributes
if volSrc.NodePublishSecretRef != nil {
secretName := volSrc.NodePublishSecretRef.Name
ns := c.pod.Namespace
secretRef = &api.SecretReference{Name: secretName, Namespace: ns}
}
case pvSrc != nil:
if c.driverMode != persistentDriverMode {
return fmt.Errorf("unexpected driver mode: %s", c.driverMode)
}
fsType = pvSrc.FSType
volAttribs = pvSrc.VolumeAttributes
if pvSrc.NodePublishSecretRef != nil {
secretRef = pvSrc.NodePublishSecretRef
}
//TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI
if c.spec.PersistentVolume.Spec.AccessModes != nil {
accessMode = c.spec.PersistentVolume.Spec.AccessModes[0]
}
mountOptions = c.spec.PersistentVolume.Spec.MountOptions
// Check for STAGE_UNSTAGE_VOLUME set and populate deviceMountPath if so // Check for STAGE_UNSTAGE_VOLUME set and populate deviceMountPath if so
deviceMountPath := ""
stageUnstageSet, err := csi.NodeSupportsStageUnstage(ctx) stageUnstageSet, err := csi.NodeSupportsStageUnstage(ctx)
if err != nil { if err != nil {
klog.Error(log("mounter.SetUpAt failed to check for STAGE_UNSTAGE_VOLUME capabilty: %v", err)) klog.Error(log("mounter.SetUpAt failed to check for STAGE_UNSTAGE_VOLUME capabilty: %v", err))
@ -132,24 +189,19 @@ func (c *csiMountMgr) SetUpAt(dir string, fsGroup *int64) error {
return err return err
} }
} }
// search for attachment by VolumeAttachment.Spec.Source.PersistentVolumeName // search for attachment by VolumeAttachment.Spec.Source.PersistentVolumeName
if c.publishContext == nil { if c.publishContext == nil {
nodeName := string(c.plugin.host.GetNodeName()) nodeName := string(c.plugin.host.GetNodeName())
c.publishContext, err = c.plugin.getPublishContext(c.k8s, c.volumeID, string(c.driverName), nodeName) c.publishContext, err = c.plugin.getPublishContext(c.k8s, volumeHandle, string(driverName), nodeName)
if err != nil { if err != nil {
return err return err
} }
publishContext = c.publishContext
} }
attribs := csiSource.VolumeAttributes default:
return fmt.Errorf("volume source not found in volume.Spec")
nodePublishSecrets := map[string]string{}
if csiSource.NodePublishSecretRef != nil {
nodePublishSecrets, err = getCredentialsFromSecret(c.k8s, csiSource.NodePublishSecretRef)
if err != nil {
return fmt.Errorf("fetching NodePublishSecretRef %s/%s failed: %v",
csiSource.NodePublishSecretRef.Namespace, csiSource.NodePublishSecretRef.Name, err)
}
} }
// create target_dir before call to NodePublish // create target_dir before call to NodePublish
@ -159,10 +211,14 @@ func (c *csiMountMgr) SetUpAt(dir string, fsGroup *int64) error {
} }
klog.V(4).Info(log("created target path successfully [%s]", dir)) klog.V(4).Info(log("created target path successfully [%s]", dir))
//TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI nodePublishSecrets = map[string]string{}
accessMode := api.ReadWriteOnce if secretRef != nil {
if c.spec.PersistentVolume.Spec.AccessModes != nil { nodePublishSecrets, err = getCredentialsFromSecret(c.k8s, secretRef)
accessMode = c.spec.PersistentVolume.Spec.AccessModes[0] if err != nil {
return fmt.Errorf("fetching NodePublishSecretRef %s/%s failed: %v",
secretRef.Namespace, secretRef.Name, err)
}
} }
// Inject pod information into volume_attributes // Inject pod information into volume_attributes
@ -172,28 +228,27 @@ func (c *csiMountMgr) SetUpAt(dir string, fsGroup *int64) error {
return err return err
} }
if podAttrs != nil { if podAttrs != nil {
if attribs == nil { if volAttribs == nil {
attribs = podAttrs volAttribs = podAttrs
} else { } else {
for k, v := range podAttrs { for k, v := range podAttrs {
attribs[k] = v volAttribs[k] = v
} }
} }
} }
fsType := csiSource.FSType
err = csi.NodePublishVolume( err = csi.NodePublishVolume(
ctx, ctx,
c.volumeID, volumeHandle,
c.readOnly, readOnly,
deviceMountPath, deviceMountPath,
dir, dir,
accessMode, accessMode,
c.publishContext, publishContext,
attribs, volAttribs,
nodePublishSecrets, nodePublishSecrets,
fsType, fsType,
c.spec.PersistentVolume.Spec.MountOptions, mountOptions,
) )
if err != nil { if err != nil {
@ -387,3 +442,9 @@ func removeMountDir(plug *csiPlugin, mountPath string) error {
} }
return nil return nil
} }
// makeVolumeHandle returns csi-<sha256(podUID,volSourceSpecName)>
func makeVolumeHandle(podUID, volSourceSpecName string) string {
result := sha256.Sum256([]byte(fmt.Sprintf("%s%s", podUID, volSourceSpecName)))
return fmt.Sprintf("csi-%x", result)
}

View File

@ -21,6 +21,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/rand"
"os" "os"
"path" "path"
"testing" "testing"
@ -266,6 +267,266 @@ func TestMounterSetUp(t *testing.T) {
MounterSetUpTests(t, false) MounterSetUpTests(t, false)
}) })
} }
func TestMounterSetUpSimple(t *testing.T) {
fakeClient := fakeclient.NewSimpleClientset()
plug, tmpDir := newTestPlugin(t, fakeClient)
defer os.RemoveAll(tmpDir)
testCases := []struct {
name string
podUID types.UID
mode driverMode
fsType string
options []string
spec func(string, []string) *volume.Spec
shouldFail bool
}{
{
name: "setup with vol source",
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
mode: ephemeralDriverMode,
fsType: "ext4",
shouldFail: true,
spec: func(fsType string, options []string) *volume.Spec {
volSrc := makeTestVol("pv1", testDriver)
volSrc.CSI.FSType = &fsType
return volume.NewSpecFromVolume(volSrc)
},
},
{
name: "setup with persistent source",
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
mode: persistentDriverMode,
fsType: "zfs",
spec: func(fsType string, options []string) *volume.Spec {
pvSrc := makeTestPV("pv1", 20, testDriver, "vol1")
pvSrc.Spec.CSI.FSType = fsType
pvSrc.Spec.MountOptions = options
return volume.NewSpecFromPersistentVolume(pvSrc, false)
},
},
{
name: "setup with persistent source without unspecified fstype and options",
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
mode: persistentDriverMode,
spec: func(fsType string, options []string) *volume.Spec {
return volume.NewSpecFromPersistentVolume(makeTestPV("pv1", 20, testDriver, "vol2"), false)
},
},
{
name: "setup with missing spec",
shouldFail: true,
spec: func(fsType string, options []string) *volume.Spec { return nil },
},
}
for _, tc := range testCases {
registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t)
t.Run(tc.name, func(t *testing.T) {
mounter, err := plug.NewMounter(
tc.spec(tc.fsType, tc.options),
&api.Pod{ObjectMeta: meta.ObjectMeta{UID: tc.podUID, Namespace: testns}},
volume.VolumeOptions{},
)
if tc.shouldFail && err != nil {
t.Log(err)
return
}
if !tc.shouldFail && err != nil {
t.Fatal("unexpected error:", err)
}
if mounter == nil {
t.Fatal("failed to create CSI mounter")
}
csiMounter := mounter.(*csiMountMgr)
csiMounter.csiClient = setupClient(t, true)
if csiMounter.driverMode != persistentDriverMode {
t.Fatal("unexpected driver mode: ", csiMounter.driverMode)
}
attachID := getAttachmentName(csiMounter.volumeID, string(csiMounter.driverName), string(plug.host.GetNodeName()))
attachment := makeTestAttachment(attachID, "test-node", csiMounter.spec.Name())
_, err = csiMounter.k8s.StorageV1().VolumeAttachments().Create(attachment)
if err != nil {
t.Fatalf("failed to setup VolumeAttachment: %v", err)
}
// Mounter.SetUp()
if err := csiMounter.SetUp(nil); err != nil {
t.Fatalf("mounter.Setup failed: %v", err)
}
// ensure call went all the way
pubs := csiMounter.csiClient.(*fakeCsiDriverClient).nodeClient.GetNodePublishedVolumes()
vol, ok := pubs[csiMounter.volumeID]
if !ok {
t.Error("csi server may not have received NodePublishVolume call")
}
if vol.VolumeHandle != csiMounter.volumeID {
t.Error("volumeHandle not sent to CSI driver properly")
}
devicePath, err := makeDeviceMountPath(plug, csiMounter.spec)
if err != nil {
t.Fatal(err)
}
if vol.DeviceMountPath != devicePath {
t.Errorf("DeviceMountPath not sent properly to CSI driver: %s, %s", vol.DeviceMountPath, devicePath)
}
if !reflect.DeepEqual(vol.MountFlags, csiMounter.spec.PersistentVolume.Spec.MountOptions) {
t.Errorf("unexpected mount flags passed to driver: %+v", vol.MountFlags)
}
if vol.FSType != tc.fsType {
t.Error("unexpected FSType sent to driver:", vol.FSType)
}
if vol.Path != csiMounter.GetPath() {
t.Error("csi server may not have received NodePublishVolume call")
}
})
}
}
func TestMounterSetUpWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
fakeClient := fakeclient.NewSimpleClientset()
plug, tmpDir := newTestPlugin(t, fakeClient)
defer os.RemoveAll(tmpDir)
testCases := []struct {
name string
podUID types.UID
mode driverMode
fsType string
options []string
spec func(string, []string) *volume.Spec
shouldFail bool
}{
{
name: "setup with vol source",
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
mode: ephemeralDriverMode,
fsType: "ext4",
spec: func(fsType string, options []string) *volume.Spec {
volSrc := makeTestVol("pv1", testDriver)
volSrc.CSI.FSType = &fsType
return volume.NewSpecFromVolume(volSrc)
},
},
{
name: "setup with persistent source",
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
mode: persistentDriverMode,
fsType: "zfs",
spec: func(fsType string, options []string) *volume.Spec {
pvSrc := makeTestPV("pv1", 20, testDriver, "vol1")
pvSrc.Spec.CSI.FSType = fsType
pvSrc.Spec.MountOptions = options
return volume.NewSpecFromPersistentVolume(pvSrc, false)
},
},
{
name: "setup with persistent source without unspecified fstype and options",
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
mode: persistentDriverMode,
spec: func(fsType string, options []string) *volume.Spec {
return volume.NewSpecFromPersistentVolume(makeTestPV("pv1", 20, testDriver, "vol2"), false)
},
},
{
name: "setup with missing spec",
shouldFail: true,
spec: func(fsType string, options []string) *volume.Spec { return nil },
},
}
for _, tc := range testCases {
registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t)
t.Run(tc.name, func(t *testing.T) {
mounter, err := plug.NewMounter(
tc.spec(tc.fsType, tc.options),
&api.Pod{ObjectMeta: meta.ObjectMeta{UID: tc.podUID, Namespace: testns}},
volume.VolumeOptions{},
)
if tc.shouldFail && err != nil {
t.Log(err)
return
}
if !tc.shouldFail && err != nil {
t.Fatal("unexpected error:", err)
}
if mounter == nil {
t.Fatal("failed to create CSI mounter")
}
csiMounter := mounter.(*csiMountMgr)
csiMounter.csiClient = setupClient(t, true)
if csiMounter.driverMode != tc.mode {
t.Fatal("unexpected driver mode: ", csiMounter.driverMode)
}
if csiMounter.driverMode == ephemeralDriverMode && csiMounter.volumeID != makeVolumeHandle(string(tc.podUID), csiMounter.specVolumeID) {
t.Fatal("unexpected generated volumeHandle:", csiMounter.volumeID)
}
if csiMounter.driverMode == persistentDriverMode {
attachID := getAttachmentName(csiMounter.volumeID, string(csiMounter.driverName), string(plug.host.GetNodeName()))
attachment := makeTestAttachment(attachID, "test-node", csiMounter.spec.Name())
_, err = csiMounter.k8s.StorageV1().VolumeAttachments().Create(attachment)
if err != nil {
t.Fatalf("failed to setup VolumeAttachment: %v", err)
}
}
// Mounter.SetUp()
if err := csiMounter.SetUp(nil); err != nil {
t.Fatalf("mounter.Setup failed: %v", err)
}
// ensure call went all the way
pubs := csiMounter.csiClient.(*fakeCsiDriverClient).nodeClient.GetNodePublishedVolumes()
vol, ok := pubs[csiMounter.volumeID]
if !ok {
t.Error("csi server may not have received NodePublishVolume call")
}
if vol.VolumeHandle != csiMounter.volumeID {
t.Error("volumeHandle not sent to CSI driver properly")
}
// validate stagingTargetPath
if tc.mode == ephemeralDriverMode && vol.DeviceMountPath != "" {
t.Errorf("unexpected devicePathTarget sent to driver: %s", vol.DeviceMountPath)
}
if tc.mode == persistentDriverMode {
devicePath, err := makeDeviceMountPath(plug, csiMounter.spec)
if err != nil {
t.Fatal(err)
}
if vol.DeviceMountPath != devicePath {
t.Errorf("DeviceMountPath not sent properly to CSI driver: %s, %s", vol.DeviceMountPath, devicePath)
}
if !reflect.DeepEqual(vol.MountFlags, csiMounter.spec.PersistentVolume.Spec.MountOptions) {
t.Errorf("unexpected mount flags passed to driver: %+v", vol.MountFlags)
}
}
if vol.FSType != tc.fsType {
t.Error("unexpected FSType sent to driver:", vol.FSType)
}
if vol.Path != csiMounter.GetPath() {
t.Error("csi server may not have received NodePublishVolume call")
}
})
}
}
func TestMounterSetUpWithFSGroup(t *testing.T) { func TestMounterSetUpWithFSGroup(t *testing.T) {
fakeClient := fakeclient.NewSimpleClientset() fakeClient := fakeclient.NewSimpleClientset()
plug, tmpDir := newTestPlugin(t, fakeClient) plug, tmpDir := newTestPlugin(t, fakeClient)

View File

@ -74,6 +74,12 @@ type csiPlugin struct {
csiDriverInformer csiinformer.CSIDriverInformer csiDriverInformer csiinformer.CSIDriverInformer
} }
//TODO (vladimirvivien) add this type to storage api
type driverMode string
const persistentDriverMode driverMode = "persistent"
const ephemeralDriverMode driverMode = "ephemeral"
// ProbeVolumePlugins returns implemented plugins // ProbeVolumePlugins returns implemented plugins
func ProbeVolumePlugins() []volume.VolumePlugin { func ProbeVolumePlugins() []volume.VolumePlugin {
p := &csiPlugin{ p := &csiPlugin{
@ -303,7 +309,7 @@ func (p *csiPlugin) GetPluginName() string {
// GetvolumeName returns a concatenated string of CSIVolumeSource.Driver<volNameSe>CSIVolumeSource.VolumeHandle // GetvolumeName returns a concatenated string of CSIVolumeSource.Driver<volNameSe>CSIVolumeSource.VolumeHandle
// That string value is used in Detach() to extract driver name and volumeName. // That string value is used in Detach() to extract driver name and volumeName.
func (p *csiPlugin) GetVolumeName(spec *volume.Spec) (string, error) { func (p *csiPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
csi, err := getCSISourceFromSpec(spec) csi, err := getPVSourceFromSpec(spec)
if err != nil { if err != nil {
klog.Error(log("plugin.GetVolumeName failed to extract volume source from spec: %v", err)) klog.Error(log("plugin.GetVolumeName failed to extract volume source from spec: %v", err))
return "", err return "", err
@ -316,6 +322,14 @@ func (p *csiPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
func (p *csiPlugin) CanSupport(spec *volume.Spec) bool { func (p *csiPlugin) CanSupport(spec *volume.Spec) bool {
// TODO (vladimirvivien) CanSupport should also take into account // TODO (vladimirvivien) CanSupport should also take into account
// the availability/registration of specified Driver in the volume source // the availability/registration of specified Driver in the volume source
if spec == nil {
return false
}
if utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CSI != nil) ||
(spec.Volume != nil && spec.Volume.CSI != nil)
}
return spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CSI != nil return spec.PersistentVolume != nil && spec.PersistentVolume.Spec.CSI != nil
} }
@ -331,11 +345,34 @@ func (p *csiPlugin) NewMounter(
spec *volume.Spec, spec *volume.Spec,
pod *api.Pod, pod *api.Pod,
_ volume.VolumeOptions) (volume.Mounter, error) { _ volume.VolumeOptions) (volume.Mounter, error) {
pvSource, err := getCSISourceFromSpec(spec)
volSrc, pvSrc, err := getSourceFromSpec(spec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
readOnly, err := getReadOnlyFromSpec(spec)
var (
driverName string
volumeHandle string
readOnly bool
)
switch {
case volSrc != nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume):
volumeHandle = makeVolumeHandle(string(pod.UID), spec.Name())
driverName = volSrc.Driver
if volSrc.ReadOnly != nil {
readOnly = *volSrc.ReadOnly
}
case pvSrc != nil:
driverName = pvSrc.Driver
volumeHandle = pvSrc.VolumeHandle
readOnly = spec.ReadOnly
default:
return nil, fmt.Errorf("volume source not found in volume.Spec")
}
driverMode, err := p.getDriverMode(spec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -346,7 +383,7 @@ func (p *csiPlugin) NewMounter(
return nil, errors.New("failed to get a Kubernetes client") return nil, errors.New("failed to get a Kubernetes client")
} }
csi, err := newCsiDriverClient(csiDriverName(pvSource.Driver)) csi, err := newCsiDriverClient(csiDriverName(driverName))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -357,8 +394,9 @@ func (p *csiPlugin) NewMounter(
spec: spec, spec: spec,
pod: pod, pod: pod,
podUID: pod.UID, podUID: pod.UID,
driverName: csiDriverName(pvSource.Driver), driverName: csiDriverName(driverName),
volumeID: pvSource.VolumeHandle, driverMode: driverMode,
volumeID: volumeHandle,
specVolumeID: spec.Name(), specVolumeID: spec.Name(),
csiClient: csi, csiClient: csi,
readOnly: readOnly, readOnly: readOnly,
@ -376,15 +414,17 @@ func (p *csiPlugin) NewMounter(
// persist volume info data for teardown // persist volume info data for teardown
node := string(p.host.GetNodeName()) node := string(p.host.GetNodeName())
attachID := getAttachmentName(pvSource.VolumeHandle, pvSource.Driver, node)
volData := map[string]string{ volData := map[string]string{
volDataKey.specVolID: spec.Name(), volDataKey.specVolID: spec.Name(),
volDataKey.volHandle: pvSource.VolumeHandle, volDataKey.volHandle: volumeHandle,
volDataKey.driverName: pvSource.Driver, volDataKey.driverName: driverName,
volDataKey.nodeName: node, volDataKey.nodeName: node,
volDataKey.attachmentID: attachID, volDataKey.driverMode: string(driverMode),
} }
attachID := getAttachmentName(volumeHandle, driverName, node)
volData[volDataKey.attachmentID] = attachID
if err := saveVolumeData(dataDir, volDataFileName, volData); err != nil { if err := saveVolumeData(dataDir, volDataFileName, volData); err != nil {
klog.Error(log("failed to save volume info data: %v", err)) klog.Error(log("failed to save volume info data: %v", err))
if err := os.RemoveAll(dataDir); err != nil { if err := os.RemoveAll(dataDir); err != nil {
@ -437,23 +477,58 @@ func (p *csiPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.S
klog.V(4).Info(log("plugin.ConstructVolumeSpec extracted [%#v]", volData)) klog.V(4).Info(log("plugin.ConstructVolumeSpec extracted [%#v]", volData))
var spec *volume.Spec
inlineEnabled := utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume)
if inlineEnabled {
mode := driverMode(volData[volDataKey.driverMode])
switch {
case mode == ephemeralDriverMode:
spec = p.constructVolSourceSpec(volData[volDataKey.specVolID], volData[volDataKey.driverName])
case mode == persistentDriverMode:
fallthrough
default:
spec = p.constructPVSourceSpec(volData[volDataKey.specVolID], volData[volDataKey.driverName], volData[volDataKey.volHandle])
}
} else {
spec = p.constructPVSourceSpec(volData[volDataKey.specVolID], volData[volDataKey.driverName], volData[volDataKey.volHandle])
}
return spec, nil
}
// constructVolSourceSpec constructs volume.Spec with CSIVolumeSource
func (p *csiPlugin) constructVolSourceSpec(volSpecName, driverName string) *volume.Spec {
vol := &api.Volume{
Name: volSpecName,
VolumeSource: api.VolumeSource{
CSI: &api.CSIVolumeSource{
Driver: driverName,
},
},
}
return volume.NewSpecFromVolume(vol)
}
//constructPVSourceSpec constructs volume.Spec with CSIPersistentVolumeSource
func (p *csiPlugin) constructPVSourceSpec(volSpecName, driverName, volumeHandle string) *volume.Spec {
fsMode := api.PersistentVolumeFilesystem fsMode := api.PersistentVolumeFilesystem
pv := &api.PersistentVolume{ pv := &api.PersistentVolume{
ObjectMeta: meta.ObjectMeta{ ObjectMeta: meta.ObjectMeta{
Name: volData[volDataKey.specVolID], Name: volSpecName,
}, },
Spec: api.PersistentVolumeSpec{ Spec: api.PersistentVolumeSpec{
PersistentVolumeSource: api.PersistentVolumeSource{ PersistentVolumeSource: api.PersistentVolumeSource{
CSI: &api.CSIPersistentVolumeSource{ CSI: &api.CSIPersistentVolumeSource{
Driver: volData[volDataKey.driverName], Driver: driverName,
VolumeHandle: volData[volDataKey.volHandle], VolumeHandle: volumeHandle,
}, },
}, },
VolumeMode: &fsMode, VolumeMode: &fsMode,
}, },
} }
return volume.NewSpecFromPersistentVolume(pv, false)
return volume.NewSpecFromPersistentVolume(pv, false), nil
} }
func (p *csiPlugin) SupportsMountOption() bool { func (p *csiPlugin) SupportsMountOption() bool {
@ -506,24 +581,31 @@ func (p *csiPlugin) NewDetacher() (volume.Detacher, error) {
}, nil }, nil
} }
// TODO change CanAttach to return error to propagate ability
// to support Attachment or an error - see https://github.com/kubernetes/kubernetes/issues/74810
func (p *csiPlugin) CanAttach(spec *volume.Spec) bool { func (p *csiPlugin) CanAttach(spec *volume.Spec) bool {
if spec.PersistentVolume == nil { driverMode, err := p.getDriverMode(spec)
klog.Error(log("plugin.CanAttach test failed, spec missing PersistentVolume")) if err != nil {
return false return false
} }
var driverName string if driverMode == ephemeralDriverMode {
if spec.PersistentVolume.Spec.CSI != nil { klog.V(4).Info(log("driver ephemeral mode detected for spec %v", spec.Name))
driverName = spec.PersistentVolume.Spec.CSI.Driver
} else {
klog.Error(log("plugin.CanAttach test failed, spec missing CSIPersistentVolume"))
return false return false
} }
pvSrc, err := getCSISourceFromSpec(spec)
if err != nil {
klog.Error(log("plugin.CanAttach failed to get info from spec: %s", err))
return false
}
driverName := pvSrc.Driver
skipAttach, err := p.skipAttach(driverName) skipAttach, err := p.skipAttach(driverName)
if err != nil { if err != nil {
klog.Error(log("plugin.CanAttach error when calling plugin.skipAttach for driver %s: %s", driverName, err)) klog.Error(log("plugin.CanAttach error when calling plugin.skipAttach for driver %s: %s", driverName, err))
return false
} }
return !skipAttach return !skipAttach
@ -675,6 +757,8 @@ func (p *csiPlugin) ConstructBlockVolumeSpec(podUID types.UID, specVolName, mapP
return volume.NewSpecFromPersistentVolume(pv, false), nil return volume.NewSpecFromPersistentVolume(pv, false), nil
} }
// skipAttach looks up CSIDriver object associated with driver name
// to determine if driver requies attachment volume operation
func (p *csiPlugin) skipAttach(driver string) (bool, error) { func (p *csiPlugin) skipAttach(driver string) (bool, error) {
if !utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) { if !utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) {
return false, nil return false, nil
@ -696,6 +780,26 @@ func (p *csiPlugin) skipAttach(driver string) (bool, error) {
return false, nil return false, nil
} }
// getDriverMode returns the driver mode for the specified spec: {persistent|ephemeral}.
// 1) If mode cannot be determined, it will default to "persistent".
// 2) If Mode cannot be resolved to either {persistent | ephemeral}, an error is returned
// See https://github.com/kubernetes/enhancements/blob/master/keps/sig-storage/20190122-csi-inline-volumes.md
func (p *csiPlugin) getDriverMode(spec *volume.Spec) (driverMode, error) {
// TODO (vladimirvivien) ultimately, mode will be retrieved from CSIDriver.Spec.Mode.
// However, in alpha version, mode is determined by the volume source:
// 1) if volume.Spec.Volume.CSI != nil -> mode is ephemeral
// 2) if volume.Spec.PersistentVolume.Spec.CSI != nil -> persistent
volSrc, _, err := getSourceFromSpec(spec)
if err != nil {
return "", err
}
if volSrc != nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
return ephemeralDriverMode, nil
}
return persistentDriverMode, nil
}
func (p *csiPlugin) getPublishContext(client clientset.Interface, handle, driver, nodeName string) (map[string]string, error) { func (p *csiPlugin) getPublishContext(client clientset.Interface, handle, driver, nodeName string) (map[string]string, error) {
skip, err := p.skipAttach(driver) skip, err := p.skipAttach(driver)
if err != nil { if err != nil {

View File

@ -18,6 +18,7 @@ package csi
import ( import (
"fmt" "fmt"
"math/rand"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -99,6 +100,19 @@ func makeTestPV(name string, sizeGig int, driverName, volID string) *api.Persist
} }
} }
func makeTestVol(name string, driverName string) *api.Volume {
ro := false
return &api.Volume{
Name: name,
VolumeSource: api.VolumeSource{
CSI: &api.CSIVolumeSource{
Driver: driverName,
ReadOnly: &ro,
},
},
}
}
func registerFakePlugin(pluginName, endpoint string, versions []string, t *testing.T) { func registerFakePlugin(pluginName, endpoint string, versions []string, t *testing.T) {
highestSupportedVersions, err := highestSupportedVersion(versions) highestSupportedVersions, err := highestSupportedVersion(versions)
if err != nil { if err != nil {
@ -131,22 +145,105 @@ func TestPluginGetVolumeName(t *testing.T) {
name string name string
driverName string driverName string
volName string volName string
spec *volume.Spec
shouldFail bool shouldFail bool
}{ }{
{"alphanum names", "testdr", "testvol", false}, {
{"mixchar driver", "test.dr.cc", "testvol", false}, name: "alphanum names",
{"mixchar volume", "testdr", "test-vol-name", false}, driverName: "testdr",
{"mixchars all", "test-driver", "test.vol.name", false}, volName: "testvol",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "testdr", "testvol"), false),
},
{
name: "mixchar driver",
driverName: "test.dr.cc",
volName: "testvol",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "test.dr.cc", "testvol"), false),
},
{
name: "mixchar volume",
driverName: "testdr",
volName: "test-vol-name",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "testdr", "test-vol-name"), false),
},
{
name: "mixchars all",
driverName: "test-driver",
volName: "test.vol.name",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "test-driver", "test.vol.name"), false),
},
{
name: "volume source with mixchars all",
driverName: "test-driver",
volName: "test.vol.name",
spec: volume.NewSpecFromVolume(makeTestVol("test-pv", "test-driver")),
shouldFail: true, // csi inline feature off
},
{
name: "missing spec",
shouldFail: true,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Logf("testing: %s", tc.name) t.Logf("testing: %s", tc.name)
registerFakePlugin(tc.driverName, "endpoint", []string{"0.3.0"}, t) registerFakePlugin(tc.driverName, "endpoint", []string{"0.3.0"}, t)
pv := makeTestPV("test-pv", 10, tc.driverName, tc.volName) name, err := plug.GetVolumeName(tc.spec)
spec := volume.NewSpecFromPersistentVolume(pv, false) if tc.shouldFail != (err != nil) {
name, err := plug.GetVolumeName(spec) t.Fatal("shouldFail does match expected error")
if tc.shouldFail && err == nil { }
t.Fatal("GetVolumeName should fail, but got err=nil") if tc.shouldFail && err != nil {
t.Log(err)
continue
}
if name != fmt.Sprintf("%s%s%s", tc.driverName, volNameSep, tc.volName) {
t.Errorf("unexpected volume name %s", name)
}
}
}
func TestPluginGetVolumeNameWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir)
testCases := []struct {
name string
driverName string
volName string
shouldFail bool
spec *volume.Spec
}{
{
name: "missing spec",
shouldFail: true,
},
{
name: "alphanum names for pv",
driverName: "testdr",
volName: "testvol",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "testdr", "testvol"), false),
},
{
name: "alphanum names for vol source",
driverName: "testdr",
volName: "testvol",
spec: volume.NewSpecFromVolume(makeTestVol("test-pv", "testdr")),
shouldFail: true,
},
}
for _, tc := range testCases {
t.Logf("testing: %s", tc.name)
registerFakePlugin(tc.driverName, "endpoint", []string{"0.3.0"}, t)
name, err := plug.GetVolumeName(tc.spec)
if tc.shouldFail != (err != nil) {
t.Fatal("shouldFail does match expected error")
}
if tc.shouldFail && err != nil {
t.Log(err)
continue
} }
if name != fmt.Sprintf("%s%s%s", tc.driverName, volNameSep, tc.volName) { if name != fmt.Sprintf("%s%s%s", tc.driverName, volNameSep, tc.volName) {
t.Errorf("unexpected volume name %s", name) t.Errorf("unexpected volume name %s", name)
@ -157,15 +254,79 @@ func TestPluginGetVolumeName(t *testing.T) {
func TestPluginCanSupport(t *testing.T) { func TestPluginCanSupport(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)() defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
tests := []struct {
name string
spec *volume.Spec
canSupport bool
}{
{
name: "no spec provided",
canSupport: false,
},
{
name: "can support volume source",
spec: volume.NewSpecFromVolume(makeTestVol("test-vol", testDriver)),
canSupport: false, // csi inline not enabled
},
{
name: "can support persistent volume source",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 20, testDriver, testVol), true),
canSupport: true,
},
}
plug, tmpDir := newTestPlugin(t, nil) plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t) registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t)
pv := makeTestPV("test-pv", 10, testDriver, testVol)
spec := volume.NewSpecFromPersistentVolume(pv, false)
if !plug.CanSupport(spec) { for _, tc := range tests {
t.Errorf("should support CSI spec") t.Run(tc.name, func(t *testing.T) {
actual := plug.CanSupport(tc.spec)
if tc.canSupport != actual {
t.Errorf("expecting canSupport %t, got %t", tc.canSupport, actual)
}
})
}
}
func TestPluginCanSupportWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
tests := []struct {
name string
spec *volume.Spec
canSupport bool
}{
{
name: "no spec provided",
canSupport: false,
},
{
name: "can support volume source",
spec: volume.NewSpecFromVolume(makeTestVol("test-vol", testDriver)),
canSupport: true,
},
{
name: "can support persistent volume source",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 20, testDriver, testVol), true),
canSupport: true,
},
}
plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir)
registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t)
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
actual := plug.CanSupport(tc.spec)
if tc.canSupport != actual {
t.Errorf("expecting canSupport %t, got %t", tc.canSupport, actual)
}
})
} }
} }
@ -177,44 +338,77 @@ func TestPluginConstructVolumeSpec(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
originSpec *volume.Spec
specVolID string specVolID string
data map[string]string volHandle string
podUID types.UID
shouldFail bool shouldFail bool
}{ }{
{ {
name: "valid spec name", name: "construct spec1 from original persistent spec",
specVolID: "test.vol.id", specVolID: "test.vol.id",
data: map[string]string{volDataKey.specVolID: "test.vol.id", volDataKey.volHandle: "test-vol0", volDataKey.driverName: "test-driver0"}, volHandle: "testvol-handle1",
originSpec: volume.NewSpecFromPersistentVolume(makeTestPV("test.vol.id", 20, testDriver, "testvol-handle1"), true),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
},
{
name: "construct spec2 from original persistent spec",
specVolID: "spec2",
volHandle: "handle2",
originSpec: volume.NewSpecFromPersistentVolume(makeTestPV("spec2", 20, testDriver, "handle2"), true),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
},
{
name: "construct spec from original volume spec",
specVolID: "volspec",
originSpec: volume.NewSpecFromVolume(makeTestVol("spec2", testDriver)),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
shouldFail: true, // csi inline off
}, },
} }
for _, tc := range testCases { registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t)
t.Logf("test case: %s", tc.name)
dir := getTargetPath(testPodUID, tc.specVolID, plug.host)
// create the data file for _, tc := range testCases {
if tc.data != nil { t.Run(tc.name, func(t *testing.T) {
mountDir := path.Join(getTargetPath(testPodUID, tc.specVolID, plug.host), "/mount") mounter, err := plug.NewMounter(
if err := os.MkdirAll(mountDir, 0755); err != nil && !os.IsNotExist(err) { tc.originSpec,
t.Errorf("failed to create dir [%s]: %v", mountDir, err) &api.Pod{ObjectMeta: meta.ObjectMeta{UID: tc.podUID, Namespace: testns}},
volume.VolumeOptions{},
)
if tc.shouldFail && err != nil {
t.Log(err)
return
} }
if err := saveVolumeData(path.Dir(mountDir), volDataFileName, tc.data); err != nil { if !tc.shouldFail && err != nil {
t.Fatal(err) t.Fatal(err)
} }
if mounter == nil {
t.Fatal("failed to create CSI mounter")
} }
csiMounter := mounter.(*csiMountMgr)
// rebuild spec // rebuild spec
spec, err := plug.ConstructVolumeSpec("test-pv", dir) spec, err := plug.ConstructVolumeSpec("test-pv", path.Dir(csiMounter.GetPath()))
if tc.shouldFail { if err != nil {
if err == nil { t.Fatal(err)
t.Fatal("expecting ConstructVolumeSpec to fail, but got nil error")
} }
continue if spec == nil {
t.Fatal("nil volume.Spec contstructed")
}
// inspect spec
if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.CSI == nil {
t.Fatal("CSIPersistentVolume not found in constructed spec ")
} }
volHandle := spec.PersistentVolume.Spec.CSI.VolumeHandle volHandle := spec.PersistentVolume.Spec.CSI.VolumeHandle
if volHandle != tc.data[volDataKey.volHandle] { if volHandle != tc.originSpec.PersistentVolume.Spec.CSI.VolumeHandle {
t.Errorf("expected volID %s, got volID %s", tc.data[volDataKey.volHandle], volHandle) t.Error("unexpected volumeHandle constructed:", volHandle)
}
driverName := spec.PersistentVolume.Spec.CSI.Driver
if driverName != tc.originSpec.PersistentVolume.Spec.CSI.Driver {
t.Error("unexpected driverName constructed:", driverName)
} }
if spec.PersistentVolume.Spec.VolumeMode == nil { if spec.PersistentVolume.Spec.VolumeMode == nil {
@ -226,26 +420,180 @@ func TestPluginConstructVolumeSpec(t *testing.T) {
} }
if spec.Name() != tc.specVolID { if spec.Name() != tc.specVolID {
t.Errorf("Unexpected spec name %s", spec.Name()) t.Errorf("Unexpected spec name constructed %s", spec.Name())
} }
})
}
}
func TestPluginConstructVolumeSpecWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir)
testCases := []struct {
name string
originSpec *volume.Spec
specVolID string
volHandle string
podUID types.UID
shouldFail bool
}{
{
name: "construct spec1 from persistent spec",
specVolID: "test.vol.id",
volHandle: "testvol-handle1",
originSpec: volume.NewSpecFromPersistentVolume(makeTestPV("test.vol.id", 20, testDriver, "testvol-handle1"), true),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
},
{
name: "construct spec2 from persistent spec",
specVolID: "spec2",
volHandle: "handle2",
originSpec: volume.NewSpecFromPersistentVolume(makeTestPV("spec2", 20, testDriver, "handle2"), true),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
},
{
name: "construct spec from volume spec",
specVolID: "volspec",
originSpec: volume.NewSpecFromVolume(makeTestVol("volspec", testDriver)),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
},
{
name: "construct spec from volume spec2",
specVolID: "volspec2",
originSpec: volume.NewSpecFromVolume(makeTestVol("volspec2", testDriver)),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
},
{
name: "missing spec",
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
shouldFail: true,
},
}
registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mounter, err := plug.NewMounter(
tc.originSpec,
&api.Pod{ObjectMeta: meta.ObjectMeta{UID: tc.podUID, Namespace: testns}},
volume.VolumeOptions{},
)
if tc.shouldFail && err != nil {
t.Log(err)
return
}
if !tc.shouldFail && err != nil {
t.Fatal(err)
}
if mounter == nil {
t.Fatal("failed to create CSI mounter")
}
csiMounter := mounter.(*csiMountMgr)
// rebuild spec
spec, err := plug.ConstructVolumeSpec("test-pv", path.Dir(csiMounter.GetPath()))
if err != nil {
t.Fatal(err)
}
if spec == nil {
t.Fatal("nil volume.Spec contstructed")
}
if spec.Name() != tc.specVolID {
t.Errorf("unexpected spec name constructed volume.Spec: %s", spec.Name())
}
switch {
case spec.Volume != nil:
if spec.Volume.CSI == nil {
t.Error("missing CSIVolumeSource in constructed volume.Spec")
}
if spec.Volume.CSI.Driver != tc.originSpec.Volume.CSI.Driver {
t.Error("unexpected driver in constructed volume source:", spec.Volume.CSI.Driver)
}
case spec.PersistentVolume != nil:
if spec.PersistentVolume.Spec.CSI == nil {
t.Fatal("missing CSIPersistentVolumeSource in constructed volume.spec")
}
volHandle := spec.PersistentVolume.Spec.CSI.VolumeHandle
if volHandle != tc.originSpec.PersistentVolume.Spec.CSI.VolumeHandle {
t.Error("unexpected volumeHandle constructed in persistent volume source:", volHandle)
}
driverName := spec.PersistentVolume.Spec.CSI.Driver
if driverName != tc.originSpec.PersistentVolume.Spec.CSI.Driver {
t.Error("unexpected driverName constructed in persistent volume source:", driverName)
}
if spec.PersistentVolume.Spec.VolumeMode == nil {
t.Fatalf("Volume mode has not been set.")
}
if *spec.PersistentVolume.Spec.VolumeMode != api.PersistentVolumeFilesystem {
t.Errorf("Unexpected volume mode %q", *spec.PersistentVolume.Spec.VolumeMode)
}
default:
t.Fatal("invalid volume.Spec constructed")
}
})
} }
} }
func TestPluginNewMounter(t *testing.T) { func TestPluginNewMounter(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)() defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
tests := []struct {
name string
spec *volume.Spec
podUID types.UID
namespace string
driverMode driverMode
shouldFail bool
}{
{
name: "mounter from persistent volume source",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv1", 20, testDriver, testVol), true),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
namespace: "test-ns1",
driverMode: persistentDriverMode,
},
{
name: "mounter from volume source",
spec: volume.NewSpecFromVolume(makeTestVol("test-vol1", testDriver)),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
namespace: "test-ns2",
driverMode: ephemeralDriverMode,
shouldFail: true, // csi inline not enabled
},
{
name: "mounter from no spec provided",
shouldFail: true,
},
}
for _, test := range tests {
plug, tmpDir := newTestPlugin(t, nil) plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
registerFakePlugin(testDriver, "endpoint", []string{"1.2.0"}, t) registerFakePlugin(testDriver, "endpoint", []string{"1.2.0"}, t)
pv := makeTestPV("test-pv", 10, testDriver, testVol)
t.Run(test.name, func(t *testing.T) {
mounter, err := plug.NewMounter( mounter, err := plug.NewMounter(
volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly), test.spec,
&api.Pod{ObjectMeta: meta.ObjectMeta{UID: testPodUID, Namespace: testns}}, &api.Pod{ObjectMeta: meta.ObjectMeta{UID: test.podUID, Namespace: test.namespace}},
volume.VolumeOptions{}, volume.VolumeOptions{},
) )
if err != nil { if test.shouldFail != (err != nil) {
t.Fatalf("Failed to make a new Mounter: %v", err) t.Fatal("Unexpected error:", err)
}
if test.shouldFail && err != nil {
t.Log(err)
return
} }
if mounter == nil { if mounter == nil {
@ -257,18 +605,21 @@ func TestPluginNewMounter(t *testing.T) {
if string(csiMounter.driverName) != testDriver { if string(csiMounter.driverName) != testDriver {
t.Error("mounter driver name not set") t.Error("mounter driver name not set")
} }
if csiMounter.volumeID != testVol { if csiMounter.volumeID == "" {
t.Error("mounter volume id not set") t.Error("mounter volume id not set")
} }
if csiMounter.pod == nil { if csiMounter.pod == nil {
t.Error("mounter pod not set") t.Error("mounter pod not set")
} }
if csiMounter.podUID == types.UID("") { if string(csiMounter.podUID) != string(test.podUID) {
t.Error("mounter podUID not set") t.Error("mounter podUID not set")
} }
if csiMounter.csiClient == nil { if csiMounter.csiClient == nil {
t.Error("mounter csiClient is nil") t.Error("mounter csiClient is nil")
} }
if csiMounter.driverMode != test.driverMode {
t.Error("unexpected driver mode:", csiMounter.driverMode)
}
// ensure data file is created // ensure data file is created
dataDir := path.Dir(mounter.GetPath()) dataDir := path.Dir(mounter.GetPath())
@ -280,6 +631,145 @@ func TestPluginNewMounter(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
data, err := loadVolumeData(dataDir, volDataFileName)
if err != nil {
t.Fatal(err)
}
if data[volDataKey.specVolID] != csiMounter.spec.Name() {
t.Error("volume data file unexpected specVolID:", data[volDataKey.specVolID])
}
if data[volDataKey.volHandle] != csiMounter.volumeID {
t.Error("volume data file unexpected volHandle:", data[volDataKey.volHandle])
}
if data[volDataKey.driverName] != string(csiMounter.driverName) {
t.Error("volume data file unexpected driverName:", data[volDataKey.driverName])
}
if data[volDataKey.nodeName] != string(csiMounter.plugin.host.GetNodeName()) {
t.Error("volume data file unexpected nodeName:", data[volDataKey.nodeName])
}
if data[volDataKey.driverMode] != string(test.driverMode) {
t.Error("volume data file unexpected driverMode:", data[volDataKey.driverMode])
}
})
}
}
func TestPluginNewMounterWithInline(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIBlockVolume, true)()
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
tests := []struct {
name string
spec *volume.Spec
podUID types.UID
namespace string
driverMode driverMode
shouldFail bool
}{
{
name: "mounter with missing spec",
shouldFail: true,
},
{
name: "mounter with spec with both volSrc and pvSrc",
spec: &volume.Spec{
Volume: makeTestVol("test-vol1", testDriver),
PersistentVolume: makeTestPV("test-pv1", 20, testDriver, testVol),
ReadOnly: true,
},
shouldFail: true,
},
{
name: "mounter with persistent volume source",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-pv1", 20, testDriver, testVol), true),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
namespace: "test-ns1",
driverMode: persistentDriverMode,
},
{
name: "mounter with volume source",
spec: volume.NewSpecFromVolume(makeTestVol("test-vol1", testDriver)),
podUID: types.UID(fmt.Sprintf("%08X", rand.Uint64())),
namespace: "test-ns2",
driverMode: ephemeralDriverMode,
},
}
for _, test := range tests {
plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir)
registerFakePlugin(testDriver, "endpoint", []string{"1.2.0"}, t)
t.Run(test.name, func(t *testing.T) {
mounter, err := plug.NewMounter(
test.spec,
&api.Pod{ObjectMeta: meta.ObjectMeta{UID: test.podUID, Namespace: test.namespace}},
volume.VolumeOptions{},
)
if test.shouldFail != (err != nil) {
t.Fatal("Unexpected error:", err)
}
if test.shouldFail && err != nil {
t.Log(err)
return
}
if mounter == nil {
t.Fatal("failed to create CSI mounter")
}
csiMounter := mounter.(*csiMountMgr)
// validate mounter fields
if string(csiMounter.driverName) != testDriver {
t.Error("mounter driver name not set")
}
if csiMounter.volumeID == "" {
t.Error("mounter volume id not set")
}
if csiMounter.pod == nil {
t.Error("mounter pod not set")
}
if string(csiMounter.podUID) != string(test.podUID) {
t.Error("mounter podUID not set")
}
if csiMounter.csiClient == nil {
t.Error("mounter csiClient is nil")
}
if csiMounter.driverMode != test.driverMode {
t.Error("unexpected driver mode:", csiMounter.driverMode)
}
// ensure data file is created
dataDir := path.Dir(mounter.GetPath())
dataFile := filepath.Join(dataDir, volDataFileName)
if _, err := os.Stat(dataFile); err != nil {
if os.IsNotExist(err) {
t.Errorf("data file not created %s", dataFile)
} else {
t.Fatal(err)
}
}
data, err := loadVolumeData(dataDir, volDataFileName)
if err != nil {
t.Fatal(err)
}
if data[volDataKey.specVolID] != csiMounter.spec.Name() {
t.Error("volume data file unexpected specVolID:", data[volDataKey.specVolID])
}
if data[volDataKey.volHandle] != csiMounter.volumeID {
t.Error("volume data file unexpected volHandle:", data[volDataKey.volHandle])
}
if data[volDataKey.driverName] != string(csiMounter.driverName) {
t.Error("volume data file unexpected driverName:", data[volDataKey.driverName])
}
if data[volDataKey.nodeName] != string(csiMounter.plugin.host.GetNodeName()) {
t.Error("volume data file unexpected nodeName:", data[volDataKey.nodeName])
}
if data[volDataKey.driverMode] != string(csiMounter.driverMode) {
t.Error("volume data file unexpected driverMode:", data[volDataKey.driverMode])
}
})
}
} }
func TestPluginNewUnmounter(t *testing.T) { func TestPluginNewUnmounter(t *testing.T) {
@ -371,25 +861,36 @@ func TestPluginNewDetacher(t *testing.T) {
} }
func TestPluginCanAttach(t *testing.T) { func TestPluginCanAttach(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIDriverRegistry, true)()
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
tests := []struct { tests := []struct {
name string name string
driverName string driverName string
spec *volume.Spec
canAttach bool canAttach bool
}{ }{
{ {
name: "attachable", name: "non-attachable inline",
driverName: "attachble-driver", driverName: "attachable-inline",
spec: volume.NewSpecFromVolume(makeTestVol("test-vol", "attachable-inline")),
canAttach: false,
},
{
name: "attachable PV",
driverName: "attachable-pv",
spec: volume.NewSpecFromPersistentVolume(makeTestPV("test-vol", 20, "attachable-pv", testVol), true),
canAttach: true, canAttach: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
csiDriver := getCSIDriver(test.driverName, nil, &test.canAttach)
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
plug, tmpDir := newTestPlugin(t, nil) fakeCSIClient := fakeclient.NewSimpleClientset(csiDriver)
plug, tmpDir := newTestPlugin(t, fakeCSIClient)
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, test.driverName, "test-vol"), false)
pluginCanAttach := plug.CanAttach(spec) pluginCanAttach := plug.CanAttach(test.spec)
if pluginCanAttach != test.canAttach { if pluginCanAttach != test.canAttach {
t.Fatalf("expecting plugin.CanAttach %t got %t", test.canAttach, pluginCanAttach) t.Fatalf("expecting plugin.CanAttach %t got %t", test.canAttach, pluginCanAttach)
return return

View File

@ -25,8 +25,10 @@ import (
api "k8s.io/api/core/v1" api "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/klog" "k8s.io/klog"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
utilstrings "k8s.io/utils/strings" utilstrings "k8s.io/utils/strings"
) )
@ -90,12 +92,7 @@ func loadVolumeData(dir string, fileName string) (map[string]string, error) {
} }
func getCSISourceFromSpec(spec *volume.Spec) (*api.CSIPersistentVolumeSource, error) { func getCSISourceFromSpec(spec *volume.Spec) (*api.CSIPersistentVolumeSource, error) {
if spec.PersistentVolume != nil && return getPVSourceFromSpec(spec)
spec.PersistentVolume.Spec.CSI != nil {
return spec.PersistentVolume.Spec.CSI, nil
}
return nil, fmt.Errorf("CSIPersistentVolumeSource not defined in spec")
} }
func getReadOnlyFromSpec(spec *volume.Spec) (bool, error) { func getReadOnlyFromSpec(spec *volume.Spec) (bool, error) {
@ -140,3 +137,34 @@ func hasReadWriteOnce(modes []api.PersistentVolumeAccessMode) bool {
} }
return false return false
} }
// getSourceFromSpec returns either CSIVolumeSource or CSIPersistentVolumeSource, but not both
func getSourceFromSpec(spec *volume.Spec) (*api.CSIVolumeSource, *api.CSIPersistentVolumeSource, error) {
if spec == nil {
return nil, nil, fmt.Errorf("volume.Spec nil")
}
if spec.Volume != nil && spec.PersistentVolume != nil {
return nil, nil, fmt.Errorf("volume.Spec has both volume and persistent volume sources")
}
if spec.Volume != nil && spec.Volume.CSI != nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
return spec.Volume.CSI, nil, nil
}
if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.CSI != nil {
return nil, spec.PersistentVolume.Spec.CSI, nil
}
return nil, nil, fmt.Errorf("volume source not found in volume.Spec")
}
// getPVSourceFromSpec ensures only CSIPersistentVolumeSource is present in volume.Spec
func getPVSourceFromSpec(spec *volume.Spec) (*api.CSIPersistentVolumeSource, error) {
volSrc, pvSrc, err := getSourceFromSpec(spec)
if err != nil {
return nil, err
}
if volSrc != nil {
return nil, fmt.Errorf("unexpected api.CSIVolumeSource found in volume.Spec")
}
return pvSrc, nil
}

View File

@ -57,8 +57,11 @@ func (f *IdentityClient) Probe(ctx context.Context, in *csipb.ProbeRequest, opts
} }
type CSIVolume struct { type CSIVolume struct {
VolumeHandle string
VolumeContext map[string]string VolumeContext map[string]string
Path string Path string
DeviceMountPath string
FSType string
MountFlags []string MountFlags []string
} }
@ -118,7 +121,6 @@ func (f *NodeClient) AddNodeStagedVolume(volID, deviceMountPath string, volumeCo
// NodePublishVolume implements CSI NodePublishVolume // NodePublishVolume implements CSI NodePublishVolume
func (f *NodeClient) NodePublishVolume(ctx context.Context, req *csipb.NodePublishVolumeRequest, opts ...grpc.CallOption) (*csipb.NodePublishVolumeResponse, error) { func (f *NodeClient) NodePublishVolume(ctx context.Context, req *csipb.NodePublishVolumeRequest, opts ...grpc.CallOption) (*csipb.NodePublishVolumeResponse, error) {
if f.nextErr != nil { if f.nextErr != nil {
return nil, f.nextErr return nil, f.nextErr
} }
@ -135,8 +137,11 @@ func (f *NodeClient) NodePublishVolume(ctx context.Context, req *csipb.NodePubli
return nil, errors.New("invalid fstype") return nil, errors.New("invalid fstype")
} }
f.nodePublishedVolumes[req.GetVolumeId()] = CSIVolume{ f.nodePublishedVolumes[req.GetVolumeId()] = CSIVolume{
VolumeHandle: req.GetVolumeId(),
Path: req.GetTargetPath(), Path: req.GetTargetPath(),
DeviceMountPath: req.GetStagingTargetPath(),
VolumeContext: req.GetVolumeContext(), VolumeContext: req.GetVolumeContext(),
FSType: req.GetVolumeCapability().GetMount().GetFsType(),
MountFlags: req.GetVolumeCapability().GetMount().MountFlags, MountFlags: req.GetVolumeCapability().GetMount().MountFlags,
} }
return &csipb.NodePublishVolumeResponse{}, nil return &csipb.NodePublishVolumeResponse{}, nil