• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / blob-csi-driver / 4916492637

08 May 2023 03:09PM UTC coverage: 80.771%. Remained the same
4916492637

Pull #916

github

GitHub
chore(deps): bump github/codeql-action from 1 to 2
Pull Request #916: chore(deps): bump github/codeql-action from 1 to 2

1823 of 2257 relevant lines covered (80.77%)

5.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.31
/pkg/blob/controllerserver.go
1
/*
2
Copyright 2017 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package blob
18

19
import (
20
        "context"
21
        "fmt"
22
        "strconv"
23
        "strings"
24

25
        "google.golang.org/grpc/codes"
26
        "google.golang.org/grpc/status"
27

28
        "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage"
29
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
30
        "github.com/container-storage-interface/spec/lib/go/csi"
31

32
        "k8s.io/apimachinery/pkg/util/wait"
33
        "k8s.io/klog/v2"
34
        "k8s.io/utils/pointer"
35

36
        "sigs.k8s.io/blob-csi-driver/pkg/util"
37
        azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
38
        "sigs.k8s.io/cloud-provider-azure/pkg/metrics"
39
        "sigs.k8s.io/cloud-provider-azure/pkg/provider"
40
        azure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
41
)
42

43
const (
44
        privateEndpoint = "privateendpoint"
45
)
46

47
// CreateVolume provisions a volume
48
func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
20✔
49
        if err := d.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {
21✔
50
                klog.Errorf("invalid create volume req: %v", req)
1✔
51
                return nil, err
1✔
52
        }
1✔
53

54
        volName := req.GetName()
19✔
55
        if len(volName) == 0 {
20✔
56
                return nil, status.Error(codes.InvalidArgument, "CreateVolume Name must be provided")
1✔
57
        }
1✔
58

59
        if err := isValidVolumeCapabilities(req.GetVolumeCapabilities()); err != nil {
20✔
60
                return nil, status.Error(codes.InvalidArgument, err.Error())
2✔
61
        }
2✔
62

63
        if acquired := d.volumeLocks.TryAcquire(volName); !acquired {
16✔
64
                return nil, status.Errorf(codes.Aborted, volumeOperationAlreadyExistsFmt, volName)
×
65
        }
×
66
        defer d.volumeLocks.Release(volName)
16✔
67

16✔
68
        volSizeBytes := int64(req.GetCapacityRange().GetRequiredBytes())
16✔
69
        requestGiB := int(util.RoundUpGiB(volSizeBytes))
16✔
70

16✔
71
        parameters := req.GetParameters()
16✔
72
        if parameters == nil {
17✔
73
                parameters = make(map[string]string)
1✔
74
        }
1✔
75
        var storageAccountType, subsID, resourceGroup, location, account, containerName, containerNamePrefix, protocol, customTags, secretName, secretNamespace, pvcNamespace string
16✔
76
        var isHnsEnabled, requireInfraEncryption, enableBlobVersioning *bool
16✔
77
        var vnetResourceGroup, vnetName, subnetName, accessTier, networkEndpointType, storageEndpointSuffix string
16✔
78
        var matchTags, useDataPlaneAPI bool
16✔
79
        var softDeleteBlobs, softDeleteContainers int32
16✔
80
        // set allowBlobPublicAccess as false by default
16✔
81
        allowBlobPublicAccess := pointer.Bool(false)
16✔
82

16✔
83
        containerNameReplaceMap := map[string]string{}
16✔
84

16✔
85
        // store account key to k8s secret by default
16✔
86
        storeAccountKey := true
16✔
87

16✔
88
        // Apply ProvisionerParameters (case-insensitive). We leave validation of
16✔
89
        // the values to the cloud provider.
16✔
90
        for k, v := range parameters {
105✔
91
                switch strings.ToLower(k) {
89✔
92
                case skuNameField:
9✔
93
                        storageAccountType = v
9✔
94
                case storageAccountTypeField:
10✔
95
                        storageAccountType = v
10✔
96
                case locationField:
9✔
97
                        location = v
9✔
98
                case storageAccountField:
10✔
99
                        account = v
10✔
100
                case subscriptionIDField:
2✔
101
                        subsID = v
2✔
102
                case resourceGroupField:
9✔
103
                        resourceGroup = v
9✔
104
                case containerNameField:
10✔
105
                        containerName = v
10✔
106
                case containerNamePrefixField:
2✔
107
                        containerNamePrefix = v
2✔
108
                case protocolField:
9✔
109
                        protocol = v
9✔
110
                case tagsField:
1✔
111
                        customTags = v
1✔
112
                case matchTagsField:
1✔
113
                        matchTags = strings.EqualFold(v, trueValue)
1✔
114
                case secretNameField:
×
115
                        secretName = v
×
116
                case secretNamespaceField:
×
117
                        secretNamespace = v
×
118
                case isHnsEnabledField:
×
119
                        if strings.EqualFold(v, trueValue) {
×
120
                                isHnsEnabled = pointer.Bool(true)
×
121
                        }
×
122
                case softDeleteBlobsField:
×
123
                        days, err := parseDays(v)
×
124
                        if err != nil {
×
125
                                return nil, err
×
126
                        }
×
127
                        softDeleteBlobs = days
×
128
                case softDeleteContainersField:
×
129
                        days, err := parseDays(v)
×
130
                        if err != nil {
×
131
                                return nil, err
×
132
                        }
×
133
                        softDeleteContainers = days
×
134
                case enableBlobVersioningField:
×
135
                        enableBlobVersioning = pointer.Bool(strings.EqualFold(v, trueValue))
×
136
                case storeAccountKeyField:
4✔
137
                        if strings.EqualFold(v, falseValue) {
7✔
138
                                storeAccountKey = false
3✔
139
                        }
3✔
140
                case allowBlobPublicAccessField:
×
141
                        if strings.EqualFold(v, trueValue) {
×
142
                                allowBlobPublicAccess = pointer.Bool(true)
×
143
                        }
×
144
                case requireInfraEncryptionField:
×
145
                        if strings.EqualFold(v, trueValue) {
×
146
                                requireInfraEncryption = pointer.Bool(true)
×
147
                        }
×
148
                case pvcNamespaceKey:
×
149
                        pvcNamespace = v
×
150
                        containerNameReplaceMap[pvcNamespaceMetadata] = v
×
151
                case pvcNameKey:
×
152
                        containerNameReplaceMap[pvcNameMetadata] = v
×
153
                case pvNameKey:
×
154
                        containerNameReplaceMap[pvNameMetadata] = v
×
155
                case serverNameField:
×
156
                        // no op, only used in NodeStageVolume
157
                case storageEndpointSuffixField:
×
158
                        storageEndpointSuffix = v
×
159
                case vnetResourceGroupField:
×
160
                        vnetResourceGroup = v
×
161
                case vnetNameField:
×
162
                        vnetName = v
×
163
                case subnetNameField:
×
164
                        subnetName = v
×
165
                case accessTierField:
×
166
                        accessTier = v
×
167
                case networkEndpointTypeField:
×
168
                        networkEndpointType = v
×
169
                case mountPermissionsField:
11✔
170
                        // only do validations here, used in NodeStageVolume, NodePublishVolume
11✔
171
                        if v != "" {
22✔
172
                                if _, err := strconv.ParseUint(v, 8, 32); err != nil {
12✔
173
                                        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid mountPermissions %s in storage class", v))
1✔
174
                                }
1✔
175
                        }
176
                case useDataPlaneAPIField:
1✔
177
                        useDataPlaneAPI = strings.EqualFold(v, trueValue)
1✔
178
                default:
1✔
179
                        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid parameter %q in storage class", k))
1✔
180
                }
181
        }
182

183
        if pointer.BoolDeref(enableBlobVersioning, false) {
14✔
184
                if protocol == NFS || pointer.BoolDeref(isHnsEnabled, false) {
×
185
                        return nil, status.Errorf(codes.InvalidArgument, "enableBlobVersioning is not supported for NFS protocol or HNS enabled account")
×
186
                }
×
187
        }
188

189
        if matchTags && account != "" {
15✔
190
                return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("matchTags must set as false when storageAccount(%s) is provided", account))
1✔
191
        }
1✔
192

193
        if subsID != "" && subsID != d.cloud.SubscriptionID {
15✔
194
                if protocol == NFS {
3✔
195
                        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("NFS protocol is not supported in cross subscription(%s)", subsID))
1✔
196
                }
1✔
197
                if !storeAccountKey {
2✔
198
                        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("storeAccountKey must set as true in cross subscription(%s)", subsID))
1✔
199
                }
1✔
200
        }
201

202
        if resourceGroup == "" {
15✔
203
                resourceGroup = d.cloud.ResourceGroup
4✔
204
        }
4✔
205

206
        if secretNamespace == "" {
22✔
207
                if pvcNamespace == "" {
22✔
208
                        secretNamespace = defaultNamespace
11✔
209
                } else {
11✔
210
                        secretNamespace = pvcNamespace
×
211
                }
×
212
        }
213

214
        if protocol == "" {
15✔
215
                protocol = Fuse
4✔
216
        }
4✔
217
        if !isSupportedProtocol(protocol) {
12✔
218
                return nil, status.Errorf(codes.InvalidArgument, "protocol(%s) is not supported, supported protocol list: %v", protocol, supportedProtocolList)
1✔
219
        }
1✔
220
        if !isSupportedAccessTier(accessTier) {
10✔
221
                return nil, status.Errorf(codes.InvalidArgument, "accessTier(%s) is not supported, supported AccessTier list: %v", accessTier, storage.PossibleAccessTierValues())
×
222
        }
×
223

224
        if containerName != "" && containerNamePrefix != "" {
11✔
225
                return nil, status.Errorf(codes.InvalidArgument, "containerName(%s) and containerNamePrefix(%s) could not be specified together", containerName, containerNamePrefix)
1✔
226
        }
1✔
227
        if !isSupportedContainerNamePrefix(containerNamePrefix) {
10✔
228
                return nil, status.Errorf(codes.InvalidArgument, "containerNamePrefix(%s) can only contain lowercase letters, numbers, hyphens, and length should be less than 21", containerNamePrefix)
1✔
229
        }
1✔
230

231
        enableHTTPSTrafficOnly := true
8✔
232
        createPrivateEndpoint := false
8✔
233
        if strings.EqualFold(networkEndpointType, privateEndpoint) {
8✔
234
                createPrivateEndpoint = true
×
235
        }
×
236
        accountKind := string(storage.KindStorageV2)
8✔
237
        var (
8✔
238
                vnetResourceIDs []string
8✔
239
                enableNfsV3     *bool
8✔
240
        )
8✔
241
        if protocol == NFS {
9✔
242
                isHnsEnabled = pointer.Bool(true)
1✔
243
                enableNfsV3 = pointer.Bool(true)
1✔
244
                // NFS protocol does not need account key
1✔
245
                storeAccountKey = false
1✔
246
                if !createPrivateEndpoint {
2✔
247
                        // set VirtualNetworkResourceIDs for storage account firewall setting
1✔
248
                        vnetResourceID := d.getSubnetResourceID(vnetResourceGroup, vnetName, subnetName)
1✔
249
                        klog.V(2).Infof("set vnetResourceID(%s) for NFS protocol", vnetResourceID)
1✔
250
                        vnetResourceIDs = []string{vnetResourceID}
1✔
251
                        if err := d.updateSubnetServiceEndpoints(ctx, vnetResourceGroup, vnetName, subnetName); err != nil {
2✔
252
                                return nil, status.Errorf(codes.Internal, "update service endpoints failed with error: %v", err)
1✔
253
                        }
1✔
254
                }
255
        }
256

257
        if strings.HasPrefix(strings.ToLower(storageAccountType), "premium") {
8✔
258
                accountKind = string(storage.KindBlockBlobStorage)
1✔
259
        }
1✔
260
        if IsAzureStackCloud(d.cloud) {
8✔
261
                accountKind = string(storage.KindStorage)
1✔
262
                if storageAccountType != "" && storageAccountType != string(storage.SkuNameStandardLRS) && storageAccountType != string(storage.SkuNamePremiumLRS) {
2✔
263
                        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("Invalid skuName value: %s, as Azure Stack only supports %s and %s Storage Account types.", storageAccountType, storage.SkuNamePremiumLRS, storage.SkuNameStandardLRS))
1✔
264
                }
1✔
265
        }
266

267
        tags, err := util.ConvertTagsToMap(customTags)
6✔
268
        if err != nil {
7✔
269
                return nil, status.Errorf(codes.InvalidArgument, err.Error())
1✔
270
        }
1✔
271

272
        if strings.TrimSpace(storageEndpointSuffix) == "" {
10✔
273
                if d.cloud.Environment.StorageEndpointSuffix != "" {
5✔
274
                        storageEndpointSuffix = d.cloud.Environment.StorageEndpointSuffix
×
275
                } else {
5✔
276
                        storageEndpointSuffix = defaultStorageEndPointSuffix
5✔
277
                }
5✔
278
        }
279

280
        accountOptions := &azure.AccountOptions{
5✔
281
                Name:                            account,
5✔
282
                Type:                            storageAccountType,
5✔
283
                Kind:                            accountKind,
5✔
284
                SubscriptionID:                  subsID,
5✔
285
                ResourceGroup:                   resourceGroup,
5✔
286
                Location:                        location,
5✔
287
                EnableHTTPSTrafficOnly:          enableHTTPSTrafficOnly,
5✔
288
                VirtualNetworkResourceIDs:       vnetResourceIDs,
5✔
289
                Tags:                            tags,
5✔
290
                MatchTags:                       matchTags,
5✔
291
                IsHnsEnabled:                    isHnsEnabled,
5✔
292
                EnableNfsV3:                     enableNfsV3,
5✔
293
                AllowBlobPublicAccess:           allowBlobPublicAccess,
5✔
294
                RequireInfrastructureEncryption: requireInfraEncryption,
5✔
295
                VNetResourceGroup:               vnetResourceGroup,
5✔
296
                VNetName:                        vnetName,
5✔
297
                SubnetName:                      subnetName,
5✔
298
                AccessTier:                      accessTier,
5✔
299
                CreatePrivateEndpoint:           createPrivateEndpoint,
5✔
300
                StorageType:                     provider.StorageTypeBlob,
5✔
301
                StorageEndpointSuffix:           storageEndpointSuffix,
5✔
302
                EnableBlobVersioning:            enableBlobVersioning,
5✔
303
                SoftDeleteBlobs:                 softDeleteBlobs,
5✔
304
                SoftDeleteContainers:            softDeleteContainers,
5✔
305
        }
5✔
306

5✔
307
        var accountKey string
5✔
308
        accountName := account
5✔
309
        secrets := req.GetSecrets()
5✔
310
        if len(secrets) == 0 && accountName == "" {
6✔
311
                if v, ok := d.volMap.Load(volName); ok {
1✔
312
                        accountName = v.(string)
×
313
                } else {
1✔
314
                        lockKey := fmt.Sprintf("%s%s%s%s%s%v", storageAccountType, accountKind, resourceGroup, location, protocol, createPrivateEndpoint)
1✔
315
                        // search in cache first
1✔
316
                        cache, err := d.accountSearchCache.Get(lockKey, azcache.CacheReadTypeDefault)
1✔
317
                        if err != nil {
1✔
318
                                return nil, status.Errorf(codes.Internal, err.Error())
×
319
                        }
×
320
                        if cache != nil {
1✔
321
                                accountName = cache.(string)
×
322
                        } else {
1✔
323
                                d.volLockMap.LockEntry(lockKey)
1✔
324
                                err = wait.ExponentialBackoff(d.cloud.RequestBackoff(), func() (bool, error) {
2✔
325
                                        var retErr error
1✔
326
                                        accountName, accountKey, retErr = d.cloud.EnsureStorageAccount(ctx, accountOptions, protocol)
1✔
327
                                        if isRetriableError(retErr) {
1✔
328
                                                klog.Warningf("EnsureStorageAccount(%s) failed with error(%v), waiting for retrying", account, retErr)
×
329
                                                return false, nil
×
330
                                        }
×
331
                                        return true, retErr
1✔
332
                                })
333
                                d.volLockMap.UnlockEntry(lockKey)
1✔
334
                                if err != nil {
2✔
335
                                        return nil, status.Errorf(codes.Internal, "ensure storage account failed with %v", err)
1✔
336
                                }
1✔
337
                                d.accountSearchCache.Set(lockKey, accountName)
×
338
                                d.volMap.Store(volName, accountName)
×
339
                        }
340
                }
341
        }
342

343
        if createPrivateEndpoint && protocol == NFS {
4✔
344
                // As for blobfuse/blobfuse2, serverName, i.e.,AZURE_STORAGE_BLOB_ENDPOINT env variable can't include
×
345
                // "privatelink", issue: https://github.com/Azure/azure-storage-fuse/issues/1014
×
346
                //
×
347
                // And use public endpoint will be befine to blobfuse/blobfuse2, because it will be resolved to private endpoint
×
348
                // by private dns zone, which includes CNAME record, documented here:
×
349
                // https://learn.microsoft.com/en-us/azure/storage/common/storage-private-endpoints?toc=%2Fazure%2Fstorage%2Fblobs%2Ftoc.json&bc=%2Fazure%2Fstorage%2Fblobs%2Fbreadcrumb%2Ftoc.json#dns-changes-for-private-endpoints
×
350
                setKeyValueInMap(parameters, serverNameField, fmt.Sprintf("%s.privatelink.blob.%s", accountName, storageEndpointSuffix))
×
351
        }
×
352

353
        accountOptions.Name = accountName
4✔
354
        if len(secrets) == 0 && useDataPlaneAPI {
5✔
355
                if accountKey == "" {
2✔
356
                        if accountName, accountKey, err = d.GetStorageAccesskey(ctx, accountOptions, secrets, secretName, secretNamespace); err != nil {
2✔
357
                                return nil, status.Errorf(codes.Internal, "failed to GetStorageAccesskey on account(%s) rg(%s), error: %v", accountOptions.Name, accountOptions.ResourceGroup, err)
1✔
358
                        }
1✔
359
                }
360
                secrets = createStorageAccountSecret(accountName, accountKey)
×
361
        }
362

363
        // replace pv/pvc name namespace metadata in subDir
364
        containerName = replaceWithMap(containerName, containerNameReplaceMap)
3✔
365
        validContainerName := containerName
3✔
366
        if validContainerName == "" {
3✔
367
                validContainerName = volName
×
368
                if containerNamePrefix != "" {
×
369
                        validContainerName = containerNamePrefix + "-" + volName
×
370
                }
×
371
                validContainerName = getValidContainerName(validContainerName, protocol)
×
372
                setKeyValueInMap(parameters, containerNameField, validContainerName)
×
373
        }
374

375
        var volumeID string
3✔
376
        mc := metrics.NewMetricContext(blobCSIDriverName, "controller_create_volume", d.cloud.ResourceGroup, d.cloud.SubscriptionID, d.Name)
3✔
377
        isOperationSucceeded := false
3✔
378
        defer func() {
6✔
379
                mc.ObserveOperationWithResult(isOperationSucceeded, VolumeID, volumeID)
3✔
380
        }()
3✔
381

382
        klog.V(2).Infof("begin to create container(%s) on account(%s) type(%s) subsID(%s) rg(%s) location(%s) size(%d)", validContainerName, accountName, storageAccountType, subsID, resourceGroup, location, requestGiB)
3✔
383
        if err := d.CreateBlobContainer(ctx, subsID, resourceGroup, accountName, validContainerName, secrets); err != nil {
4✔
384
                return nil, status.Errorf(codes.Internal, "failed to create container(%s) on account(%s) type(%s) rg(%s) location(%s) size(%d), error: %v", validContainerName, accountName, storageAccountType, resourceGroup, location, requestGiB, err)
1✔
385
        }
1✔
386

387
        if storeAccountKey && len(req.GetSecrets()) == 0 {
4✔
388
                if accountKey == "" {
4✔
389
                        if accountName, accountKey, err = d.GetStorageAccesskey(ctx, accountOptions, secrets, secretName, secretNamespace); err != nil {
3✔
390
                                return nil, status.Errorf(codes.Internal, "failed to GetStorageAccesskey on account(%s) rg(%s), error: %v", accountOptions.Name, accountOptions.ResourceGroup, err)
1✔
391
                        }
1✔
392
                }
393

394
                secretName, err := setAzureCredentials(ctx, d.cloud.KubeClient, accountName, accountKey, secretNamespace)
1✔
395
                if err != nil {
1✔
396
                        return nil, status.Errorf(codes.Internal, "failed to store storage account key: %v", err)
×
397
                }
×
398
                if secretName != "" {
1✔
399
                        klog.V(2).Infof("store account key to k8s secret(%v) in %s namespace", secretName, secretNamespace)
×
400
                }
×
401
        }
402

403
        var uuid string
1✔
404
        if containerName != "" {
2✔
405
                // add volume name as suffix to differentiate volumeID since "containerName" is specified
1✔
406
                // not necessary for dynamic container name creation since volumeID already contains volume name
1✔
407
                uuid = volName
1✔
408
        }
1✔
409
        volumeID = fmt.Sprintf(volumeIDTemplate, resourceGroup, accountName, validContainerName, uuid, secretNamespace, subsID)
1✔
410
        klog.V(2).Infof("create container %s on storage account %s successfully", validContainerName, accountName)
1✔
411

1✔
412
        if useDataPlaneAPI {
1✔
413
                d.dataPlaneAPIVolCache.Set(volumeID, "")
×
414
                d.dataPlaneAPIVolCache.Set(accountName, "")
×
415
        }
×
416

417
        isOperationSucceeded = true
1✔
418
        // reset secretNamespace field in VolumeContext
1✔
419
        setKeyValueInMap(parameters, secretNamespaceField, secretNamespace)
1✔
420
        return &csi.CreateVolumeResponse{
1✔
421
                Volume: &csi.Volume{
1✔
422
                        VolumeId:      volumeID,
1✔
423
                        CapacityBytes: req.GetCapacityRange().GetRequiredBytes(),
1✔
424
                        VolumeContext: parameters,
1✔
425
                },
1✔
426
        }, nil
1✔
427
}
428

429
// DeleteVolume delete a volume
430
func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
6✔
431
        volumeID := req.GetVolumeId()
6✔
432
        if len(volumeID) == 0 {
7✔
433
                return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
1✔
434
        }
1✔
435

436
        if err := d.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {
6✔
437
                return nil, status.Errorf(codes.Internal, "invalid delete volume req: %v", req)
1✔
438
        }
1✔
439

440
        if acquired := d.volumeLocks.TryAcquire(volumeID); !acquired {
4✔
441
                return nil, status.Errorf(codes.Aborted, volumeOperationAlreadyExistsFmt, volumeID)
×
442
        }
×
443
        defer d.volumeLocks.Release(volumeID)
4✔
444

4✔
445
        resourceGroupName, accountName, containerName, _, subsID, err := GetContainerInfo(volumeID)
4✔
446
        if err != nil {
5✔
447
                // According to CSI Driver Sanity Tester, should succeed when an invalid volume id is used
1✔
448
                klog.Errorf("GetContainerInfo(%s) in DeleteVolume failed with error: %v", volumeID, err)
1✔
449
                return &csi.DeleteVolumeResponse{}, nil
1✔
450
        }
1✔
451

452
        secrets := req.GetSecrets()
3✔
453
        if len(secrets) == 0 && d.useDataPlaneAPI(volumeID, accountName) {
4✔
454
                _, accountName, accountKey, _, _, err := d.GetAuthEnv(ctx, volumeID, "", nil, secrets)
1✔
455
                if err != nil {
2✔
456
                        return nil, status.Errorf(codes.Internal, "GetAuthEnv(%s) failed with %v", volumeID, err)
1✔
457
                }
1✔
458
                if accountName != "" && accountKey != "" {
×
459
                        secrets = createStorageAccountSecret(accountName, accountKey)
×
460
                }
×
461
        }
462

463
        mc := metrics.NewMetricContext(blobCSIDriverName, "controller_delete_volume", d.cloud.ResourceGroup, d.cloud.SubscriptionID, d.Name)
2✔
464
        isOperationSucceeded := false
2✔
465
        defer func() {
4✔
466
                mc.ObserveOperationWithResult(isOperationSucceeded, VolumeID, volumeID)
2✔
467
        }()
2✔
468

469
        if resourceGroupName == "" {
3✔
470
                resourceGroupName = d.cloud.ResourceGroup
1✔
471
        }
1✔
472
        klog.V(2).Infof("deleting container(%s) rg(%s) account(%s) volumeID(%s)", containerName, resourceGroupName, accountName, volumeID)
2✔
473
        if err := d.DeleteBlobContainer(ctx, subsID, resourceGroupName, accountName, containerName, secrets); err != nil {
4✔
474
                return nil, status.Errorf(codes.Internal, "failed to delete container(%s) under rg(%s) account(%s) volumeID(%s), error: %v", containerName, resourceGroupName, accountName, volumeID, err)
2✔
475
        }
2✔
476

477
        isOperationSucceeded = true
×
478
        klog.V(2).Infof("container(%s) under rg(%s) account(%s) volumeID(%s) is deleted successfully", containerName, resourceGroupName, accountName, volumeID)
×
479
        return &csi.DeleteVolumeResponse{}, nil
×
480
}
481

482
// ValidateVolumeCapabilities return the capabilities of the volume
483
func (d *Driver) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
8✔
484
        volumeID := req.GetVolumeId()
8✔
485
        if len(volumeID) == 0 {
9✔
486
                return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
1✔
487
        }
1✔
488
        if err := isValidVolumeCapabilities(req.GetVolumeCapabilities()); err != nil {
9✔
489
                return nil, status.Error(codes.InvalidArgument, err.Error())
2✔
490
        }
2✔
491

492
        resourceGroupName, accountName, containerName, _, subsID, err := GetContainerInfo(volumeID)
5✔
493
        if err != nil {
6✔
494
                klog.Errorf("GetContainerInfo(%s) in ValidateVolumeCapabilities failed with error: %v", volumeID, err)
1✔
495
                return nil, status.Error(codes.NotFound, err.Error())
1✔
496
        }
1✔
497

498
        var exist bool
4✔
499
        secrets := req.GetSecrets()
4✔
500
        if len(secrets) > 0 {
5✔
501
                container, err := getContainerReference(containerName, secrets, d.cloud.Environment)
1✔
502
                if err != nil {
2✔
503
                        return nil, status.Error(codes.Internal, err.Error())
1✔
504
                }
1✔
505
                exist, err = container.Exists()
×
506
                if err != nil {
×
507
                        return nil, status.Error(codes.Internal, err.Error())
×
508
                }
×
509
        } else {
3✔
510
                if resourceGroupName == "" {
3✔
511
                        resourceGroupName = d.cloud.ResourceGroup
×
512
                }
×
513
                blobContainer, retryErr := d.cloud.BlobClient.GetContainer(ctx, subsID, resourceGroupName, accountName, containerName)
3✔
514
                err = retryErr.Error()
3✔
515
                if err != nil {
4✔
516
                        return nil, status.Error(codes.Internal, err.Error())
1✔
517
                }
1✔
518
                if blobContainer.ContainerProperties == nil {
3✔
519
                        return nil, status.Errorf(codes.Internal, "ContainerProperties of volume(%s) is nil", volumeID)
1✔
520
                }
1✔
521
                exist = blobContainer.ContainerProperties.Deleted != nil && !*blobContainer.ContainerProperties.Deleted
1✔
522
        }
523
        if !exist {
2✔
524
                return nil, status.Errorf(codes.NotFound, "requested volume(%s) does not exist", volumeID)
1✔
525
        }
1✔
526
        klog.V(2).Infof("ValidateVolumeCapabilities on volume(%s) succeeded", volumeID)
×
527

×
528
        // blob driver supports all AccessModes, no need to check capabilities here
×
529
        return &csi.ValidateVolumeCapabilitiesResponse{
×
530
                Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
×
531
                        VolumeCapabilities: req.GetVolumeCapabilities(),
×
532
                },
×
533
                Message: "",
×
534
        }, nil
×
535
}
536

537
func (d *Driver) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
1✔
538
        return nil, status.Error(codes.Unimplemented, "ControllerPublishVolume is not yet implemented")
1✔
539
}
1✔
540

541
func (d *Driver) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
1✔
542
        return nil, status.Error(codes.Unimplemented, "ControllerUnpublishVolume is not yet implemented")
1✔
543
}
1✔
544

545
// ControllerGetVolume get volume
546
func (d *Driver) ControllerGetVolume(context.Context, *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) {
1✔
547
        return nil, status.Error(codes.Unimplemented, "ControllerGetVolume is not yet implemented")
1✔
548
}
1✔
549

550
// GetCapacity returns the capacity of the total available storage pool
551
func (d *Driver) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) {
1✔
552
        return nil, status.Error(codes.Unimplemented, "GetCapacity is not yet implemented")
1✔
553
}
1✔
554

555
// ListVolumes return all available volumes
556
func (d *Driver) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) {
1✔
557
        return nil, status.Error(codes.Unimplemented, "ListVolumes is not yet implemented")
1✔
558
}
1✔
559

560
// CreateSnapshot create snapshot
561
func (d *Driver) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
1✔
562
        return nil, status.Error(codes.Unimplemented, "CreateSnapshot is not yet implemented")
1✔
563
}
1✔
564

565
// DeleteSnapshot delete snapshot
566
func (d *Driver) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
1✔
567
        return nil, status.Error(codes.Unimplemented, "DeleteSnapshot is not yet implemented")
1✔
568
}
1✔
569

570
// ListSnapshots list snapshots
571
func (d *Driver) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
1✔
572
        return nil, status.Error(codes.Unimplemented, "ListSnapshots is not yet implemented")
1✔
573
}
1✔
574

575
// ControllerGetCapabilities returns the capabilities of the Controller plugin
576
func (d *Driver) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
1✔
577
        return &csi.ControllerGetCapabilitiesResponse{
1✔
578
                Capabilities: d.Cap,
1✔
579
        }, nil
1✔
580
}
1✔
581

582
// ControllerExpandVolume controller expand volume
583
func (d *Driver) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
5✔
584
        if len(req.GetVolumeId()) == 0 {
6✔
585
                return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
1✔
586
        }
1✔
587

588
        if req.GetCapacityRange() == nil {
5✔
589
                return nil, status.Error(codes.InvalidArgument, "Capacity Range missing in request")
1✔
590
        }
1✔
591

592
        if err := d.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_EXPAND_VOLUME); err != nil {
4✔
593
                return nil, status.Errorf(codes.Internal, "invalid expand volume req: %v", req)
1✔
594
        }
1✔
595

596
        volSizeBytes := int64(req.GetCapacityRange().GetRequiredBytes())
2✔
597
        requestGiB := int64(util.RoundUpGiB(volSizeBytes))
2✔
598

2✔
599
        if volSizeBytes > containerMaxSize {
3✔
600
                return nil, status.Errorf(codes.OutOfRange, "required bytes (%d) exceeds the maximum supported bytes (%d)", volSizeBytes, containerMaxSize)
1✔
601
        }
1✔
602

603
        klog.V(2).Infof("ControllerExpandVolume(%s) successfully, currentQuota: %d Gi", req.VolumeId, requestGiB)
1✔
604

1✔
605
        return &csi.ControllerExpandVolumeResponse{CapacityBytes: req.GetCapacityRange().GetRequiredBytes()}, nil
1✔
606
}
607

608
// CreateBlobContainer creates a blob container
609
func (d *Driver) CreateBlobContainer(ctx context.Context, subsID, resourceGroupName, accountName, containerName string, secrets map[string]string) error {
9✔
610
        if containerName == "" {
10✔
611
                return fmt.Errorf("containerName is empty")
1✔
612
        }
1✔
613
        return wait.ExponentialBackoff(d.cloud.RequestBackoff(), func() (bool, error) {
16✔
614
                var err error
8✔
615
                if len(secrets) > 0 {
9✔
616
                        container, getErr := getContainerReference(containerName, secrets, d.cloud.Environment)
1✔
617
                        if getErr != nil {
2✔
618
                                return true, getErr
1✔
619
                        }
1✔
620
                        _, err = container.CreateIfNotExists(&azstorage.CreateContainerOptions{Access: azstorage.ContainerAccessTypePrivate})
×
621
                } else {
7✔
622
                        blobContainer := storage.BlobContainer{
7✔
623
                                ContainerProperties: &storage.ContainerProperties{
7✔
624
                                        PublicAccess: storage.PublicAccessNone,
7✔
625
                                },
7✔
626
                        }
7✔
627
                        err = d.cloud.BlobClient.CreateContainer(ctx, subsID, resourceGroupName, accountName, containerName, blobContainer).Error()
7✔
628
                }
7✔
629
                if err != nil {
11✔
630
                        if strings.Contains(err.Error(), containerBeingDeletedDataplaneAPIError) ||
4✔
631
                                strings.Contains(err.Error(), containerBeingDeletedManagementAPIError) {
7✔
632
                                klog.Warningf("CreateContainer(%s, %s, %s) failed with error(%v), retry", resourceGroupName, accountName, containerName, err)
3✔
633
                                return false, nil
3✔
634
                        }
3✔
635
                }
636
                return true, err
4✔
637
        })
638
}
639

640
// DeleteBlobContainer deletes a blob container
641
func (d *Driver) DeleteBlobContainer(ctx context.Context, subsID, resourceGroupName, accountName, containerName string, secrets map[string]string) error {
8✔
642
        if containerName == "" {
9✔
643
                return fmt.Errorf("containerName is empty")
1✔
644
        }
1✔
645
        return wait.ExponentialBackoff(d.cloud.RequestBackoff(), func() (bool, error) {
14✔
646
                var err error
7✔
647
                if len(secrets) > 0 {
10✔
648
                        container, getErr := getContainerReference(containerName, secrets, d.cloud.Environment)
3✔
649
                        if getErr != nil {
6✔
650
                                return true, getErr
3✔
651
                        }
3✔
652
                        _, err = container.DeleteIfExists(nil)
×
653
                } else {
4✔
654
                        err = d.cloud.BlobClient.DeleteContainer(ctx, subsID, resourceGroupName, accountName, containerName).Error()
4✔
655
                }
4✔
656
                if err != nil {
7✔
657
                        if strings.Contains(err.Error(), containerBeingDeletedDataplaneAPIError) ||
3✔
658
                                strings.Contains(err.Error(), containerBeingDeletedManagementAPIError) ||
3✔
659
                                strings.Contains(err.Error(), statusCodeNotFound) ||
3✔
660
                                strings.Contains(err.Error(), httpCodeNotFound) {
5✔
661
                                klog.Warningf("delete container(%s) on account(%s) failed with error(%v), return as success", containerName, accountName, err)
2✔
662
                                return true, nil
2✔
663
                        }
2✔
664
                        return false, fmt.Errorf("failed to delete container(%s) on account(%s), error: %w", containerName, accountName, err)
1✔
665
                }
666
                return true, err
1✔
667
        })
668
}
669

670
// isValidVolumeCapabilities validates the given VolumeCapability array is valid
671
func isValidVolumeCapabilities(volCaps []*csi.VolumeCapability) error {
25✔
672
        if len(volCaps) == 0 {
27✔
673
                return fmt.Errorf("volume capabilities missing in request")
2✔
674
        }
2✔
675
        for _, c := range volCaps {
46✔
676
                if c.GetBlock() != nil {
25✔
677
                        return fmt.Errorf("block volume capability not supported")
2✔
678
                }
2✔
679
        }
680
        return nil
21✔
681
}
682

683
func parseDays(dayStr string) (int32, error) {
3✔
684
        days, err := strconv.Atoi(dayStr)
3✔
685
        if err != nil {
4✔
686
                return 0, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid %s:%s in storage class", softDeleteBlobsField, dayStr))
1✔
687
        }
1✔
688
        if days <= 0 || days > 365 {
3✔
689
                return 0, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid %s:%s in storage class, should be in range [1, 365]", softDeleteBlobsField, dayStr))
1✔
690
        }
1✔
691

692
        return int32(days), nil
1✔
693
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc