change staging path for csi driver to pv agnostic

This commit is contained in:
Saikat Roychowdhury 2021-12-14 08:14:45 +00:00
parent 2ac6a4121f
commit 80e0501e89
2 changed files with 118 additions and 48 deletions

View File

@ -650,15 +650,29 @@ func isAttachmentName(unknownString string) bool {
func makeDeviceMountPath(plugin *csiPlugin, spec *volume.Spec) (string, error) { func makeDeviceMountPath(plugin *csiPlugin, spec *volume.Spec) (string, error) {
if spec == nil { if spec == nil {
return "", errors.New(log("makeDeviceMountPath failed, spec is nil")) return "", errors.New("makeDeviceMountPath failed, spec is nil")
} }
pvName := spec.PersistentVolume.Name driver, err := GetCSIDriverName(spec)
if pvName == "" { if err != nil {
return "", errors.New(log("makeDeviceMountPath failed, pv name empty")) return "", err
}
if driver == "" {
return "", errors.New("makeDeviceMountPath failed, csi source driver name is empty")
} }
return filepath.Join(plugin.host.GetPluginDir(plugin.GetPluginName()), persistentVolumeInGlobalPath, pvName, globalMountInGlobalPath), nil csiSource, err := getPVSourceFromSpec(spec)
if err != nil {
return "", errors.New(log("makeDeviceMountPath failed to get CSIPersistentVolumeSource: %v", err))
}
if csiSource.VolumeHandle == "" {
return "", errors.New("makeDeviceMountPath failed, CSIPersistentVolumeSource volume handle is empty")
}
result := sha256.Sum256([]byte(fmt.Sprintf("%s", csiSource.VolumeHandle)))
volSha := fmt.Sprintf("%x", result)
return filepath.Join(plugin.host.GetPluginDir(plugin.GetPluginName()), driver, volSha, globalMountInGlobalPath), nil
} }
func getDriverAndVolNameFromDeviceMountPath(k8s kubernetes.Interface, deviceMountPath string) (string, string, error) { func getDriverAndVolNameFromDeviceMountPath(k8s kubernetes.Interface, deviceMountPath string) (string, string, error) {

View File

@ -18,6 +18,7 @@ package csi
import ( import (
"context" "context"
"crypto/sha256"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -1023,24 +1024,41 @@ func TestAttacherGetDeviceMountPath(t *testing.T) {
csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, testWatchTimeout) csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, testWatchTimeout)
pluginDir := csiAttacher.plugin.host.GetPluginDir(plug.GetPluginName()) pluginDir := csiAttacher.plugin.host.GetPluginDir(plug.GetPluginName())
testCases := []struct { testCases := []struct {
testName string testName string
pvName string pvName string
expectedMountPath string volumeId string
skipPVCSISource bool // The test clears PV.Spec.CSI
shouldFail bool shouldFail bool
addVolSource bool // The test adds a Volume.VolumeSource.CSI.
removeVolumeHandle bool // The test force removes CSI volume handle.
}{ }{
{ {
testName: "normal test", testName: "success test",
pvName: "test-pv1", pvName: "test-pv1",
expectedMountPath: pluginDir + "/pv/test-pv1/globalmount", volumeId: "test-vol1",
}, },
{ {
testName: "no pv name", testName: "fail test, failed to create device mount path due to missing CSI source",
pvName: "", pvName: "test-pv1",
expectedMountPath: pluginDir + "/pv/test-pv1/globalmount", volumeId: "test-vol1",
skipPVCSISource: true,
shouldFail: true, shouldFail: true,
}, },
{
testName: "fail test, failed to create device mount path, CSIVolumeSource found",
pvName: "test-pv1",
volumeId: "test-vol1",
addVolSource: true,
shouldFail: true,
},
{
testName: "fail test, failed to create device mount path, missing CSI volume handle",
pvName: "test-pv1",
volumeId: "test-vol1",
shouldFail: true,
removeVolumeHandle: true,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -1048,9 +1066,18 @@ func TestAttacherGetDeviceMountPath(t *testing.T) {
var spec *volume.Spec var spec *volume.Spec
// Create spec // Create spec
pv := makeTestPV(tc.pvName, 10, testDriver, "testvol") pv := makeTestPV(tc.pvName, 10, testDriver, tc.volumeId)
if tc.removeVolumeHandle {
pv.Spec.PersistentVolumeSource.CSI.VolumeHandle = ""
}
if tc.addVolSource {
spec = volume.NewSpecFromVolume(makeTestVol(tc.pvName, testDriver))
} else {
spec = volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly) spec = volume.NewSpecFromPersistentVolume(pv, pv.Spec.PersistentVolumeSource.CSI.ReadOnly)
if tc.skipPVCSISource {
spec.PersistentVolume.Spec.CSI = nil
}
}
// Run // Run
mountPath, err := csiAttacher.GetDeviceMountPath(spec) mountPath, err := csiAttacher.GetDeviceMountPath(spec)
@ -1058,10 +1085,11 @@ func TestAttacherGetDeviceMountPath(t *testing.T) {
if err != nil && !tc.shouldFail { if err != nil && !tc.shouldFail {
t.Errorf("test should not fail, but error occurred: %v", err) t.Errorf("test should not fail, but error occurred: %v", err)
} else if err == nil { } else if err == nil {
expectedMountPath := filepath.Join(pluginDir, testDriver, generateSha(tc.volumeId), globalMountInGlobalPath)
if tc.shouldFail { if tc.shouldFail {
t.Errorf("test should fail, but no error occurred") t.Errorf("test should fail, but no error occurred")
} else if mountPath != tc.expectedMountPath { } else if mountPath != expectedMountPath {
t.Errorf("mountPath does not equal expectedMountPath. Got: %s. Expected: %s", mountPath, tc.expectedMountPath) t.Errorf("mountPath does not equal expectedMountPath. Got: %s. Expected: %s", mountPath, expectedMountPath)
} }
} }
} }
@ -1573,53 +1601,76 @@ func TestAttacherUnmountDevice(t *testing.T) {
shouldFail bool shouldFail bool
watchTimeout time.Duration watchTimeout time.Duration
}{ }{
// PV agnostic path positive test cases
{ {
testName: "normal, json file exists", testName: "success, json file exists",
volID: "project/zone/test-vol1", volID: "project/zone/test-vol1",
deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount", deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
jsonFile: `{"driverName": "csi", "volumeHandle":"project/zone/test-vol1"}`, jsonFile: `{"driverName": "csi", "volumeHandle":"project/zone/test-vol1"}`,
createPV: false,
stageUnstageSet: true, stageUnstageSet: true,
}, },
{ {
testName: "normal, json file doesn't exist -> use PV", testName: "stage_unstage not set, PV agnostic path, unmount device is skipped",
deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
jsonFile: `{"driverName":"test-driver","volumeHandle":"test-vol1"}`,
stageUnstageSet: false,
},
// PV agnostic path negative test cases
{
testName: "fail: missing json, fail to retrieve driver and volumeID from globalpath",
volID: "project/zone/test-vol1",
deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
jsonFile: "",
stageUnstageSet: true,
shouldFail: true,
},
{
testName: "fail: invalid json, fail to retrieve driver and volumeID from globalpath",
volID: "project/zone/test-vol1",
deviceMountPath: "plugins/csi/" + generateSha("project/zone/test-vol1") + "/globalmount",
jsonFile: `{"driverName"}}`,
stageUnstageSet: true,
shouldFail: true,
},
// Old style PV based path positive test cases
{
testName: "success: json file doesn't exist, old style pv based global path -> use PV",
volID: "project/zone/test-vol1", volID: "project/zone/test-vol1",
deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount", deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount",
jsonFile: "", jsonFile: "",
createPV: true,
stageUnstageSet: true, stageUnstageSet: true,
createPV: true,
}, },
{ {
testName: "invalid json -> use PV", testName: "success: invalid json file, old style pv based global path -> use PV",
volID: "project/zone/test-vol1", volID: "project/zone/test-vol1",
deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount", deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount",
jsonFile: `{"driverName"}}`, jsonFile: `{"driverName"}}`,
createPV: true,
stageUnstageSet: true, stageUnstageSet: true,
},
{
testName: "no json, no PV.volID",
volID: "",
deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount",
jsonFile: "",
createPV: true, createPV: true,
shouldFail: true,
}, },
{ {
testName: "no json, no PV", testName: "stage_unstage not set, PV based path, unmount device is skipped",
volID: "project/zone/test-vol1",
deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount",
jsonFile: "",
createPV: false,
stageUnstageSet: true,
shouldFail: true,
},
{
testName: "stage_unstage not set no vars should not fail",
deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount", deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount",
jsonFile: `{"driverName":"test-driver","volumeHandle":"test-vol1"}`, jsonFile: `{"driverName":"test-driver","volumeHandle":"test-vol1"}`,
stageUnstageSet: false, stageUnstageSet: false,
}, },
// Old style PV based path negative test cases
{
testName: "fail: json file doesn't exist, old style pv based device path, missing PV",
volID: "project/zone/test-vol1",
deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount",
jsonFile: "",
stageUnstageSet: true,
shouldFail: true,
},
{
testName: "fail: no json, no PV.volID, old style pv based global path",
volID: "",
deviceMountPath: "plugins/csi/pv/test-pv-name/globalmount",
jsonFile: "",
shouldFail: true,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -1640,18 +1691,18 @@ func TestAttacherUnmountDevice(t *testing.T) {
if tc.deviceMountPath != "" { if tc.deviceMountPath != "" {
tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath) tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath)
} }
// Add the volume to NodeStagedVolumes // Add the volume to NodeStagedVolumes
cdc := csiAttacher.csiClient.(*fakeCsiDriverClient) cdc := csiAttacher.csiClient.(*fakeCsiDriverClient)
cdc.nodeClient.AddNodeStagedVolume(tc.volID, tc.deviceMountPath, nil) cdc.nodeClient.AddNodeStagedVolume(tc.volID, tc.deviceMountPath, nil)
// Make JSON for this object // Make the device staged path
if tc.deviceMountPath != "" { if tc.deviceMountPath != "" {
if err := os.MkdirAll(tc.deviceMountPath, 0755); err != nil { if err := os.MkdirAll(tc.deviceMountPath, 0755); err != nil {
t.Fatalf("error creating directory %s: %s", tc.deviceMountPath, err) t.Fatalf("error creating directory %s: %s", tc.deviceMountPath, err)
} }
} }
dir := filepath.Dir(tc.deviceMountPath) dir := filepath.Dir(tc.deviceMountPath)
// Make JSON for this object
if tc.jsonFile != "" { if tc.jsonFile != "" {
dataPath := filepath.Join(dir, volDataFileName) dataPath := filepath.Join(dir, volDataFileName)
if err := ioutil.WriteFile(dataPath, []byte(tc.jsonFile), 0644); err != nil { if err := ioutil.WriteFile(dataPath, []byte(tc.jsonFile), 0644); err != nil {
@ -1749,3 +1800,8 @@ func getCsiAttacherFromDeviceUnmounter(deviceUnmounter volume.DeviceUnmounter, w
csiAttacher.watchTimeout = watchTimeout csiAttacher.watchTimeout = watchTimeout
return csiAttacher return csiAttacher
} }
func generateSha(handle string) string {
result := sha256.Sum256([]byte(fmt.Sprintf("%s", handle)))
return fmt.Sprintf("%x", result)
}