diff --git a/storage_drivers/ontap/api/ontap_rest.go b/storage_drivers/ontap/api/ontap_rest.go index fb15bf330..d0d55cc0a 100644 --- a/storage_drivers/ontap/api/ontap_rest.go +++ b/storage_drivers/ontap/api/ontap_rest.go @@ -279,10 +279,6 @@ func (c RestClient) SupportsFeature(ctx context.Context, feature Feature) bool { return false } - if feature == LunGeometrySkip { - return !ontapSemVer.AtLeast(versionutils.MustParseSemantic("9.11.0")) - } - if minVersion, ok := featuresByVersion[feature]; ok { return ontapSemVer.AtLeast(minVersion) } else { diff --git a/storage_drivers/ontap/api/ontap_zapi.go b/storage_drivers/ontap/api/ontap_zapi.go index a469d07a5..8f095faad 100644 --- a/storage_drivers/ontap/api/ontap_zapi.go +++ b/storage_drivers/ontap/api/ontap_zapi.go @@ -201,7 +201,6 @@ var features = map[Feature]*versionutils.Version{ NetAppFlexGroupsClone: versionutils.MustParseSemantic("1.170.0"), // cDOT 9.7.0 NetAppFabricPoolFlexVol: versionutils.MustParseSemantic("1.120.0"), // cDOT 9.2.0 NetAppFabricPoolFlexGroup: versionutils.MustParseSemantic("1.150.0"), // cDOT 9.5.0 - LunGeometrySkip: versionutils.MustParseSemantic("1.150.0"), // cDOT 9.5.0 FabricPoolForSVMDR: versionutils.MustParseSemantic("1.150.0"), // cDOT 9.5.0 QosPolicies: versionutils.MustParseSemantic("1.180.0"), // cDOT 9.8.0 LIFServices: versionutils.MustParseSemantic("1.160.0"), // cDOT 9.6.0 @@ -216,7 +215,6 @@ var featuresByVersion = map[Feature]*versionutils.Version{ NetAppFlexGroupsClone: versionutils.MustParseSemantic("9.7.0"), NetAppFabricPoolFlexVol: versionutils.MustParseSemantic("9.2.0"), NetAppFabricPoolFlexGroup: versionutils.MustParseSemantic("9.5.0"), - LunGeometrySkip: versionutils.MustParseSemantic("9.5.0"), FabricPoolForSVMDR: versionutils.MustParseSemantic("9.5.0"), QosPolicies: versionutils.MustParseSemantic("9.8.0"), LIFServices: versionutils.MustParseSemantic("9.6.0"), diff --git a/storage_drivers/ontap/ontap_common.go b/storage_drivers/ontap/ontap_common.go index 4327acd81..5208a2eff 100644 --- a/storage_drivers/ontap/ontap_common.go +++ b/storage_drivers/ontap/ontap_common.go @@ -1228,6 +1228,7 @@ const ( DefaultMirroring = "false" DefaultLimitAggregateUsage = "" DefaultLimitVolumeSize = "" + DefaultLimitVolumePoolSize = "" DefaultTieringPolicy = "" ) @@ -1332,14 +1333,6 @@ func PopulateConfigurationDefaults(ctx context.Context, config *drivers.OntapSto config.Mirroring = DefaultMirroring } - if config.LimitAggregateUsage == "" { - config.LimitAggregateUsage = DefaultLimitAggregateUsage - } - - if config.LimitVolumeSize == "" { - config.LimitVolumeSize = DefaultLimitVolumeSize - } - if config.TieringPolicy == "" { config.TieringPolicy = DefaultTieringPolicy } @@ -1403,6 +1396,7 @@ func PopulateConfigurationDefaults(ctx context.Context, config *drivers.OntapSto "Mirroring": config.Mirroring, "LimitAggregateUsage": config.LimitAggregateUsage, "LimitVolumeSize": config.LimitVolumeSize, + "LimitVolumePoolSize": config.LimitVolumePoolSize, "Size": config.Size, "TieringPolicy": config.TieringPolicy, "AutoExportPolicy": config.AutoExportPolicy, @@ -1527,6 +1521,43 @@ func GetVolumeSize(sizeBytes uint64, poolDefaultSizeBytes string) (uint64, error return sizeBytes, nil } +// CheckVolumePoolSizeLimits checks if a volume pool size limit has been set. +func CheckVolumePoolSizeLimits( + ctx context.Context, requestedSize uint64, config *drivers.OntapStorageDriverConfig, +) (bool, uint64, error) { + // If the user specified a limit for volume pool size, parse and enforce it + limitVolumePoolSize := config.LimitVolumePoolSize + Logc(ctx).WithFields(LogFields{ + "limitVolumePoolSize": limitVolumePoolSize, + }).Debugf("Limits") + + if limitVolumePoolSize == "" { + Logc(ctx).Debugf("No limits specified, not limiting volume pool size") + return false, 0, nil + } + + var volumePoolSizeLimit uint64 + volumePoolSizeLimitStr, parseErr := utils.ConvertSizeToBytes(limitVolumePoolSize) + if parseErr != nil { + return false, 0, fmt.Errorf("error parsing limitVolumePoolSize: %v", parseErr) + } + volumePoolSizeLimit, _ = strconv.ParseUint(volumePoolSizeLimitStr, 10, 64) + + Logc(ctx).WithFields(LogFields{ + "limitVolumePoolSize": limitVolumePoolSize, + "volumePoolSizeLimit": volumePoolSizeLimit, + "requestedSizeBytes": requestedSize, + }).Debugf("Comparing pool limits") + + // Check whether pool size limit would prevent *any* Flexvol from working + if requestedSize > volumePoolSizeLimit { + return true, volumePoolSizeLimit, errors.UnsupportedCapacityRangeError(fmt.Errorf( + "requested size: %d > the pool size limit: %d", requestedSize, volumePoolSizeLimit)) + } + + return true, volumePoolSizeLimit, nil +} + func GetSnapshotReserve(snapshotPolicy, snapshotReserve string) (int, error) { if snapshotReserve != "" { // snapshotReserve defaults to "", so if it is explicitly set diff --git a/storage_drivers/ontap/ontap_common_test.go b/storage_drivers/ontap/ontap_common_test.go index 733318c35..d3dd4cf2c 100644 --- a/storage_drivers/ontap/ontap_common_test.go +++ b/storage_drivers/ontap/ontap_common_test.go @@ -4241,6 +4241,48 @@ func TestValidateNASDriver(t *testing.T) { assert.NoError(t, err) } +func TestCheckVolumePoolSizeLimits(t *testing.T) { + ctx := context.Background() + requestedSize := uint64(1073741824) // 1Gi + + // Returns because LimitVolumePoolSize not specified. + config := &drivers.OntapStorageDriverConfig{ + LimitVolumePoolSize: "", + } + shouldLimit, sizeLimit, err := CheckVolumePoolSizeLimits(ctx, requestedSize, config) + assert.False(t, shouldLimit, "expected should limit to be false") + assert.Zero(t, sizeLimit, "expected zero size limit") + assert.Nil(t, err, "expected nil error") + + // Errors when LimitVolumePoolSize is not empty but invalid and cannot be parsed. + config = &drivers.OntapStorageDriverConfig{ + LimitVolumePoolSize: "Gi", + } + shouldLimit, sizeLimit, err = CheckVolumePoolSizeLimits(ctx, requestedSize, config) + assert.False(t, shouldLimit, "expected should limit to be false") + assert.Zero(t, sizeLimit, "expected zero size limit") + assert.NotNil(t, err, "expected non-nil error") + + // Errors when the requested volume is larger than LimitVolumePoolSize. + requestedSize = uint64(2000000000) + config = &drivers.OntapStorageDriverConfig{ + LimitVolumePoolSize: "1Gi", + } + shouldLimit, sizeLimit, err = CheckVolumePoolSizeLimits(ctx, requestedSize, config) + assert.True(t, shouldLimit, "expected should limit to be true") + assert.Equal(t, sizeLimit, uint64(1073741824), "expected size limit of 1Gi") + assert.NotNil(t, err, "expected non-nil error") + + requestedSize = uint64(1000000000) + config = &drivers.OntapStorageDriverConfig{ + LimitVolumePoolSize: "1Gi", + } + shouldLimit, sizeLimit, err = CheckVolumePoolSizeLimits(ctx, requestedSize, config) + assert.True(t, shouldLimit, "expected should limit to be true") + assert.Equal(t, sizeLimit, uint64(1073741824), "expected size limit of 1Gi") + assert.Nil(t, err, "expected nil error") +} + func TestGetSnapshotReserve(t *testing.T) { // snapshot reserve is not passed snapshotPolicy := "fakePolicy" diff --git a/storage_drivers/ontap/ontap_nas_qtree.go b/storage_drivers/ontap/ontap_nas_qtree.go index 23926a5a2..65456bd68 100644 --- a/storage_drivers/ontap/ontap_nas_qtree.go +++ b/storage_drivers/ontap/ontap_nas_qtree.go @@ -355,6 +355,12 @@ func (d *NASQtreeStorageDriver) Create( return err } + if _, _, checkVolumeSizeLimitsError := drivers.CheckVolumeSizeLimits( + ctx, sizeBytes, d.Config.CommonStorageDriverConfig, + ); checkVolumeSizeLimitsError != nil { + return checkVolumeSizeLimitsError + } + // Ensure qtree name isn't too long if len(name) > maxQtreeNameLength { return fmt.Errorf("volume %s name exceeds the limit of %d characters", name, maxQtreeNameLength) @@ -429,7 +435,7 @@ func (d *NASQtreeStorageDriver) Create( // Make sure we have a Flexvol for the new qtree flexvol, err := d.ensureFlexvolForQtree( ctx, aggregate, spaceReserve, snapshotPolicy, tieringPolicy, enableSnapshotDir, enableEncryption, sizeBytes, - d.Config, snapshotReserve, exportPolicy) + &d.Config, snapshotReserve, exportPolicy) if err != nil { errMessage := fmt.Sprintf("ONTAP-NAS-QTREE pool %s/%s; Flexvol location/creation failed %s: %v", storagePool.Name(), aggregate, name, err) @@ -1034,18 +1040,18 @@ func (d *NASQtreeStorageDriver) Get(ctx context.Context, name string) error { // qtree or it creates a new Flexvol with the needed attributes. func (d *NASQtreeStorageDriver) ensureFlexvolForQtree( ctx context.Context, aggregate, spaceReserve, snapshotPolicy, tieringPolicy string, enableSnapshotDir bool, - enableEncryption *bool, sizeBytes uint64, config drivers.OntapStorageDriverConfig, snapshotReserve, + enableEncryption *bool, sizeBytes uint64, config *drivers.OntapStorageDriverConfig, snapshotReserve, exportPolicy string, ) (string, error) { - shouldLimitVolumeSize, flexvolQuotaSizeLimit, checkVolumeSizeLimitsError := drivers.CheckVolumeSizeLimits( - ctx, sizeBytes, config.CommonStorageDriverConfig) - if checkVolumeSizeLimitsError != nil { - return "", checkVolumeSizeLimitsError + shouldLimitFlexvolSize, flexvolSizeLimit, checkFlexvolSizeLimitsError := CheckVolumePoolSizeLimits( + ctx, sizeBytes, config) + if checkFlexvolSizeLimitsError != nil { + return "", checkFlexvolSizeLimitsError } // Check if a suitable Flexvol already exists flexvol, err := d.findFlexvolForQtree(ctx, aggregate, spaceReserve, snapshotPolicy, tieringPolicy, snapshotReserve, - enableSnapshotDir, enableEncryption, shouldLimitVolumeSize, sizeBytes, flexvolQuotaSizeLimit) + enableSnapshotDir, enableEncryption, shouldLimitFlexvolSize, sizeBytes, flexvolSizeLimit) if err != nil { return "", fmt.Errorf("error finding Flexvol for qtree: %v", err) } @@ -1175,8 +1181,8 @@ func (d *NASQtreeStorageDriver) createFlexvolForQtree( // is returned at random. func (d *NASQtreeStorageDriver) findFlexvolForQtree( ctx context.Context, aggregate, spaceReserve, snapshotPolicy, tieringPolicy, snapshotReserve string, - enableSnapshotDir bool, enableEncryption *bool, shouldLimitFlexvolQuotaSize bool, - sizeBytes, flexvolQuotaSizeLimit uint64, + enableSnapshotDir bool, enableEncryption *bool, shouldLimitFlexvolSize bool, + sizeBytes, flexvolSizeLimit uint64, ) (string, error) { snapshotReserveInt, err := GetSnapshotReserve(snapshotPolicy, snapshotReserve) if err != nil { @@ -1207,15 +1213,14 @@ func (d *NASQtreeStorageDriver) findFlexvolForQtree( volName := volume.Name // skip flexvols over the size limit - if shouldLimitFlexvolQuotaSize { + if shouldLimitFlexvolSize { sizeWithRequest, err := d.getOptimalSizeForFlexvol(ctx, volName, sizeBytes) if err != nil { Logc(ctx).Errorf("Error checking size for existing qtree. %v %v", volName, err) continue } - if sizeWithRequest > flexvolQuotaSizeLimit { - Logc(ctx).Debugf("Flexvol quota size for %v is over the limit of %v", volName, - flexvolQuotaSizeLimit) + if sizeWithRequest > flexvolSizeLimit { + Logc(ctx).Debugf("Flexvol quota size for %v is over the limit of %v", volName, flexvolSizeLimit) continue } } @@ -1257,8 +1262,7 @@ func (d *NASQtreeStorageDriver) getOptimalSizeForFlexvol( return 0, err } - return calculateFlexvolEconomySizeBytes(ctx, flexvol, volAttrs, newQtreeSizeBytes, - totalDiskLimitBytes), nil + return calculateFlexvolEconomySizeBytes(ctx, flexvol, volAttrs, newQtreeSizeBytes, totalDiskLimitBytes), nil } // addDefaultQuotaForFlexvol adds a default quota rule to a Flexvol so that quotas for @@ -1298,15 +1302,14 @@ func (d *NASQtreeStorageDriver) setQuotaForQtree(ctx context.Context, qtree, fle } // getQuotaDiskLimitSize returns the disk limit size for the specified quota. -func (d *NASQtreeStorageDriver) getQuotaDiskLimitSize(ctx context.Context, name, flexvol string) (int64, error) { +func (d *NASQtreeStorageDriver) getQuotaDiskLimitSize(ctx context.Context, name, flexvol string) (uint64, error) { quota, err := d.API.QuotaGetEntry(ctx, flexvol, name, "tree") if err != nil { return 0, err } - quotaSize := quota.DiskLimitBytes - - return quotaSize, nil + // Report no quota as 0 + return uint64(max(0, quota.DiskLimitBytes)), nil } // enableQuotas disables quotas on a Flexvol, optionally waiting for the operation to finish. @@ -2085,29 +2088,49 @@ func (d *NASQtreeStorageDriver) Resize(ctx context.Context, volConfig *storage.V return resizeError } - volConfig.Size = strconv.FormatInt(quotaSize, 10) - if int64(sizeBytes) == quotaSize { + volConfig.Size = strconv.FormatUint(quotaSize, 10) + if sizeBytes == quotaSize { Logc(ctx).Infof("Requested size and existing volume size are the same for volume %s.", name) return nil } - if quotaSize >= 0 && int64(sizeBytes) < quotaSize { + if sizeBytes < quotaSize { return fmt.Errorf("requested size %d is less than existing volume size %d", sizeBytes, quotaSize) } - deltaQuotaSize := int64(sizeBytes) - quotaSize + deltaQuotaSize := sizeBytes - quotaSize + // Enforce aggregate usage limit if aggrLimitsErr := checkAggregateLimitsForFlexvol( ctx, flexvol, sizeBytes, d.Config, d.GetAPI(), ); aggrLimitsErr != nil { return aggrLimitsErr } + // Enforce volume size limit if _, _, checkVolumeSizeLimitsError := drivers.CheckVolumeSizeLimits( ctx, sizeBytes, d.Config.CommonStorageDriverConfig, ); checkVolumeSizeLimitsError != nil { return checkVolumeSizeLimitsError } + // Enforce Flexvol pool size limit + shouldLimitFlexvolSize, flexvolSizeLimit, checkFlexvolSizeLimitsError := CheckVolumePoolSizeLimits( + ctx, sizeBytes, &d.Config) + if checkFlexvolSizeLimitsError != nil { + return checkFlexvolSizeLimitsError + } + if shouldLimitFlexvolSize { + flexvolSize, flexvolSizeErr := d.getOptimalSizeForFlexvol(ctx, flexvol, uint64(deltaQuotaSize)) + if flexvolSizeErr != nil { + return flexvolSizeErr + } + if flexvolSize > flexvolSizeLimit { + return errors.UnsupportedCapacityRangeError(fmt.Errorf( + "requested size %d would exceed the pool size limit %d", sizeBytes, flexvolSizeLimit)) + } + } + + // Resize the Flexvol err = d.resizeFlexvol(ctx, flexvol, uint64(deltaQuotaSize)) if err != nil { Logc(ctx).WithField("error", err).Error("Failed to resize flexvol.") @@ -2131,20 +2154,20 @@ func (d *NASQtreeStorageDriver) resizeFlexvol(ctx context.Context, flexvol strin flexvolSizeBytes, err := d.getOptimalSizeForFlexvol(ctx, flexvol, sizeBytes) if err != nil { Logc(ctx).Warnf("Could not calculate optimal Flexvol size. %v", err) - // Lacking the optimal size, just grow the Flexvol to contain the new qtree + + // Lacking the optimal size, just grow the Flexvol to contain the new qtree if Flexvol size isn't capped size := strconv.FormatUint(sizeBytes, 10) - err := d.API.VolumeSetSize(ctx, flexvol, "+"+size) - if err != nil { - return fmt.Errorf("flexvol resize failed: %v", err) - } - } else { - // Got optimal size, so just set the Flexvol to that value - flexvolSizeStr := strconv.FormatUint(flexvolSizeBytes, 10) - err := d.API.VolumeSetSize(ctx, flexvol, flexvolSizeStr) - if err != nil { + if err = d.API.VolumeSetSize(ctx, flexvol, "+"+size); err != nil { return fmt.Errorf("flexvol resize failed: %v", err) } } + + // Got optimal size, so just set the Flexvol to that value + flexvolSizeStr := strconv.FormatUint(flexvolSizeBytes, 10) + if err = d.API.VolumeSetSize(ctx, flexvol, flexvolSizeStr); err != nil { + return fmt.Errorf("flexvol resize failed: %v", err) + } + return nil } diff --git a/storage_drivers/ontap/ontap_nas_qtree_test.go b/storage_drivers/ontap/ontap_nas_qtree_test.go index 8a7d186ce..2d8d83ba2 100644 --- a/storage_drivers/ontap/ontap_nas_qtree_test.go +++ b/storage_drivers/ontap/ontap_nas_qtree_test.go @@ -20,13 +20,12 @@ import ( "github.com/netapp/trident/acp" tridentconfig "github.com/netapp/trident/config" mockacp "github.com/netapp/trident/mocks/mock_acp" - "github.com/netapp/trident/utils" - mockapi "github.com/netapp/trident/mocks/mock_storage_drivers/mock_ontap" "github.com/netapp/trident/storage" sa "github.com/netapp/trident/storage_attribute" drivers "github.com/netapp/trident/storage_drivers" "github.com/netapp/trident/storage_drivers/ontap/api" + "github.com/netapp/trident/utils" "github.com/netapp/trident/utils/errors" ) @@ -1133,7 +1132,7 @@ func TestGet_WithErrorInApiOperation(t *testing.T) { } func TestEnsureFlexvolForQtree_Success_EligibleFlexvolFound(t *testing.T) { - driverConfig := drivers.OntapStorageDriverConfig{ + driverConfig := &drivers.OntapStorageDriverConfig{ CommonStorageDriverConfig: &drivers.CommonStorageDriverConfig{}, } @@ -1157,7 +1156,7 @@ func TestEnsureFlexvolForQtree_Success_EligibleFlexvolFound(t *testing.T) { } func TestEnsureFlexvolForQtree_Success_NoEligibleFlexvol(t *testing.T) { - driverConfig := drivers.OntapStorageDriverConfig{ + driverConfig := &drivers.OntapStorageDriverConfig{ CommonStorageDriverConfig: &drivers.CommonStorageDriverConfig{}, } @@ -1187,8 +1186,8 @@ func TestEnsureFlexvolForQtree_Success_NoEligibleFlexvol(t *testing.T) { } func TestEnsureFlexvolForQtree_WithInvalidConfig(t *testing.T) { - driverConfig := drivers.OntapStorageDriverConfig{ - CommonStorageDriverConfig: &drivers.CommonStorageDriverConfig{LimitVolumeSize: "invalid"}, + driverConfig := &drivers.OntapStorageDriverConfig{ + LimitVolumePoolSize: "invalid", } _, driver := newMockOntapNasQtreeDriver(t) @@ -1202,7 +1201,7 @@ func TestEnsureFlexvolForQtree_WithInvalidConfig(t *testing.T) { } func TestEnsureFlexvolForQtree_WithErrorInApiOperation(t *testing.T) { - driverConfig := drivers.OntapStorageDriverConfig{ + driverConfig := &drivers.OntapStorageDriverConfig{ CommonStorageDriverConfig: &drivers.CommonStorageDriverConfig{}, } @@ -2804,6 +2803,147 @@ func TestResize_WithInvalidVolumeSizeLimit(t *testing.T) { assert.Error(t, result, "Expected error when invalid volume size limit, but got no error") } +func TestResize_OverSizeLimit(t *testing.T) { + volName := "vol1" + volNameInternal := volName + "Internal" + volInfo, _ := MockGetVolumeInfo(ctx, volName) + volInfo.Aggregates = []string{"aggr1"} + volConfig := &storage.VolumeConfig{ + Name: volName, + InternalName: volNameInternal, + } + resizeToInBytes := 10737418240 // 10g + + quotaEntry := &api.QuotaEntry{Target: "/vol/vol1/qtree1", DiskLimitBytes: 1073741824} + + mockAPI, driver := newMockOntapNasQtreeDriver(t) + driver.Config.CommonStorageDriverConfig.LimitVolumeSize = "9GiB" + + mockAPI.EXPECT().QtreeExists(ctx, volNameInternal, gomock.Any()).AnyTimes().Return(true, volName, nil) + mockAPI.EXPECT().QuotaGetEntry(ctx, volName, volNameInternal, gomock.Any()).AnyTimes().Return(quotaEntry, nil) + mockAPI.EXPECT().VolumeInfo(ctx, volName).AnyTimes().Return(volInfo, nil) + + result := driver.Resize(ctx, volConfig, uint64(resizeToInBytes)) + assert.Error(t, result, "Expected error with insufficient volume size limit, but got no error") +} + +func TestResize_OverPoolSizeLimit_CheckPassed(t *testing.T) { + volName := "vol1" + volNameInternal := volName + "Internal" + volInfo, _ := MockGetVolumeInfo(ctx, volName) + volInfo.Aggregates = []string{"aggr1"} + volInfo.Size = "1181116006" + volConfig := &storage.VolumeConfig{ + Name: volName, + InternalName: volNameInternal, + } + resizeToInBytes := 10737418240 // 10g + + quotaEntry := &api.QuotaEntry{Target: "/vol/vol1/qtree1", DiskLimitBytes: 1073741824} + + mockAPI, driver := newMockOntapNasQtreeDriver(t) + driver.Config.LimitVolumePoolSize = "20GiB" + + mockAPI.EXPECT().QtreeExists(ctx, volNameInternal, gomock.Any()).AnyTimes().Return(true, volName, nil) + mockAPI.EXPECT().QuotaGetEntry(ctx, volName, volNameInternal, gomock.Any()).AnyTimes().Return(quotaEntry, nil) + mockAPI.EXPECT().VolumeInfo(ctx, volName).AnyTimes().Return(volInfo, nil) + mockAPI.EXPECT().VolumeSetSize(ctx, volName, gomock.Any()).AnyTimes().Return(nil) + mockAPI.EXPECT().QuotaSetEntry(ctx, volNameInternal, volName, gomock.Any(), gomock.Any()).AnyTimes().Return(nil) + mockAPI.EXPECT().QuotaEntryList(ctx, volName).AnyTimes().Return(api.QuotaEntries{quotaEntry}, nil) + + result := driver.Resize(ctx, volConfig, uint64(resizeToInBytes)) + + assert.NoError(t, result, "Expected no error") +} + +func TestResize_OverPoolSizeLimit_MultipleQtrees(t *testing.T) { + volName := "vol1" + volNameInternal := volName + "Internal" + volInfo, _ := MockGetVolumeInfo(ctx, volName) + volInfo.Aggregates = []string{"aggr1"} + volInfo.Size = "10737418240" + volInfo.SnapshotReserve = 0 + volConfig := &storage.VolumeConfig{ + Name: volName, + InternalName: volNameInternal, + } + resizeToInBytes := 10737418240 // 10g + + quotaEntry1 := &api.QuotaEntry{Target: "/vol/vol1/qtree1", DiskLimitBytes: 1073741824} + quotaEntry2 := &api.QuotaEntry{Target: "/vol/vol1/qtree2", DiskLimitBytes: 3221225472} + quotaEntries := api.QuotaEntries{quotaEntry1, quotaEntry2} + + mockAPI, driver := newMockOntapNasQtreeDriver(t) + driver.Config.LimitVolumePoolSize = "11GiB" + + mockAPI.EXPECT().QtreeExists(ctx, volNameInternal, gomock.Any()).Return(true, volName, nil) + mockAPI.EXPECT().QuotaGetEntry(ctx, volName, volNameInternal, gomock.Any()).Return(quotaEntry1, nil) + mockAPI.EXPECT().VolumeInfo(ctx, volName).Return(volInfo, nil) + mockAPI.EXPECT().VolumeInfo(ctx, volName).Return(volInfo, nil) + mockAPI.EXPECT().QuotaEntryList(ctx, volName).Return(quotaEntries, nil) + + result := driver.Resize(ctx, volConfig, uint64(resizeToInBytes)) + + assert.Error(t, result, "Expected error") + ok, _ := errors.HasUnsupportedCapacityRangeError(result) + assert.True(t, ok) +} + +func TestResize_OverPoolSizeLimit_OneQtree(t *testing.T) { + volName := "vol1" + volNameInternal := volName + "Internal" + volInfo, _ := MockGetVolumeInfo(ctx, volName) + volInfo.Aggregates = []string{"aggr1"} + volInfo.Size = "1181116006" + volConfig := &storage.VolumeConfig{ + Name: volName, + InternalName: volNameInternal, + } + resizeToInBytes := 10737418240 // 10g + + quotaEntry := &api.QuotaEntry{Target: "/vol/vol1/qtree1", DiskLimitBytes: 1073741824} + + mockAPI, driver := newMockOntapNasQtreeDriver(t) + driver.Config.LimitVolumePoolSize = "9GiB" + + mockAPI.EXPECT().QtreeExists(ctx, volNameInternal, gomock.Any()).Return(true, volName, nil) + mockAPI.EXPECT().QuotaGetEntry(ctx, volName, volNameInternal, gomock.Any()).Return(quotaEntry, nil) + mockAPI.EXPECT().VolumeInfo(ctx, volName).Return(volInfo, nil) + + result := driver.Resize(ctx, volConfig, uint64(resizeToInBytes)) + + assert.Error(t, result, "Expected error") + ok, _ := errors.HasUnsupportedCapacityRangeError(result) + assert.True(t, ok) +} + +func TestResize_OverPoolSizeLimitCheck_VolumeInfoFailed(t *testing.T) { + volName := "vol1" + volNameInternal := volName + "Internal" + volInfo, _ := MockGetVolumeInfo(ctx, volName) + volInfo.Aggregates = []string{"aggr1"} + volInfo.Size = "1181116006" + volConfig := &storage.VolumeConfig{ + Name: volName, + InternalName: volNameInternal, + } + resizeToInBytes := 10737418240 // 10g + + quotaEntry := &api.QuotaEntry{Target: "/vol/vol1/qtree1", DiskLimitBytes: 1073741824} + + mockAPI, driver := newMockOntapNasQtreeDriver(t) + driver.Config.LimitVolumePoolSize = "20GiB" + + mockAPI.EXPECT().QtreeExists(ctx, volNameInternal, gomock.Any()).AnyTimes().Return(true, volName, nil) + mockAPI.EXPECT().QuotaGetEntry(ctx, volName, volNameInternal, gomock.Any()).AnyTimes().Return(quotaEntry, nil) + mockAPI.EXPECT().VolumeInfo(ctx, volName).Return(volInfo, nil) + mockAPI.EXPECT().VolumeInfo(ctx, volName).AnyTimes().Return(nil, failed) + + result := driver.Resize(ctx, volConfig, uint64(resizeToInBytes)) + + assert.Error(t, result, "Expected error") +} + func TestResize_WithErrorInApiOperation(t *testing.T) { volName := "testVol" volNameInternal := volName + "Internal" @@ -3182,14 +3322,14 @@ func TestNASQtreeStorageDriver_getQuotaDiskLimitSize_1Gi(t *testing.T) { } qtreeName := "foo" flexvolName := "bar" - expectedLimit := quotaEntry.DiskLimitBytes + expectedLimit := uint64(quotaEntry.DiskLimitBytes) mockAPI := mockapi.NewMockOntapAPI(mockCtrl) mockAPI.EXPECT().QuotaGetEntry(gomock.Any(), flexvolName, qtreeName, "tree").Return(quotaEntry, nil) - driver := newNASQtreeStorageDriver(mockAPI) limit, err := driver.getQuotaDiskLimitSize(ctx, qtreeName, flexvolName) + assert.Nil(t, err, fmt.Sprintf("Unexpected err, %v", err)) assert.Equal(t, expectedLimit, limit, "Unexpected return value") } @@ -3203,14 +3343,35 @@ func TestNASQtreeStorageDriver_getQuotaDiskLimitSize_1Ki(t *testing.T) { } qtreeName := "foo" flexvolName := "bar" - expectedLimit := quotaEntry.DiskLimitBytes + expectedLimit := uint64(quotaEntry.DiskLimitBytes) mockAPI := mockapi.NewMockOntapAPI(mockCtrl) mockAPI.EXPECT().QuotaGetEntry(gomock.Any(), flexvolName, qtreeName, "tree").Return(quotaEntry, nil) + driver := newNASQtreeStorageDriver(mockAPI) + + limit, err := driver.getQuotaDiskLimitSize(ctx, qtreeName, flexvolName) + + assert.Nil(t, err, fmt.Sprintf("Unexpected err, %v", err)) + assert.Equal(t, expectedLimit, limit, "Unexpected return value") +} + +func TestNASQtreeStorageDriver_getQuotaDiskLimitSize_NoLimit(t *testing.T) { + mockCtrl := gomock.NewController(t) + quotaEntry := &api.QuotaEntry{ + Target: "", + DiskLimitBytes: -1, + } + qtreeName := "foo" + flexvolName := "bar" + expectedLimit := uint64(0) + + mockAPI := mockapi.NewMockOntapAPI(mockCtrl) + mockAPI.EXPECT().QuotaGetEntry(gomock.Any(), flexvolName, qtreeName, "tree").Return(quotaEntry, nil) driver := newNASQtreeStorageDriver(mockAPI) limit, err := driver.getQuotaDiskLimitSize(ctx, qtreeName, flexvolName) + assert.Nil(t, err, fmt.Sprintf("Unexpected err, %v", err)) assert.Equal(t, expectedLimit, limit, "Unexpected return value") } @@ -3220,14 +3381,14 @@ func TestNASQtreeStorageDriver_getQuotaDiskLimitSize_Error(t *testing.T) { qtreeName := "foo" flexvolName := "bar" - expectedLimit := int64(0) + expectedLimit := uint64(0) mockAPI := mockapi.NewMockOntapAPI(mockCtrl) mockAPI.EXPECT().QuotaGetEntry(gomock.Any(), flexvolName, qtreeName, "tree").Return(nil, fmt.Errorf("error")) - driver := newNASQtreeStorageDriver(mockAPI) limit, err := driver.getQuotaDiskLimitSize(ctx, qtreeName, flexvolName) + assert.NotNil(t, err, "Unexpected success") assert.Equal(t, expectedLimit, limit, "Unexpected return value") } @@ -3356,6 +3517,7 @@ func TestNASQtreeStorageDriver_VolumeCreate(t *testing.T) { driver.physicalPools = map[string]storage.Pool{"pool1": pool1} driver.Config.AutoExportPolicy = true driver.Config.NASType = sa.SMB + driver.Config.LimitVolumeSize = "2g" volAttrs := map[string]sa.Request{} addCommonExpectForQtreeCreate(mockAPI, flexvol, flexvolName, sizeBytesStr) @@ -3558,6 +3720,89 @@ func TestCreate_WithInvalidConfig(t *testing.T) { } } +func TestCreate_OverSizeLimit(t *testing.T) { + mockAPI, driver := newMockOntapNasQtreeDriver(t) + qtreeName := "qtree1" + volConfig := &storage.VolumeConfig{ + Size: "200g", + FileSystem: "nfs", + Name: qtreeName, + InternalName: qtreeName, + } + + sb := &storage.StorageBackend{} + sb.SetBackendUUID(BackendUUID) + pool1 := storage.NewStoragePool(sb, "pool1") + pool1.SetInternalAttributes(map[string]string{ + SpaceReserve: "none", + SnapshotPolicy: "fake-snap-policy", + SnapshotReserve: "10", + UnixPermissions: "0755", + SnapshotDir: "true", + ExportPolicy: "fake-export-policy", + SecurityStyle: "mixed", + Encryption: "false", + TieringPolicy: "", + QosPolicy: "fake-qos-policy", + AdaptiveQosPolicy: "", + }) + driver.physicalPools = map[string]storage.Pool{"pool1": pool1} + driver.Config.AutoExportPolicy = true + driver.Config.NASType = sa.SMB + driver.Config.LimitVolumeSize = "100g" + volAttrs := map[string]sa.Request{} + + mockAPI.EXPECT().QtreeExists(ctx, qtreeName, "*").Return(false, "", nil) + + result := driver.Create(ctx, volConfig, pool1, volAttrs) + + assert.Error(t, result) +} + +func TestCreate_OverPoolSizeLimit(t *testing.T) { + mockAPI, driver := newMockOntapNasQtreeDriver(t) + qtreeName := "qtree1" + volConfig := &storage.VolumeConfig{ + Size: "5g", + FileSystem: "nfs", + Name: qtreeName, + InternalName: qtreeName, + } + flexvolName := "flexvol1" + flexvol := &api.Volume{Name: flexvolName} + sizeBytes := 1073741824 + sizeBytesStr := strconv.FormatUint(uint64(sizeBytes), 10) + + sb := &storage.StorageBackend{} + sb.SetBackendUUID(BackendUUID) + pool1 := storage.NewStoragePool(sb, "pool1") + pool1.SetInternalAttributes(map[string]string{ + SpaceReserve: "none", + SnapshotPolicy: "fake-snap-policy", + SnapshotReserve: "10", + UnixPermissions: "0755", + SnapshotDir: "true", + ExportPolicy: "fake-export-policy", + SecurityStyle: "mixed", + Encryption: "false", + TieringPolicy: "", + QosPolicy: "fake-qos-policy", + AdaptiveQosPolicy: "", + }) + driver.physicalPools = map[string]storage.Pool{"pool1": pool1} + driver.Config.AutoExportPolicy = true + driver.Config.NASType = sa.SMB + driver.Config.LimitVolumePoolSize = "2g" + volAttrs := map[string]sa.Request{} + + addCommonExpectForQtreeCreate(mockAPI, flexvol, flexvolName, sizeBytesStr) + mockAPI.EXPECT().QtreeExists(ctx, qtreeName, "*").Return(false, "", nil) + + result := driver.Create(ctx, volConfig, pool1, volAttrs) + + assert.Error(t, result, "Expected error in volume create when ineligible backend, got nil") +} + func TestCreate_WithIneligibleBackend(t *testing.T) { volConfig := &storage.VolumeConfig{ Size: "1g", diff --git a/storage_drivers/ontap/ontap_san_economy.go b/storage_drivers/ontap/ontap_san_economy.go index ab8427847..56ba4c84b 100644 --- a/storage_drivers/ontap/ontap_san_economy.go +++ b/storage_drivers/ontap/ontap_san_economy.go @@ -465,6 +465,12 @@ func (d *SANEconomyStorageDriver) Create( lunSize := strconv.FormatUint(sizeBytes, 10) + if _, _, checkVolumeSizeLimitsError := drivers.CheckVolumeSizeLimits( + ctx, sizeBytes, d.Config.CommonStorageDriverConfig, + ); checkVolumeSizeLimitsError != nil { + return checkVolumeSizeLimitsError + } + // Ensure LUN name isn't too long if len(name) > maxLunNameLength { return fmt.Errorf("volume %s name exceeds the limit of %d characters", name, maxLunNameLength) @@ -574,7 +580,7 @@ func (d *SANEconomyStorageDriver) Create( // Make sure we have a Flexvol for the new LUN bucketVol, newVol, err = d.ensureFlexvolForLUN( - ctx, volAttrs, sizeBytes, opts, d.Config, storagePool, ignoredVols, + ctx, volAttrs, sizeBytes, opts, &d.Config, storagePool, ignoredVols, ) if err != nil { errMessage := fmt.Sprintf( @@ -810,6 +816,29 @@ func (d *SANEconomyStorageDriver) createLUNClone( return fmt.Errorf("error source LUN %s does not exist", source) } + sourceLunSizeBytes, lunSizeErr := d.getLUNSize(ctx, source, flexvol) + if lunSizeErr != nil { + Logc(ctx).WithField("error", err).Error("Failed to determine LUN size") + return lunSizeErr + } + + shouldLimitFlexvolSize, flexvolSizeLimit, checkFlexvolSizeLimitsError := CheckVolumePoolSizeLimits( + ctx, sourceLunSizeBytes, config) + if checkFlexvolSizeLimitsError != nil { + return checkFlexvolSizeLimitsError + } + + if shouldLimitFlexvolSize { + sizeWithRequest, flexvolSizeErr := d.getOptimalSizeForFlexvol(ctx, flexvol, sourceLunSizeBytes) + if flexvolSizeErr != nil { + return fmt.Errorf("could not calculate optimal Flexvol size; %v", flexvolSizeErr) + } + if sizeWithRequest > flexvolSizeLimit { + return errors.UnsupportedCapacityRangeError(fmt.Errorf( + "requested size %d would exceed the pool size limit %d", sourceLunSizeBytes, flexvolSizeLimit)) + } + } + // Create the clone based on given LUN // For the ONTAP SAN economy driver, we set the QoS policy at the LUN layer. err = client.LunCloneCreate(ctx, flexvol, source, lunName, qosPolicyGroup) @@ -1509,18 +1538,16 @@ func (d *SANEconomyStorageDriver) Get(ctx context.Context, name string) error { // as is a boolean indicating whether the volume was newly created to satisfy this request. func (d *SANEconomyStorageDriver) ensureFlexvolForLUN( ctx context.Context, volAttrs *api.Volume, sizeBytes uint64, opts map[string]string, - config drivers.OntapStorageDriverConfig, - storagePool storage.Pool, ignoredVols map[string]struct{}, + config *drivers.OntapStorageDriverConfig, storagePool storage.Pool, ignoredVols map[string]struct{}, ) (string, bool, error) { - shouldLimitVolumeSize, flexvolSizeLimit, checkVolumeSizeLimitsError := drivers.CheckVolumeSizeLimits( - ctx, sizeBytes, config.CommonStorageDriverConfig, - ) - if checkVolumeSizeLimitsError != nil { - return "", false, checkVolumeSizeLimitsError + shouldLimitFlexvolSize, flexvolSizeLimit, checkFlexvolSizeLimitsError := CheckVolumePoolSizeLimits( + ctx, sizeBytes, config) + if checkFlexvolSizeLimitsError != nil { + return "", false, checkFlexvolSizeLimitsError } // Check if a suitable Flexvol already exists - flexvol, err := d.getFlexvolForLUN(ctx, volAttrs, sizeBytes, shouldLimitVolumeSize, flexvolSizeLimit, ignoredVols) + flexvol, err := d.getFlexvolForLUN(ctx, volAttrs, sizeBytes, shouldLimitFlexvolSize, flexvolSizeLimit, ignoredVols) if err != nil { return "", false, fmt.Errorf("error finding Flexvol for LUN: %v", err) } @@ -2085,48 +2112,40 @@ func (d *SANEconomyStorageDriver) Resize(ctx context.Context, volConfig *storage return fmt.Errorf("requested size %d is less than existing volume size %d", flexvolSize, totalLunSize) } + // Enforce aggregate usage limit if aggrLimitsErr := checkAggregateLimitsForFlexvol( ctx, bucketVol, flexvolSize, d.Config, d.GetAPI(), ); aggrLimitsErr != nil { return aggrLimitsErr } + // Enforce volume size limit if _, _, checkVolumeSizeLimitsError := drivers.CheckVolumeSizeLimits( - ctx, flexvolSize, d.Config.CommonStorageDriverConfig, + ctx, sizeBytes, d.Config.CommonStorageDriverConfig, ); checkVolumeSizeLimitsError != nil { return checkVolumeSizeLimitsError } + // Enforce Flexvol pool size limit + shouldLimitFlexvolSize, flexvolSizeLimit, checkFlexvolSizeLimitsError := CheckVolumePoolSizeLimits( + ctx, sizeBytes, &d.Config) + if checkFlexvolSizeLimitsError != nil { + return checkFlexvolSizeLimitsError + } + if shouldLimitFlexvolSize && flexvolSize > flexvolSizeLimit { + return errors.UnsupportedCapacityRangeError(fmt.Errorf( + "requested size %d would exceed the pool size limit %d", sizeBytes, flexvolSizeLimit)) + } + // Resize operations - lunPath := d.helper.GetLUNPath(bucketVol, name) - if !d.API.SupportsFeature(ctx, api.LunGeometrySkip) { - // Check LUN geometry and verify LUN max size. - lunMaxSize, err := d.API.LunGetGeometry(ctx, lunPath) - if err != nil { - Logc(ctx).WithField("error", err).Error("LUN resize failed.") - return fmt.Errorf("volume resize failed") - } - if lunMaxSize < sizeBytes { - Logc(ctx).WithFields( - LogFields{ - "error": err, - "sizeBytes": sizeBytes, - "lunMaxSize": lunMaxSize, - "lunPath": lunPath, - }, - ).Error("Requested size is larger than LUN's maximum capacity.") - return errors.UnsupportedCapacityRangeError(fmt.Errorf( - "volume resize failed as requested size is larger than LUN's maximum capacity")) - } - } + lunPath := d.helper.GetLUNPath(bucketVol, name) // Resize FlexVol err = d.API.VolumeSetSize(ctx, bucketVol, strconv.FormatUint(flexvolSize, 10)) if err != nil { Logc(ctx).WithField("error", err).Error("Volume resize failed.") return fmt.Errorf("volume resize failed") - } // Resize LUN diff --git a/storage_drivers/ontap/ontap_san_economy_test.go b/storage_drivers/ontap/ontap_san_economy_test.go index 01249701f..0c4dc3817 100644 --- a/storage_drivers/ontap/ontap_san_economy_test.go +++ b/storage_drivers/ontap/ontap_san_economy_test.go @@ -25,6 +25,7 @@ import ( "github.com/netapp/trident/storage_drivers/ontap/api" "github.com/netapp/trident/storage_drivers/ontap/awsapi" "github.com/netapp/trident/utils" + utilserrors "github.com/netapp/trident/utils/errors" ) var failed = errors.New("failed") @@ -613,6 +614,7 @@ func TestOntapSanEconomyVolumeCreate(t *testing.T) { FileSystem: "xfs", } volAttrs := map[string]sa.Request{} + d.Config.LimitVolumeSize = "2g" mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{}, nil) mockAPI.EXPECT().VolumeListByAttrs(ctx, gomock.Any()).Times(1).Return(api.Volumes{}, nil) @@ -729,6 +731,175 @@ func TestOntapSanEconomyVolumeCreate_InvalidSize(t *testing.T) { } } +func TestOntapSanEconomyVolumeCreate_OverSizeLimit(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + pool1 := storage.NewStoragePool(nil, "pool1") + pool1.InternalAttributes()[TieringPolicy] = "none" + d.physicalPools = map[string]storage.Pool{"pool1": pool1} + volAttrs := map[string]sa.Request{} + + tests := []struct { + limitVolumeSize string + }{ + {"invalid"}, + {"1GiB"}, + } + for _, test := range tests { + t.Run(test.limitVolumeSize, func(t *testing.T) { + volConfig := &storage.VolumeConfig{ + Size: "2GiB", + Encryption: "false", + FileSystem: "xfs", + } + d.Config.LimitVolumeSize = test.limitVolumeSize + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(api.Luns{}, nil) + + result := d.Create(ctx, volConfig, pool1, volAttrs) + + assert.Error(t, result) + }) + } +} + +func TestOntapSanEconomyVolumeCreate_OverPoolSizeLimit_CreateNewFlexvol(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + pool1 := storage.NewStoragePool(nil, "pool1") + pool1.SetInternalAttributes(map[string]string{ + SpaceReserve: "none", + SnapshotPolicy: "fake-snap-policy", + SnapshotReserve: "10", + UnixPermissions: "0755", + SnapshotDir: "true", + ExportPolicy: "fake-export-policy", + SecurityStyle: "mixed", + Encryption: "false", + TieringPolicy: "none", + QosPolicy: "fake-qos-policy", + AdaptiveQosPolicy: "", + LUKSEncryption: "false", + }) + d.physicalPools = map[string]storage.Pool{"pool1": pool1} + existingLUN := api.Lun{Size: "1073741824"} + existingFlexvol := &api.Volume{ + Size: "1g", + Name: "flexvol1", + } + newFlexvol := &api.Volume{ + Size: "1g", + Name: "flexvol2", + } + volConfig := &storage.VolumeConfig{ + Size: "2g", + Encryption: "false", + FileSystem: "xfs", + } + + volAttrs := map[string]sa.Request{} + d.Config.LimitVolumePoolSize = "2g" + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{}, nil) + mockAPI.EXPECT().VolumeListByAttrs(ctx, gomock.Any()).Times(1).Return([]*api.Volume{existingFlexvol}, nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(existingFlexvol, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{existingLUN}, nil) + mockAPI.EXPECT().VolumeCreate(ctx, gomock.Any()).Times(1).Return(nil) + mockAPI.EXPECT().VolumeModifySnapshotDirectoryAccess(ctx, gomock.Any(), false).Times(1).Return(nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(newFlexvol, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{existingLUN}, nil) + mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Times(1).Return(nil) + mockAPI.EXPECT().LunCreate(ctx, gomock.Any()).Times(1).Return(nil) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Times(1).Return(&api.Lun{Size: "1073741824"}, nil) + mockAPI.EXPECT().LunSetAttribute(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), + gomock.Any()).Times(1).Return(nil) + + result := d.Create(ctx, volConfig, pool1, volAttrs) + + assert.NoError(t, result) + assert.Equal(t, "none", volConfig.SpaceReserve) + assert.Equal(t, "fake-snap-policy", volConfig.SnapshotPolicy) + assert.Equal(t, "10", volConfig.SnapshotReserve) + assert.Equal(t, "0755", volConfig.UnixPermissions) + assert.Equal(t, "false", volConfig.SnapshotDir) + assert.Equal(t, "fake-export-policy", volConfig.ExportPolicy) + assert.Equal(t, "mixed", volConfig.SecurityStyle) + assert.Equal(t, "false", volConfig.Encryption) + assert.Equal(t, "fake-qos-policy", volConfig.QosPolicy) + assert.Equal(t, "", volConfig.AdaptiveQosPolicy) + assert.Equal(t, "false", volConfig.LUKSEncryption) + assert.Equal(t, "xfs", volConfig.FileSystem) +} + +func TestOntapSanEconomyVolumeCreate_NotOverPoolSizeLimit_UseExistingFlexvol(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) + pool1 := storage.NewStoragePool(nil, "pool1") + pool1.SetInternalAttributes(map[string]string{ + SpaceReserve: "none", + SnapshotPolicy: "fake-snap-policy", + SnapshotReserve: "10", + UnixPermissions: "0755", + SnapshotDir: "true", + ExportPolicy: "fake-export-policy", + SecurityStyle: "mixed", + Encryption: "false", + TieringPolicy: "none", + QosPolicy: "fake-qos-policy", + AdaptiveQosPolicy: "", + LUKSEncryption: "false", + }) + d.physicalPools = map[string]storage.Pool{"pool1": pool1} + d.lunsPerFlexvol = 100 + existingLUN := api.Lun{ + Size: "1073741824", + Name: "lun1", + } + existingFlexvol := &api.Volume{ + Size: "1g", + Name: "flexvol1", + } + newFlexvol := &api.Volume{ + Size: "1g", + Name: "flexvol2", + } + volConfig := &storage.VolumeConfig{ + Size: "2g", + Encryption: "false", + FileSystem: "xfs", + } + + volAttrs := map[string]sa.Request{} + d.Config.LimitVolumePoolSize = "5g" + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{}, nil) + mockAPI.EXPECT().VolumeListByAttrs(ctx, gomock.Any()).Times(1).Return([]*api.Volume{existingFlexvol}, nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(existingFlexvol, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{existingLUN}, nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(newFlexvol, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{existingLUN}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{existingLUN}, nil) + mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Times(1).Return(nil) + mockAPI.EXPECT().LunCreate(ctx, gomock.Any()).Times(1).Return(nil) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Times(1).Return(&api.Lun{Size: "1073741824"}, nil) + mockAPI.EXPECT().LunSetAttribute(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), + gomock.Any()).Times(1).Return(nil) + + result := d.Create(ctx, volConfig, pool1, volAttrs) + + assert.NoError(t, result) + assert.Equal(t, "none", volConfig.SpaceReserve) + assert.Equal(t, "fake-snap-policy", volConfig.SnapshotPolicy) + assert.Equal(t, "10", volConfig.SnapshotReserve) + assert.Equal(t, "0755", volConfig.UnixPermissions) + assert.Equal(t, "false", volConfig.SnapshotDir) + assert.Equal(t, "fake-export-policy", volConfig.ExportPolicy) + assert.Equal(t, "mixed", volConfig.SecurityStyle) + assert.Equal(t, "false", volConfig.Encryption) + assert.Equal(t, "fake-qos-policy", volConfig.QosPolicy) + assert.Equal(t, "", volConfig.AdaptiveQosPolicy) + assert.Equal(t, "false", volConfig.LUKSEncryption) + assert.Equal(t, "xfs", volConfig.FileSystem) +} + func TestOntapSanEconomyVolumeCreate_LUNNameLimitExceeding(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) pool1 := storage.NewStoragePool(nil, "pool1") @@ -1214,24 +1385,154 @@ func TestOntapSanEconomyVolumeClone(t *testing.T) { pool1.InternalAttributes()[TieringPolicy] = "none" d.physicalPools = map[string]storage.Pool{"pool1": pool1} volConfig := &storage.VolumeConfig{ - Size: "1g", - Encryption: "false", - FileSystem: "xfs", + CloneSourceVolumeInternal: "storagePrefix_lunName", + Size: "1g", + Encryption: "false", + FileSystem: "xfs", + } + sourceLun := api.Lun{ + Size: "1073741824", + Name: "/vol/volumeName/storagePrefix_lunName", + VolumeName: "volumeName", } mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) - mockAPI.EXPECT().LunList(ctx, - gomock.Any()).Times(1).Return(api.Luns{api.Lun{Size: "1g", Name: "lunName", VolumeName: "volumeName"}}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{sourceLun}, nil) + mockAPI.EXPECT().LunGetByName(ctx, sourceLun.Name).Return(&sourceLun, nil) mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Times(1).Return(nil) - result := d.CreateClone(ctx, volConfig, volConfig, pool1) + result := d.CreateClone(ctx, nil, volConfig, pool1) + + assert.Nil(t, result) +} + +func TestOntapSanEconomyVolumeClone_getSizeFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + pool1 := storage.NewStoragePool(nil, "pool1") + pool1.InternalAttributes()[TieringPolicy] = "none" + d.physicalPools = map[string]storage.Pool{"pool1": pool1} + volConfig := &storage.VolumeConfig{ + CloneSourceVolumeInternal: "storagePrefix_lunName", + Size: "1g", + Encryption: "false", + FileSystem: "xfs", + } + sourceLun := api.Lun{ + Size: "1073741824", + Name: "/vol/volumeName/storagePrefix_lunName", + VolumeName: "volumeName", + } + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{sourceLun}, nil) + mockAPI.EXPECT().LunGetByName(ctx, sourceLun.Name).Return(nil, failed) + + result := d.CreateClone(ctx, nil, volConfig, pool1) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeClone_OverPoolSizeLimit_CheckFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + pool1 := storage.NewStoragePool(nil, "pool1") + pool1.InternalAttributes()[TieringPolicy] = "none" + d.physicalPools = map[string]storage.Pool{"pool1": pool1} + volConfig := &storage.VolumeConfig{ + CloneSourceVolumeInternal: "storagePrefix_lunName", + Size: "1g", + Encryption: "false", + FileSystem: "xfs", + } + sourceLun := api.Lun{ + Size: "1073741824", + Name: "/vol/volumeName/storagePrefix_lunName", + VolumeName: "volumeName", + } + d.Config.LimitVolumePoolSize = "1500MiB" + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{sourceLun}, nil) + mockAPI.EXPECT().LunGetByName(ctx, sourceLun.Name).Return(&sourceLun, nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{Size: "1073741824"}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{sourceLun}, nil) + + result := d.CreateClone(ctx, nil, volConfig, pool1) + + assert.Error(t, result) + ok, _ := utilserrors.HasUnsupportedCapacityRangeError(result) + assert.True(t, ok) +} + +func TestOntapSanEconomyVolumeClone_OverPoolSizeLimit_CheckPassed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) + pool1 := storage.NewStoragePool(nil, "pool1") + pool1.InternalAttributes()[TieringPolicy] = "none" + d.physicalPools = map[string]storage.Pool{"pool1": pool1} + volConfig := &storage.VolumeConfig{ + CloneSourceVolumeInternal: "storagePrefix_lunName", + Size: "1g", + Encryption: "false", + FileSystem: "xfs", + } + sourceLun := api.Lun{ + Size: "1073741824", + Name: "/vol/volumeName/storagePrefix_lunName", + VolumeName: "volumeName", + } + cloneLun := api.Lun{ + Size: "1073741824", + Name: "/vol/volumeName/storagePrefix_lunName2", + VolumeName: "volumeName", + } + d.Config.LimitVolumePoolSize = "3g" + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{sourceLun}, nil) + mockAPI.EXPECT().LunGetByName(ctx, sourceLun.Name).Return(&sourceLun, nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{Size: "1073741824"}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{sourceLun}, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{Size: "1073741824"}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return([]api.Lun{sourceLun, cloneLun}, nil) + mockAPI.EXPECT().VolumeSetSize(ctx, "volumeName", "2147483648").Times(1).Return(nil) + + result := d.CreateClone(ctx, nil, volConfig, pool1) assert.Nil(t, result) } +func TestOntapSanEconomyVolumeClone_OverPoolSizeLimitCheck_VolumeInfoFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + pool1 := storage.NewStoragePool(nil, "pool1") + pool1.InternalAttributes()[TieringPolicy] = "none" + d.physicalPools = map[string]storage.Pool{"pool1": pool1} + volConfig := &storage.VolumeConfig{ + CloneSourceVolumeInternal: "storagePrefix_lunName", + Size: "1g", + Encryption: "false", + FileSystem: "xfs", + } + sourceLun := api.Lun{ + Size: "1073741824", + Name: "/vol/volumeName/storagePrefix_lunName", + VolumeName: "volumeName", + } + d.Config.LimitVolumePoolSize = "1500MiB" + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{sourceLun}, nil) + mockAPI.EXPECT().LunGetByName(ctx, sourceLun.Name).Return(&sourceLun, nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(nil, failed) + + result := d.CreateClone(ctx, nil, volConfig, pool1) + + assert.Error(t, result) +} + func TestOntapSanEconomyVolumeClone_BothQosPolicy(t *testing.T) { _, d := newMockOntapSanEcoDriver(t) pool1 := storage.NewStoragePool(nil, "pool1") @@ -2246,54 +2547,47 @@ func TestOntapSanEconomyGetSnapshots_LUNDoesNotExist(t *testing.T) { } } -func TestOntapSanEconomyCreateSnapshot(t *testing.T) { - mockAPI, d := newMockOntapSanEcoDriver(t) - d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) - d.flexvolNamePrefix = "storagePrefix" +func getStructsForSnapshotCreate() (string, *storage.SnapshotConfig, string, api.Lun, api.Lun) { + storagePrefix := "storagePrefix" + snapConfig := &storage.SnapshotConfig{ - InternalName: "snap_1", - VolumeName: "my_Bucket", - Name: "/vol/my_Bucket/storagePrefix_my_Lun", - VolumeInternalName: "storagePrefix_my_Lun_my_Bucket", + InternalName: "snapshot-00c6b55c-afeb-4657-b86e-156d66c0c9fc", + VolumeName: "pvc-ff297a18-921a-4435-b679-c3fea351f92c", + Name: "snapshot-00c6b55c-afeb-4657-b86e-156d66c0c9fc", + VolumeInternalName: "storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c", } - mockAPI.EXPECT().LunList(ctx, - gomock.Any()).Times(2).Return(api.Luns{ - api.Lun{ - Size: "1073741824", - Name: "/vol/my_Bucket/storagePrefix_my_Lun_my_Bucket", - VolumeName: "my_Bucket", - }, - }, - nil) - mockAPI.EXPECT().LunGetByName(ctx, - gomock.Any()).Times(1).Return(&api.Lun{ + bucketVol := "trident_lun_pool_storagePrefix_MGURDMZTKA" + + sourceLun := api.Lun{ Size: "1073741824", - Name: "/vol/my_Bucket/storagePrefix_my_Lun_my_Bucket", - VolumeName: "my_Bucket", - }, - nil) - mockAPI.EXPECT().LunList(ctx, - gomock.Any()).Times(2).Return(api.Luns{ - api.Lun{ - Size: "1073741824", - Name: "/vol/my_Bucket/storagePrefix_my_Lun_my_Bucket", - VolumeName: "my_Bucket", - }, - }, - nil) - mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil) + Name: "/vol/trident_lun_pool_storagePrefix_MGURDMZTKA/storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c", + VolumeName: "trident_lun_pool_storagePrefix_MGURDMZTKA", + } + snapLun := api.Lun{ + Size: "1073741824", + Name: "/vol/trident_lun_pool_storagePrefix_MGURDMZTKA/storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c_snapshot_snapshot_00c6b55c_afeb_4657_b86e_156d66c0c9fc", + VolumeName: "trident_lun_pool_storagePrefix_MGURDMZTKA", + } + return storagePrefix, snapConfig, bucketVol, sourceLun, snapLun +} + +func TestOntapSanEconomyCreateSnapshot(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) + + storagePrefix, snapConfig, _, sourceLun, snapLun := getStructsForSnapshotCreate() + d.flexvolNamePrefix = storagePrefix + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(1) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Times(1).Return(&sourceLun, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(nil, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(1) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&sourceLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), api.QosPolicyGroup{}).Return(nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Times(1).Return(nil) - mockAPI.EXPECT().LunList(ctx, - gomock.Any()).Times(1).Return(api.Luns{ - api.Lun{ - Size: "1073741824", - Name: "/vol/my_Bucket/storagePrefix_my_Lun_snapshot_snap_1", - VolumeName: "my_Bucket", - }, - }, - nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{snapLun}, nil) snap, err := d.CreateSnapshot(ctx, snapConfig, nil) @@ -2304,13 +2598,9 @@ func TestOntapSanEconomyCreateSnapshot(t *testing.T) { func TestOntapSanEconomyCreateSnapshot_LUNDoesNotExist(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) - d.flexvolNamePrefix = "storagePrefix" - snapConfig := &storage.SnapshotConfig{ - InternalName: "snap_1", - VolumeName: "my_Bucket", - Name: "/vol/my_Bucket/storagePrefix_my_Lun", - VolumeInternalName: "storagePrefix_my_Lun_my_Bucket", - } + + storagePrefix, snapConfig, _, _, _ := getStructsForSnapshotCreate() + d.flexvolNamePrefix = storagePrefix tests := []struct { message string @@ -2338,22 +2628,15 @@ func TestOntapSanEconomyCreateSnapshot_LUNDoesNotExist(t *testing.T) { func TestOntapSanEconomyCreateSnapshot_LUNCreateCloneFailed(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) - d.flexvolNamePrefix = "storagePrefix" - snapConfig := &storage.SnapshotConfig{ - InternalName: "snap_1", - VolumeName: "my_Bucket", - Name: "vol1_snapshot_snap_1", - VolumeInternalName: "vol1_snapshot_snap_1", - } - lun := api.Lun{ - Size: "1073741824", Name: "vol/my_Bucket/storagePrefix_vol1_snapshot_snap_1", - VolumeName: "volumeName", - } - luns := []api.Lun{lun} - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil) - mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Times(1).Return(&lun, nil) - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil).Times(2) + storagePrefix, snapConfig, _, sourceLun, _ := getStructsForSnapshotCreate() + d.flexvolNamePrefix = storagePrefix + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(1) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Times(1).Return(&sourceLun, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(nil, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(1) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&sourceLun, nil) mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), api.QosPolicyGroup{}).Return(fmt.Errorf("failed to create lun clone")) @@ -2366,18 +2649,11 @@ func TestOntapSanEconomyCreateSnapshot_LUNCreateCloneFailed(t *testing.T) { func TestOntapSanEconomyCreateSnapshot_LUNGetByNameFailed(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) - d.flexvolNamePrefix = "storagePrefix" - snapConfig := &storage.SnapshotConfig{ - InternalName: "snap_1", - VolumeName: "my_Bucket", - Name: "/vol/my_Bucket/storagePrefix_my_Lun", - VolumeInternalName: "vol1", - } - luns := []api.Lun{ - {Size: "1073741824", Name: "lun_storagePrefix_vol1", VolumeName: "volumeName"}, - } - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil) + storagePrefix, snapConfig, _, sourceLun, _ := getStructsForSnapshotCreate() + d.flexvolNamePrefix = storagePrefix + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(1) mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(nil, fmt.Errorf("failed to fetch lun")) snap, err := d.CreateSnapshot(ctx, snapConfig, nil) @@ -2390,8 +2666,10 @@ func TestOntapSanEconomyCreateSnapshot_CreateLUNClone_LUNDoesNotExist(t *testing mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) d.flexvolNamePrefix = "storagePrefix" - luns := []api.Lun{ - {Size: "1073741824", Name: "lun_storagePrefix_vol1", VolumeName: "volumeName"}, + sourceLun := api.Lun{ + Size: "1073741824", + Name: "/vol/trident_lun_pool_storagePrefix_MGURDMZTKA/storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c", + VolumeName: "trident_lun_pool_storagePrefix_MGURDMZTKA", } d.Config.CommonStorageDriverConfig.DebugTraceFlags["method"] = true policy := api.QosPolicyGroup{} @@ -2408,11 +2686,11 @@ func TestOntapSanEconomyCreateSnapshot_CreateLUNClone_LUNDoesNotExist(t *testing if test.expectError { mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(nil, fmt.Errorf(test.message)) } else { - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil) } - result := d.createLUNClone(ctx, "lun_storagePrefix_vol1", "source", "snap", &d.Config, mockAPI, - "storagePrefix", true, policy) + result := d.createLUNClone(ctx, "storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c", + "source", "snap", &d.Config, mockAPI, "storagePrefix", true, policy) assert.Error(t, result) }) @@ -2454,19 +2732,24 @@ func TestOntapSanEconomyCreateSnapshot_CreateLUNClone_LUNCloneCreateFailed(t *te mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) d.flexvolNamePrefix = "storagePrefix" - luns := []api.Lun{ - {Size: "1073741824", Name: "lun_storagePrefix_vol1_snapshot_snap", VolumeName: "volumeName"}, + sourceLun := api.Lun{ + Size: "1073741824", + Name: "/vol/trident_lun_pool_storagePrefix_MGURDMZTKA/storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c_snapshot_snapshot_00c6b55c_afeb_4657_b86e_156d66c0c9fc", + VolumeName: "trident_lun_pool_storagePrefix_MGURDMZTKA", } d.Config.CommonStorageDriverConfig.DebugTraceFlags["method"] = true policy := api.QosPolicyGroup{} mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(nil, nil) - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Times(1).Return(&sourceLun, nil) mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), api.QosPolicyGroup{}).Return(fmt.Errorf("failed to create lun clone")) - result := d.createLUNClone(ctx, "lun_storagePrefix_vol1_snapshot_snap", "vol1", "snap", &d.Config, mockAPI, - "storagePrefix", true, policy) + result := d.createLUNClone(ctx, + "storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c_snapshot_snapshot_e753e1a5-e57b-4f94-adbc-22f7c05e2121", + "storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c", "snapshot-00c6b55c-afeb-4657-b86e-156d66c0c9fc", + &d.Config, mockAPI, "storagePrefix", true, policy) assert.Error(t, result) } @@ -2474,21 +2757,15 @@ func TestOntapSanEconomyCreateSnapshot_CreateLUNClone_LUNCloneCreateFailed(t *te func TestOntapSanEconomyCreateSnapshot_GetSnapshotsEconomyFailed(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) - d.flexvolNamePrefix = "storagePrefix" - snapConfig := &storage.SnapshotConfig{ - InternalName: "snap_1", - VolumeName: "my_Bucket", - Name: "/vol/my_Bucket/storagePrefix_my_Lun", - VolumeInternalName: "vol1", - } - lun := api.Lun{ - Size: "1073741824", Name: "lun_storagePrefix_vol1", - VolumeName: "volumeName", - } - luns := []api.Lun{lun} - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil).Times(3) - mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) + storagePrefix, snapConfig, _, sourceLun, _ := getStructsForSnapshotCreate() + d.flexvolNamePrefix = storagePrefix + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(1) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Times(1).Return(&sourceLun, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(nil, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(1) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&sourceLun, nil) mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), api.QosPolicyGroup{}).Return(nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Times(1).Return(nil) @@ -2503,30 +2780,13 @@ func TestOntapSanEconomyCreateSnapshot_GetSnapshotsEconomyFailed(t *testing.T) { func TestOntapSanEconomyCreateSnapshot_InvalidSize(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) - d.flexvolNamePrefix = "storagePrefix" - snapConfig := &storage.SnapshotConfig{ - InternalName: "snap_1", - VolumeName: "my_Bucket", - Name: "/vol/my_Bucket/storagePrefix_my_Lun", - VolumeInternalName: "vol1", - } - lun := api.Lun{ - Size: "invalid", Name: "lun_storagePrefix_vol1", - VolumeName: "volumeName", - } - luns := []api.Lun{ - {Size: "1073741824", Name: "lun_storagePrefix_vol1", VolumeName: "volumeName"}, - } - snapLuns := []api.Lun{ - {Size: "1073741824", Name: "/vol/volumeName/storagePrefix_vol1_snapshot_mySnap", VolumeName: "volumeName"}, - } - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil).Times(4) - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(snapLuns, nil) - mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) - mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), api.QosPolicyGroup{}).Return(nil) - mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) - mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Times(1).Return(nil) + storagePrefix, snapConfig, _, sourceLun, snapLun := getStructsForSnapshotCreate() + d.flexvolNamePrefix = storagePrefix + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(2) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(nil, nil) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&snapLun, nil) snap, err := d.CreateSnapshot(ctx, snapConfig, nil) @@ -2537,24 +2797,14 @@ func TestOntapSanEconomyCreateSnapshot_InvalidSize(t *testing.T) { func TestOntapSanEconomyCreateSnapshot_SnapshotNotFound(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) - d.flexvolNamePrefix = "storagePrefix" - snapConfig := &storage.SnapshotConfig{ - InternalName: "snap_1", - VolumeName: "my_Bucket", - Name: "/vol/my_Bucket/storagePrefix_my_Lun", - VolumeInternalName: "vol1", - } - lun := api.Lun{ - Size: "invalid", Name: "lun_storagePrefix_vol1", - VolumeName: "volumeName", - } - luns := []api.Lun{ - {Size: "1073741824", Name: "lun_storagePrefix_vol1", VolumeName: "volumeName"}, - } - mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil).Times(4) + storagePrefix, snapConfig, _, sourceLun, snapLun := getStructsForSnapshotCreate() + d.flexvolNamePrefix = storagePrefix + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return([]api.Lun{sourceLun}, nil).Times(4) mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(nil, nil) - mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&snapLun, nil) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&sourceLun, nil) mockAPI.EXPECT().LunCloneCreate(ctx, gomock.Any(), gomock.Any(), gomock.Any(), api.QosPolicyGroup{}).Return(nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Times(1).Return(nil) @@ -2994,9 +3244,9 @@ func TestOntapSanEconomyEnsureFlexvolForLUN_InvalidVolumeSize(t *testing.T) { opts := make(map[string]string) pool1 := storage.NewStoragePool(nil, "pool1") d.physicalPools = map[string]storage.Pool{"pool1": pool1} - d.Config.LimitVolumeSize = "invalid" + d.Config.LimitVolumePoolSize = "invalid" - flexVol, newly, err := d.ensureFlexvolForLUN(ctx, &api.Volume{}, uint64(1073741824), opts, d.Config, pool1, + flexVol, newly, err := d.ensureFlexvolForLUN(ctx, &api.Volume{}, uint64(1073741824), opts, &d.Config, pool1, make(map[string]struct{})) assert.Equal(t, flexVol, "") @@ -3021,7 +3271,7 @@ func TestOntapSanEconomyEnsureFlexvolForLUN_FlexvolFound(t *testing.T) { mockAPI.EXPECT().VolumeListByAttrs(ctx, gomock.Any()).Return(api.Volumes{vol}, nil) mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil) - flexVol, newly, err := d.ensureFlexvolForLUN(ctx, vol, uint64(1073741824), opts, d.Config, pool1, + flexVol, newly, err := d.ensureFlexvolForLUN(ctx, vol, uint64(1073741824), opts, &d.Config, pool1, make(map[string]struct{})) assert.NotEqual(t, flexVol, "") @@ -3048,7 +3298,7 @@ func TestOntapSanEconomyEnsureFlexvolForLUN_FlexvolNotFound(t *testing.T) { mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil) mockAPI.EXPECT().VolumeCreate(ctx, gomock.Any()).Return(fmt.Errorf("failed to create volume")) - flexVol, newly, err := d.ensureFlexvolForLUN(ctx, vol, uint64(1073741824), opts, d.Config, pool1, + flexVol, newly, err := d.ensureFlexvolForLUN(ctx, vol, uint64(1073741824), opts, &d.Config, pool1, make(map[string]struct{})) assert.Equal(t, flexVol, "") @@ -3433,8 +3683,6 @@ func TestOntapSanEconomyVolumeResize(t *testing.T) { }, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{Aggregates: []string{"data"}}, nil) - mockAPI.EXPECT().SupportsFeature(ctx, api.LunGeometrySkip).Times(1) - mockAPI.EXPECT().LunGetGeometry(ctx, gomock.Any()).Times(1).Return(uint64(2147483648), nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Times(1).Return(nil) mockAPI.EXPECT().LunSetSize(ctx, gomock.Any(), "2147483648").Return(uint64(2147483648), nil) @@ -3582,7 +3830,7 @@ func TestOntapSanEconomyVolumeResize_SizeError(t *testing.T) { } } -func TestOntapSanEconomyVolumeResize_LimitVolumeSize(t *testing.T) { +func TestOntapSanEconomyVolumeResize_WithInvalidVolumeSizeLimit(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) d.Config.LimitVolumeSize = "invalid-value" // invalid int value @@ -3606,9 +3854,10 @@ func TestOntapSanEconomyVolumeResize_LimitVolumeSize(t *testing.T) { assert.Error(t, result) } -func TestOntapSanEconomyVolumeResize_LUNGetGeometryFailed(t *testing.T) { +func TestOntapSanEconomyVolumeResize_OverSizeLimit(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) + d.Config.LimitVolumeSize = "2GB" volConfig := &storage.VolumeConfig{ Size: "1073741824", Encryption: "false", @@ -3622,41 +3871,78 @@ func TestOntapSanEconomyVolumeResize_LUNGetGeometryFailed(t *testing.T) { mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil).Times(3) mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) - mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) - mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(&api.Volume{Aggregates: []string{"aggr"}}, nil) - mockAPI.EXPECT().SupportsFeature(ctx, api.LunGeometrySkip).Times(1) - mockAPI.EXPECT().LunGetGeometry(ctx, gomock.Any()).Times(1).Return(uint64(2147483648), - fmt.Errorf("failed to get lun geometry")) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(&api.Volume{Aggregates: []string{"aggr"}}, nil).Times(2) result := d.Resize(ctx, volConfig, uint64(2147483648)) assert.Error(t, result) } -func TestOntapSanEconomyVolumeResize_LUNMaxSizeLess(t *testing.T) { +func TestOntapSanEconomyVolumeResize_OverPoolSizeLimit_OneLUN(t *testing.T) { mockAPI, d := newMockOntapSanEcoDriver(t) d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) + d.Config.LimitVolumePoolSize = "2g" volConfig := &storage.VolumeConfig{ Size: "1073741824", Encryption: "false", FileSystem: "xfs", } lun := api.Lun{ - Size: "1073741824", Name: "lun_vol1", + Size: "1073741824", + Name: "lun_vol1", VolumeName: "volumeName", } + flexvol := &api.Volume{ + Aggregates: []string{"aggr"}, + Size: "1073741824", + } luns := []api.Lun{lun} mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil).Times(3) mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) - mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) - mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(&api.Volume{Aggregates: []string{"aggr"}}, nil) - mockAPI.EXPECT().SupportsFeature(ctx, api.LunGeometrySkip).Times(1) - mockAPI.EXPECT().LunGetGeometry(ctx, gomock.Any()).Times(1).Return(uint64(1073741824), nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(flexvol, nil).Times(2) - result := d.Resize(ctx, volConfig, uint64(2147483648)) + result := d.Resize(ctx, volConfig, uint64(3221225472)) + + assert.Error(t, result) + ok, _ := utilserrors.HasUnsupportedCapacityRangeError(result) + assert.True(t, ok) +} + +func TestOntapSanEconomyVolumeResize_OverPoolSizeLimit_MultipleLUNs(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + d.helper = NewTestLUNHelper("storagePrefix_", tridentconfig.ContextCSI) + d.Config.LimitVolumePoolSize = "5g" + volConfig := &storage.VolumeConfig{ + Size: "1073741824", + Encryption: "false", + FileSystem: "xfs", + } + lun1 := api.Lun{ + Size: "1073741824", + Name: "lun1", + VolumeName: "volumeName", + } + lun2 := api.Lun{ + Size: "3221225472", + Name: "lun2", + VolumeName: "volumeName", + } + flexvol := &api.Volume{ + Aggregates: []string{"aggr"}, + Size: "1073741824", + } + luns := []api.Lun{lun1, lun2} + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Return(luns, nil).Times(3) + mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun1, nil) + mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(flexvol, nil).Times(2) + + result := d.Resize(ctx, volConfig, uint64(3221225472)) assert.Error(t, result) + ok, _ := utilserrors.HasUnsupportedCapacityRangeError(result) + assert.True(t, ok) } func TestOntapSanEconomyVolumeResize_VolumeSetSizeFailed(t *testing.T) { @@ -3677,8 +3963,6 @@ func TestOntapSanEconomyVolumeResize_VolumeSetSizeFailed(t *testing.T) { mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(&api.Volume{Aggregates: []string{"aggr"}}, nil) - mockAPI.EXPECT().SupportsFeature(ctx, api.LunGeometrySkip).Times(1) - mockAPI.EXPECT().LunGetGeometry(ctx, gomock.Any()).Times(1).Return(uint64(2147483648), nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Return(fmt.Errorf("failed to set volume size")) result := d.Resize(ctx, volConfig, uint64(2147483648)) @@ -3704,8 +3988,6 @@ func TestOntapSanEconomyVolumeResize_LunSetSizeFailed(t *testing.T) { mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(&api.Volume{Aggregates: []string{"aggr"}}, nil) - mockAPI.EXPECT().SupportsFeature(ctx, api.LunGeometrySkip).Times(1) - mockAPI.EXPECT().LunGetGeometry(ctx, gomock.Any()).Times(1).Return(uint64(2147483648), nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Return(nil) mockAPI.EXPECT().LunSetSize(ctx, gomock.Any(), "2147483648").Return(uint64(2147483648), fmt.Errorf("failed to set lun size")) @@ -3733,8 +4015,6 @@ func TestOntapSanEconomyVolumeResize_FlexvolBiggerThanLUN(t *testing.T) { mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(&api.Volume{Aggregates: []string{"aggr"}}, nil).Times(2) - mockAPI.EXPECT().SupportsFeature(ctx, api.LunGeometrySkip).Times(1) - mockAPI.EXPECT().LunGetGeometry(ctx, gomock.Any()).Times(1).Return(uint64(16106127360), nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Return(nil).Times(2) mockAPI.EXPECT().LunSetSize(ctx, gomock.Any(), gomock.Any()).Return(uint64(16106127360), nil) mockAPI.EXPECT().VolumeSize(ctx, gomock.Any()).Return(uint64(2147483648), nil) @@ -3762,8 +4042,6 @@ func TestOntapSanEconomyVolumeResize_VolumeSizeFailed(t *testing.T) { mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(&api.Volume{Aggregates: []string{"aggr"}}, nil).Times(2) - mockAPI.EXPECT().SupportsFeature(ctx, api.LunGeometrySkip).Times(1) - mockAPI.EXPECT().LunGetGeometry(ctx, gomock.Any()).Times(1).Return(uint64(16106127360), nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Return(nil).Times(2) mockAPI.EXPECT().LunSetSize(ctx, gomock.Any(), gomock.Any()).Return(uint64(16106127360), nil) mockAPI.EXPECT().VolumeSize(ctx, gomock.Any()).Return(uint64(2147483648), fmt.Errorf("failed to get volume size")) @@ -3791,8 +4069,6 @@ func TestOntapSanEconomyVolumeResize_VolumeSetSizeFailed2(t *testing.T) { mockAPI.EXPECT().LunGetByName(ctx, gomock.Any()).Return(&lun, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Times(1).Return(&api.Volume{}, nil) mockAPI.EXPECT().VolumeInfo(ctx, gomock.Any()).Return(&api.Volume{Aggregates: []string{"aggr"}}, nil).Times(2) - mockAPI.EXPECT().SupportsFeature(ctx, api.LunGeometrySkip).Times(1) - mockAPI.EXPECT().LunGetGeometry(ctx, gomock.Any()).Times(1).Return(uint64(16106127360), nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Return(nil) mockAPI.EXPECT().LunSetSize(ctx, gomock.Any(), gomock.Any()).Return(uint64(16106127360), nil) mockAPI.EXPECT().VolumeSetSize(ctx, gomock.Any(), gomock.Any()).Return(fmt.Errorf("failed to set volume size")) diff --git a/storage_drivers/types.go b/storage_drivers/types.go index f0c9e367c..188e32dfb 100644 --- a/storage_drivers/types.go +++ b/storage_drivers/types.go @@ -123,6 +123,7 @@ type OntapStorageDriverConfig struct { CloneSplitDelay string `json:"cloneSplitDelay,omitEmpty"` // in seconds, default to 10 NfsMountOptions string `json:"nfsMountOptions"` LimitAggregateUsage string `json:"limitAggregateUsage"` + LimitVolumePoolSize string `json:"limitVolumePoolSize"` AutoExportPolicy bool `json:"autoExportPolicy"` AutoExportCIDRs []string `json:"autoExportCIDRs"` OntapStorageDriverPool