Skip to content

Commit 765f78d

Browse files
authored
Merge pull request kubernetes-csi#1157 from andyzhangx/fix/snapshot-source-mount-params
fix: prevent VolumeSnapshotClass server/share from overriding source volume mount in CreateSnapshot
2 parents e77dd09 + 9a61e55 commit 765f78d

2 files changed

Lines changed: 86 additions & 2 deletions

File tree

pkg/nfs/controllerserver.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,13 @@ func (cs *ControllerServer) internalMount(ctx context.Context, vol *nfsVolume, v
500500
paramShare: sharePath,
501501
}
502502
for k, v := range volumeContext {
503-
// don't set subDir field since only nfs-server:/share should be mounted in CreateVolume/DeleteVolume
504-
if strings.ToLower(k) != paramSubDir {
503+
// don't set subDir, server, or share fields: only nfs-server:/share
504+
// should be mounted via the volume's own values across all internal
505+
// mount callers (CreateVolume, DeleteVolume, CreateSnapshot, etc.)
506+
switch strings.ToLower(k) {
507+
case paramSubDir, paramServer, paramShare:
508+
continue
509+
default:
505510
volContext[k] = v
506511
}
507512
}

pkg/nfs/controllerserver_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,85 @@ func TestCreateSnapshotWithoutCompression(t *testing.T) {
12351235
_ = os.RemoveAll("/tmp/snapshot-name-no-compress")
12361236
}
12371237

1238+
func TestCreateSnapshotWithDifferentShareInSnapshotClass(t *testing.T) {
1239+
// Test that CreateSnapshot succeeds when VolumeSnapshotClass specifies
1240+
// a different server/share than the source volume. This verifies that
1241+
// the source volume mount uses server/share from the volumeHandle,
1242+
// not from the snapshot class parameters.
1243+
cs := initTestController(t)
1244+
1245+
// Setup: create source directory matching the source volume path
1246+
srcPath := "/tmp/src-pv-cross-share/subdir"
1247+
if err := os.MkdirAll(srcPath, 0777); err != nil {
1248+
t.Fatalf("failed to create source directory: %v", err)
1249+
}
1250+
defer func() { _ = os.RemoveAll("/tmp/src-pv-cross-share") }()
1251+
defer func() { _ = os.RemoveAll("/tmp/snapshot-cross-share") }()
1252+
1253+
// Snapshot class parameters specify a DIFFERENT server/share than the source volume.
1254+
// Note: share value omits leading '/' consistent with paramShare convention in this repo.
1255+
req := &csi.CreateSnapshotRequest{
1256+
SourceVolumeId: "nfs-server.default.svc.cluster.local#share#subdir#src-pv-cross-share",
1257+
Name: "snapshot-cross-share",
1258+
Parameters: map[string]string{
1259+
paramServer: "other-nfs-server.default.svc.cluster.local",
1260+
paramShare: "different-share",
1261+
},
1262+
}
1263+
1264+
resp, err := cs.CreateSnapshot(context.TODO(), req)
1265+
if err != nil {
1266+
t.Fatalf("CreateSnapshot failed with cross-share snapshot class: %v", err)
1267+
}
1268+
1269+
if resp == nil || resp.Snapshot == nil {
1270+
t.Fatalf("CreateSnapshot returned nil response")
1271+
}
1272+
1273+
// Verify the snapshot was created successfully
1274+
expectedSourceVolumeID := "nfs-server.default.svc.cluster.local#share#subdir#src-pv-cross-share"
1275+
if resp.Snapshot.SourceVolumeId != expectedSourceVolumeID {
1276+
t.Errorf("expected SourceVolumeId %q, got %q", expectedSourceVolumeID, resp.Snapshot.SourceVolumeId)
1277+
}
1278+
1279+
// Verify that the FakeMounter was called with the correct mount sources:
1280+
// - source volume mount should use the source volume's server/share
1281+
// - snapshot volume mount should use the snapshot class's server/share
1282+
fakeMounter := cs.Driver.ns.mounter.(*mount.FakeMounter)
1283+
mountLog := fakeMounter.GetLog()
1284+
1285+
var foundSourceMount, foundSnapMount bool
1286+
for _, action := range mountLog {
1287+
if action.Action != "mount" {
1288+
continue
1289+
}
1290+
// Source volume mount: server from volumeHandle, share from volumeHandle
1291+
// Use filepath.ToSlash to normalize Windows backslash separators
1292+
normalizedSource := filepath.ToSlash(action.Source)
1293+
if normalizedSource == "nfs-server.default.svc.cluster.local:/share" {
1294+
foundSourceMount = true
1295+
}
1296+
// Snapshot destination mount: server/share from snapshot class parameters
1297+
if normalizedSource == "other-nfs-server.default.svc.cluster.local:/different-share" {
1298+
foundSnapMount = true
1299+
}
1300+
}
1301+
if !foundSourceMount {
1302+
t.Errorf("expected source volume mount with source %q, mount log: %+v",
1303+
"nfs-server.default.svc.cluster.local:/share", mountLog)
1304+
}
1305+
if !foundSnapMount {
1306+
t.Errorf("expected snapshot destination mount with source %q, mount log: %+v",
1307+
"other-nfs-server.default.svc.cluster.local:/different-share", mountLog)
1308+
}
1309+
1310+
// Verify the snapshot archive file was created
1311+
archivePath := "/tmp/snapshot-cross-share/snapshot-cross-share/src-pv-cross-share.tar.gz"
1312+
if _, err := os.Stat(archivePath); os.IsNotExist(err) {
1313+
t.Errorf("expected snapshot archive at %s, but it does not exist", archivePath)
1314+
}
1315+
}
1316+
12381317
func TestCopyVolumeFromUncompressedSnapshot(t *testing.T) {
12391318
// Create an uncompressed snapshot archive and test restoration
12401319
srcPath := "/tmp/uncompressed-snapshot-test/uncompressed-snapshot-test"

0 commit comments

Comments
 (0)