From 4a5655ae75ca92e74490e1453be16e9d8f356348 Mon Sep 17 00:00:00 2001 From: Ryan Zhu Date: Mon, 13 Jul 2026 06:24:07 +0000 Subject: [PATCH] Copy StorageClass and AccessMode to cloneConfig on volume cloning Ensure that during volume cloning, target StorageClass and AccessMode from volumeConfig are copied onto cloneConfig if they are specified, so that the metadata in the TridentVolume CR remains in sync with the Kubernetes PV state. Fixes #1162 --- core/orchestrator_core.go | 6 ++++ core/orchestrator_core_test.go | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/core/orchestrator_core.go b/core/orchestrator_core.go index 63f67da05..685fd0f5b 100644 --- a/core/orchestrator_core.go +++ b/core/orchestrator_core.go @@ -2663,6 +2663,12 @@ func (o *TridentOrchestrator) cloneVolumeInitial( cloneConfig.SMBShareACL = volumeConfig.SMBShareACL } cloneConfig.ReadOnlyClone = volumeConfig.ReadOnlyClone + if volumeConfig.StorageClass != "" { + cloneConfig.StorageClass = volumeConfig.StorageClass + } + if volumeConfig.AccessMode != "" { + cloneConfig.AccessMode = volumeConfig.AccessMode + } cloneConfig.Namespace = volumeConfig.Namespace cloneConfig.RequestName = volumeConfig.RequestName diff --git a/core/orchestrator_core_test.go b/core/orchestrator_core_test.go index fcb981097..deea2e89e 100644 --- a/core/orchestrator_core_test.go +++ b/core/orchestrator_core_test.go @@ -1343,6 +1343,63 @@ func TestCloneVolume_VolumeDataSource_LUKS(t *testing.T) { defer orchestrator.DeleteVolume(ctx(), cloneResult.Config.Name) } +func TestCloneVolume_CrossStorageClassAndAccessMode(t *testing.T) { + // Setup + mockPools := tu.GetFakePools() + orchestrator := getOrchestrator(t, false) + + // Create backend + poolNames := []string{tu.SlowSnapshots} + pools := make(map[string]*fake.StoragePool, len(poolNames)) + for _, poolName := range poolNames { + pools[poolName] = mockPools[poolName] + } + volumes := make([]fake.Volume, 0) + cfg, err := fakedriver.NewFakeStorageDriverConfigJSON("slow-block", "block", pools, volumes) + assert.NoError(t, err) + _, err = orchestrator.AddBackend(ctx(), cfg, "") + assert.NoError(t, err) + defer orchestrator.DeleteBackend(ctx(), "slow-block") + + // Create StorageClass 'sc-rwo' + storageClassRWO := &storageclass.Config{Name: "sc-rwo"} + _, err = orchestrator.AddStorageClass(ctx(), storageClassRWO) + defer orchestrator.DeleteStorageClass(ctx(), storageClassRWO.Name) + assert.NoError(t, err) + + // Create StorageClass 'sc-rwx' + storageClassRWX := &storageclass.Config{Name: "sc-rwx"} + _, err = orchestrator.AddStorageClass(ctx(), storageClassRWX) + defer orchestrator.DeleteStorageClass(ctx(), storageClassRWX.Name) + assert.NoError(t, err) + + // Create the Source Volume using 'sc-rwo' and ReadWriteOnce + volConfig := tu.GenerateVolumeConfig("block", 1, "sc-rwo", config.Block) + volConfig.AccessMode = config.ReadWriteOnce + _, err = orchestrator.AddVolume(ctx(), volConfig) + assert.NoError(t, err) + defer orchestrator.DeleteVolume(ctx(), volConfig.Name) + + // Now clone the volume into 'sc-rwx' and ReadWriteMany + cloneName := volConfig.Name + "_clone" + cloneConfig := &storage.VolumeConfig{ + Name: cloneName, + StorageClass: "sc-rwx", + AccessMode: config.ReadWriteMany, + CloneSourceVolume: volConfig.Name, + VolumeMode: volConfig.VolumeMode, + } + cloneResult, err := orchestrator.CloneVolume(ctx(), cloneConfig) + assert.NoError(t, err) + defer orchestrator.DeleteVolume(ctx(), cloneResult.Config.Name) + + // Verify that the clone has the requested StorageClass and AccessMode, + // rather than inheriting the source volume's configuration. + assert.Equal(t, "sc-rwx", cloneResult.Config.StorageClass) + assert.Equal(t, config.ReadWriteMany, cloneResult.Config.AccessMode) +} + + func TestCloneVolume_WithImportNotManaged(t *testing.T) { orchestrator := getOrchestrator(t, false) defer cleanup(t, orchestrator)