Skip to content

Commit 57baa38

Browse files
committed
add e2e test
1 parent 8b5db5b commit 57baa38

File tree

6 files changed

+139
-243
lines changed

6 files changed

+139
-243
lines changed

docs/driver-parameters.md

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ storageEndpointSuffix | specify Azure storage endpoint suffix | `core.windows.ne
4141
tags | [tags](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources) would be created in newly created storage account | tag format: 'foo=aaa,bar=bbb' | No | ""
4242
matchTags | whether matching tags when driver tries to find a suitable storage account | `true`,`false` | No | `false`
4343
selectRandomMatchingAccount | whether randomly selecting a matching account, by default, the driver would always select the first matching account in alphabetical order | `true`,`false` | No | `false`
44+
accountQuota | to limit the quota for an account, you can specify a maximum quota in GB (`102400`GB by default). If the account exceeds the specified quota, the driver would skip selecting the account | `` | No | `102400`
4445
--- | **Following parameters are only for SMB protocol** | --- | --- |
4546
subscriptionID | specify Azure subscription ID where Azure file share will be created | Azure subscription ID | No | if not empty, `resourceGroup` must be provided
4647
storeAccountKey | whether store account key to k8s secret <br><br> Note: <br> `false` means driver would leverage kubelet identity to get account key | `true`,`false` | No | `true`

pkg/azurefile/azurefile.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const (
7373
// Minimum size of Azure Premium Files is 100GiB
7474
// See https://docs.microsoft.com/en-us/azure/storage/files/storage-files-planning#provisioned-shares
7575
defaultAzureFileQuota = 100
76+
minimumAccountQuota = 100 // GB
7677

7778
// key of snapshot name in metadata
7879
snapshotNameKey = "initiator"
@@ -135,6 +136,7 @@ const (
135136
enableMultichannelField = "enablemultichannel"
136137
premium = "premium"
137138
selectRandomMatchingAccountField = "selectrandommatchingaccount"
139+
accountQuotaField = "accountquota"
138140

139141
accountNotProvisioned = "StorageAccountIsNotProvisioned"
140142
// this is a workaround fix for 429 throttling issue, will update cloud provider for better fix later
@@ -870,7 +872,8 @@ func (d *Driver) ResizeFileShare(ctx context.Context, subsID, resourceGroup, acc
870872
})
871873
}
872874

873-
func (d *Driver) GetTotalQuotaGB(ctx context.Context, subsID, resourceGroup, accountName string) (int32, int32, error) {
875+
// GetTotalAccountQuota returns the total quota in GB of all file shares in the storage account and the number of file shares
876+
func (d *Driver) GetTotalAccountQuota(ctx context.Context, subsID, resourceGroup, accountName string) (int32, int32, error) {
874877
fileshares, err := d.cloud.FileClient.WithSubscriptionID(subsID).ListFileShare(ctx, resourceGroup, accountName, "", "")
875878
if err != nil {
876879
return -1, -1, err

pkg/azurefile/azurefile_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import (
2929

3030
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage"
3131
azure2 "github.com/Azure/go-autorest/autorest/azure"
32+
"github.com/Azure/go-autorest/autorest/to"
33+
"github.com/container-storage-interface/spec/lib/go/csi"
3234
"github.com/golang/mock/gomock"
3335
"github.com/stretchr/testify/assert"
3436
"k8s.io/client-go/kubernetes/fake"
@@ -63,6 +65,11 @@ func NewFakeDriver() *Driver {
6365
},
6466
},
6567
}
68+
driver.AddControllerServiceCapabilities(
69+
[]csi.ControllerServiceCapability_RPC_Type{
70+
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
71+
csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
72+
})
6673
return driver
6774
}
6875

@@ -78,6 +85,10 @@ func NewFakeDriverCustomOptions(opts DriverOptions) *Driver {
7885
},
7986
},
8087
}
88+
driver.AddControllerServiceCapabilities(
89+
[]csi.ControllerServiceCapability_RPC_Type{
90+
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
91+
})
8192
return driver
8293
}
8394

@@ -1274,3 +1285,68 @@ func TestGetSubnetResourceID(t *testing.T) {
12741285
t.Run(tc.name, tc.testFunc)
12751286
}
12761287
}
1288+
1289+
func TestGetTotalAccountQuota(t *testing.T) {
1290+
d := NewFakeDriver()
1291+
d.cloud = &azure.Cloud{}
1292+
1293+
ctrl := gomock.NewController(t)
1294+
defer ctrl.Finish()
1295+
1296+
fileShareItemsWithQuota := []storage.FileShareItem{
1297+
{
1298+
FileShareProperties: &storage.FileShareProperties{
1299+
ShareQuota: to.Int32Ptr(100),
1300+
},
1301+
},
1302+
{
1303+
FileShareProperties: &storage.FileShareProperties{
1304+
ShareQuota: to.Int32Ptr(200),
1305+
},
1306+
},
1307+
}
1308+
1309+
tests := []struct {
1310+
name string
1311+
subsID string
1312+
resourceGroup string
1313+
accountName string
1314+
fileShareItems []storage.FileShareItem
1315+
listFileShareErr error
1316+
expectedQuota int32
1317+
expectedShareNum int32
1318+
expectedErr error
1319+
}{
1320+
{
1321+
name: "GetTotalAccountQuota success",
1322+
expectedQuota: 0,
1323+
expectedShareNum: 0,
1324+
},
1325+
{
1326+
name: "GetTotalAccountQuota success (with 2 file shares)",
1327+
fileShareItems: fileShareItemsWithQuota,
1328+
expectedQuota: 300,
1329+
expectedShareNum: 2,
1330+
},
1331+
{
1332+
name: "list file share error",
1333+
listFileShareErr: fmt.Errorf("list file share error"),
1334+
expectedQuota: -1,
1335+
expectedShareNum: -1,
1336+
expectedErr: fmt.Errorf("list file share error"),
1337+
},
1338+
}
1339+
1340+
for _, test := range tests {
1341+
mockFileClient := mockfileclient.NewMockInterface(ctrl)
1342+
d.cloud.FileClient = mockFileClient
1343+
1344+
mockFileClient.EXPECT().WithSubscriptionID(gomock.Any()).Return(mockFileClient).AnyTimes()
1345+
mockFileClient.EXPECT().ListFileShare(context.TODO(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(test.fileShareItems, test.listFileShareErr).AnyTimes()
1346+
1347+
quota, fileShareNum, err := d.GetTotalAccountQuota(context.Background(), test.subsID, test.resourceGroup, test.accountName)
1348+
assert.Equal(t, test.expectedErr, err, test.name)
1349+
assert.Equal(t, test.expectedQuota, quota, test.name)
1350+
assert.Equal(t, test.expectedShareNum, fileShareNum, test.name)
1351+
}
1352+
}

pkg/azurefile/controllerserver.go

+21-12
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
124124
// store account key to k8s secret by default
125125
storeAccountKey := true
126126

127+
var accountQuota int32
127128
// Apply ProvisionerParameters (case-insensitive). We leave validation of
128129
// the values to the cloud provider.
129130
for k, v := range parameters {
@@ -246,6 +247,12 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
246247
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid %s: %s in storage class", getLatestAccountKeyField, v))
247248
}
248249
getLatestAccountKey = value
250+
case accountQuotaField:
251+
value, err := strconv.Atoi(v)
252+
if err != nil || value < minimumAccountQuota {
253+
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid accountQuota %s in storage class, minimum quota: %d", v, minimumAccountQuota))
254+
}
255+
accountQuota = int32(value)
249256
default:
250257
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid parameter %q in storage class", k))
251258
}
@@ -453,19 +460,21 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
453460
if err != nil {
454461
return nil, status.Errorf(codes.Internal, "failed to ensure storage account: %v", err)
455462
}
456-
totalQuotaGB, fileshareNum, err := d.GetTotalQuotaGB(ctx, subsID, resourceGroup, accountName)
457-
if err != nil {
458-
return nil, status.Errorf(codes.Internal, "failed to get total quota on account(%s), error: %v", accountName, err)
459-
}
460-
klog.V(2).Infof("total used quota on account(%s) is %d GB, file share number: %d", accountName, totalQuotaGB, fileshareNum)
461-
if totalQuotaGB > 81920 {
462-
klog.Warningf("account(%s) used quota(%d GB) is over 81920 GB, skip matching current account", accountName, totalQuotaGB)
463-
if rerr := d.cloud.AddStorageAccountTags(ctx, subsID, resourceGroup, accountName, skipMatchingTag); rerr != nil {
464-
klog.Warningf("AddStorageAccountTags(%v) on account(%s) subsID(%s) rg(%s) failed with error: %v", tags, accountName, subsID, resourceGroup, rerr.Error())
463+
if accountQuota > minimumAccountQuota {
464+
totalQuotaGB, fileshareNum, err := d.GetTotalAccountQuota(ctx, subsID, resourceGroup, accountName)
465+
if err != nil {
466+
return nil, status.Errorf(codes.Internal, "failed to get total quota on account(%s), error: %v", accountName, err)
467+
}
468+
klog.V(2).Infof("total used quota on account(%s) is %d GB, file share number: %d", accountName, totalQuotaGB, fileshareNum)
469+
if totalQuotaGB > accountQuota {
470+
klog.Warningf("account(%s) used quota(%d GB) is over %d GB, skip matching current account", accountName, accountQuota, totalQuotaGB)
471+
if rerr := d.cloud.AddStorageAccountTags(ctx, subsID, resourceGroup, accountName, skipMatchingTag); rerr != nil {
472+
klog.Warningf("AddStorageAccountTags(%v) on account(%s) subsID(%s) rg(%s) failed with error: %v", tags, accountName, subsID, resourceGroup, rerr.Error())
473+
}
474+
// release volume lock first to prevent deadlock
475+
d.volumeLocks.Release(volName)
476+
return d.CreateVolume(ctx, req)
465477
}
466-
// release volume lock first to prevent deadlock
467-
d.volumeLocks.Release(volName)
468-
return d.CreateVolume(ctx, req)
469478
}
470479
d.accountSearchCache.Set(lockKey, accountName)
471480
d.volMap.Store(volName, accountName)

0 commit comments

Comments
 (0)