From 56bf8d0298e6c1f3a5f3d565c574986f66c962fa Mon Sep 17 00:00:00 2001 From: Alex Kalenyuk Date: Tue, 10 Feb 2026 12:55:52 +0200 Subject: [PATCH] respect fractional sizes in gcnv today gcnv completely discards fractional sizes and picks the floor from the 1Gi aligment result i.e. 10.6Gi results in 10Gi volume which does not satisfy the user request Signed-off-by: Alex Kalenyuk --- storage_drivers/gcp/gcp_gcnv.go | 11 +++++ storage_drivers/gcp/gcp_gcnv_test.go | 61 ++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/storage_drivers/gcp/gcp_gcnv.go b/storage_drivers/gcp/gcp_gcnv.go index b4dc59209..8610a88dc 100644 --- a/storage_drivers/gcp/gcp_gcnv.go +++ b/storage_drivers/gcp/gcp_gcnv.go @@ -777,6 +777,11 @@ func (d *NASStorageDriver) Create( sizeBytes = minimumGCNVVolumeSizeBytes } + // Ensure sizeBytes is a multiple of 1 GiB + if remainder := sizeBytes % capacity.OneGiB; remainder != 0 { + sizeBytes = ((sizeBytes / capacity.OneGiB) + 1) * capacity.OneGiB + } + // Get the volume size based on the snapshot reserve sizeWithReserveBytes := drivers.CalculateVolumeSizeBytes(ctx, name, sizeBytes, snapshotReserveInt) @@ -2070,6 +2075,12 @@ func (d *NASStorageDriver) Resize(ctx context.Context, volConfig *storage.Volume if volume.SnapshotReserve > math.MaxInt { return fmt.Errorf("snapshot reserve too large") } + + // Ensure sizeBytes is a multiple of 1 GiB + if remainder := sizeBytes % capacity.OneGiB; remainder != 0 { + sizeBytes = ((sizeBytes / capacity.OneGiB) + 1) * capacity.OneGiB + } + sizeWithReserveBytes := drivers.CalculateVolumeSizeBytes(ctx, name, sizeBytes, int(volume.SnapshotReserve)) // If the volume is already the requested size, there's nothing to do diff --git a/storage_drivers/gcp/gcp_gcnv_test.go b/storage_drivers/gcp/gcp_gcnv_test.go index b6f03159f..b1ff289de 100644 --- a/storage_drivers/gcp/gcp_gcnv_test.go +++ b/storage_drivers/gcp/gcp_gcnv_test.go @@ -17,6 +17,7 @@ import ( tridentconfig "github.com/netapp/trident/config" mockapi "github.com/netapp/trident/mocks/mock_storage_drivers/mock_gcp" + "github.com/netapp/trident/pkg/capacity" "github.com/netapp/trident/pkg/convert" "github.com/netapp/trident/storage" storagefake "github.com/netapp/trident/storage/fake" @@ -1932,6 +1933,47 @@ func TestCreate_ZeroSize(t *testing.T) { assert.Equal(t, volume.FullName, volConfig.InternalID, "internal ID not set on volConfig") } +func TestCreate_SizeNotGibMultiple(t *testing.T) { + mockAPI, driver := newMockGCNVDriver(t) + + driver.Config.BackendName = "gcnv" + driver.Config.ServiceLevel = api.ServiceLevelPremium + driver.Config.NASType = "nfs" + + err := driver.populateConfigurationDefaults(ctx, &driver.Config) + assert.NoError(t, err, "error occurred") + + driver.initializeStoragePools(ctx) + driver.initializeTelemetry(ctx, api.BackendUUID) + + storagePool := driver.pools["gcnv_pool"] + + volConfig, capacityPool, volume, createRequest := getStructsForCreateNFSVolume(ctx, driver, storagePool) + volConfig.Size = "107374182401" // 100 GiB + 1 byte + createRequest.SizeBytes = int64(capacity.OneGiB * 101) // 101 GiB (rounded up) + volume.SizeBytes = int64(capacity.OneGiB * 101) // 101 GiB + createRequest.UnixPermissions = "0777" + volume.UnixPermissions = "0777" + + mockAPI.EXPECT().RefreshGCNVResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().VolumeExists(ctx, volConfig).Return(false, nil, nil).Times(1) + + mockAPI.EXPECT().CapacityPoolsForStoragePool(ctx, storagePool, + api.ServiceLevelPremium, gomock.Any()).Return([]*api.CapacityPool{capacityPool}).Times(1) + mockAPI.EXPECT().FilterCapacityPoolsOnTopology(ctx, []*api.CapacityPool{capacityPool}, volConfig.RequisiteTopologies, volConfig.PreferredTopologies).Return([]*api.CapacityPool{capacityPool}).Times(1) + mockAPI.EXPECT().CreateVolume(ctx, createRequest).Return(volume, nil).Times(1) + + mockAPI.EXPECT().WaitForVolumeState(ctx, volume, api.VolumeStateReady, []string{api.VolumeStateError}, + driver.volumeCreateTimeout).Return(api.VolumeStateReady, nil).Times(1) + + result := driver.Create(ctx, volConfig, storagePool, nil) + + assert.NoError(t, result, "create failed") + assert.Equal(t, createRequest.SizeBytes, int64(capacity.OneGiB*101), "request size mismatch") + assert.Equal(t, volConfig.Size, strconv.FormatInt(int64(capacity.OneGiB*101), 10), "config size mismatch") // 101 GiB + assert.Equal(t, volume.FullName, volConfig.InternalID, "internal ID not set on volConfig") +} + func TestCreate_ServiceLevelFlex(t *testing.T) { mockAPI, driver := newMockGCNVDriver(t) @@ -6469,6 +6511,25 @@ func TestResize(t *testing.T) { assert.Equal(t, strconv.FormatUint(newSize, 10), volConfig.Size, "size mismatch") } +func TestResize_SizeNotGibMultiple(t *testing.T) { + mockAPI, driver := newMockGCNVDriver(t) + + driver.initializeTelemetry(ctx, api.BackendUUID) + + volConfig, volume := getStructsForDestroyNFSVolume(ctx, driver) + newSize := (capacity.OneGiB * 100) + 1 // 100 GiB + 1 byte + expectedSize := capacity.OneGiB * 101 // 101 GiB (rounded up) + + mockAPI.EXPECT().RefreshGCNVResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().Volume(ctx, volConfig).Return(volume, nil).Times(1) + mockAPI.EXPECT().ResizeVolume(ctx, volume, int64(expectedSize)).Return(nil).Times(1) + + result := driver.Resize(ctx, volConfig, newSize) + + assert.Nil(t, result, "not nil") + assert.Equal(t, strconv.FormatUint(expectedSize, 10), volConfig.Size, "size mismatch") +} + func TestResize_DiscoveryFailed(t *testing.T) { mockAPI, driver := newMockGCNVDriver(t)