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

kubernetes-sigs / blob-csi-driver / 5624779439

21 Jul 2023 05:02PM UTC coverage: 80.434%. Remained the same
5624779439

Pull #984

github

web-flow
chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azcore

Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-go/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md)
- [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.6.0...sdk/azcore/v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #984: chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azcore from 1.6.0 to 1.7.0

1854 of 2305 relevant lines covered (80.43%)

5.3 hits per line

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

78.78
/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) {
21✔
49
        if err := d.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {
22✔
50
                klog.Errorf("invalid create volume req: %v", req)
1✔
51
                return nil, err
1✔
52
        }
1✔
53

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

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

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

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

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

17✔
84
        containerNameReplaceMap := map[string]string{}
17✔
85

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

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

194
        if pointer.BoolDeref(enableBlobVersioning, false) {
14✔
195
                if protocol == NFS || pointer.BoolDeref(isHnsEnabled, false) {
×
196
                        return nil, status.Errorf(codes.InvalidArgument, "enableBlobVersioning is not supported for NFS protocol or HNS enabled account")
×
197
                }
×
198
        }
199

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

204
        if subsID != "" && subsID != d.cloud.SubscriptionID {
15✔
205
                if protocol == NFS {
3✔
206
                        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("NFS protocol is not supported in cross subscription(%s)", subsID))
1✔
207
                }
1✔
208
                if !storeAccountKey {
2✔
209
                        return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("storeAccountKey must set as true in cross subscription(%s)", subsID))
1✔
210
                }
1✔
211
        }
212

213
        if resourceGroup == "" {
16✔
214
                resourceGroup = d.cloud.ResourceGroup
5✔
215
        }
5✔
216

217
        if secretNamespace == "" {
22✔
218
                if pvcNamespace == "" {
22✔
219
                        secretNamespace = defaultNamespace
11✔
220
                } else {
11✔
221
                        secretNamespace = pvcNamespace
×
222
                }
×
223
        }
224

225
        if protocol == "" {
15✔
226
                protocol = Fuse
4✔
227
        }
4✔
228
        if !isSupportedProtocol(protocol) {
12✔
229
                return nil, status.Errorf(codes.InvalidArgument, "protocol(%s) is not supported, supported protocol list: %v", protocol, supportedProtocolList)
1✔
230
        }
1✔
231
        if !isSupportedAccessTier(accessTier) {
10✔
232
                return nil, status.Errorf(codes.InvalidArgument, "accessTier(%s) is not supported, supported AccessTier list: %v", accessTier, storage.PossibleAccessTierValues())
×
233
        }
×
234

235
        if containerName != "" && containerNamePrefix != "" {
11✔
236
                return nil, status.Errorf(codes.InvalidArgument, "containerName(%s) and containerNamePrefix(%s) could not be specified together", containerName, containerNamePrefix)
1✔
237
        }
1✔
238
        if !isSupportedContainerNamePrefix(containerNamePrefix) {
10✔
239
                return nil, status.Errorf(codes.InvalidArgument, "containerNamePrefix(%s) can only contain lowercase letters, numbers, hyphens, and length should be less than 21", containerNamePrefix)
1✔
240
        }
1✔
241

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

268
        if strings.HasPrefix(strings.ToLower(storageAccountType), "premium") {
8✔
269
                accountKind = string(storage.KindBlockBlobStorage)
1✔
270
        }
1✔
271
        if IsAzureStackCloud(d.cloud) {
8✔
272
                accountKind = string(storage.KindStorage)
1✔
273
                if storageAccountType != "" && storageAccountType != string(storage.SkuNameStandardLRS) && storageAccountType != string(storage.SkuNamePremiumLRS) {
2✔
274
                        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✔
275
                }
1✔
276
        }
277

278
        tags, err := util.ConvertTagsToMap(customTags)
6✔
279
        if err != nil {
7✔
280
                return nil, status.Errorf(codes.InvalidArgument, err.Error())
1✔
281
        }
1✔
282

283
        if strings.TrimSpace(storageEndpointSuffix) == "" {
10✔
284
                if d.cloud.Environment.StorageEndpointSuffix != "" {
5✔
285
                        storageEndpointSuffix = d.cloud.Environment.StorageEndpointSuffix
×
286
                } else {
5✔
287
                        storageEndpointSuffix = defaultStorageEndPointSuffix
5✔
288
                }
5✔
289
        }
290

291
        accountOptions := &azure.AccountOptions{
5✔
292
                Name:                            account,
5✔
293
                Type:                            storageAccountType,
5✔
294
                Kind:                            accountKind,
5✔
295
                SubscriptionID:                  subsID,
5✔
296
                ResourceGroup:                   resourceGroup,
5✔
297
                Location:                        location,
5✔
298
                EnableHTTPSTrafficOnly:          enableHTTPSTrafficOnly,
5✔
299
                VirtualNetworkResourceIDs:       vnetResourceIDs,
5✔
300
                Tags:                            tags,
5✔
301
                MatchTags:                       matchTags,
5✔
302
                IsHnsEnabled:                    isHnsEnabled,
5✔
303
                EnableNfsV3:                     enableNfsV3,
5✔
304
                AllowBlobPublicAccess:           allowBlobPublicAccess,
5✔
305
                RequireInfrastructureEncryption: requireInfraEncryption,
5✔
306
                VNetResourceGroup:               vnetResourceGroup,
5✔
307
                VNetName:                        vnetName,
5✔
308
                SubnetName:                      subnetName,
5✔
309
                AccessTier:                      accessTier,
5✔
310
                CreatePrivateEndpoint:           createPrivateEndpoint,
5✔
311
                StorageType:                     provider.StorageTypeBlob,
5✔
312
                StorageEndpointSuffix:           storageEndpointSuffix,
5✔
313
                EnableBlobVersioning:            enableBlobVersioning,
5✔
314
                SoftDeleteBlobs:                 softDeleteBlobs,
5✔
315
                SoftDeleteContainers:            softDeleteContainers,
5✔
316
                GetLatestAccountKey:             getLatestAccountKey,
5✔
317
        }
5✔
318

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

355
        if createPrivateEndpoint && protocol == NFS {
4✔
356
                // As for blobfuse/blobfuse2, serverName, i.e.,AZURE_STORAGE_BLOB_ENDPOINT env variable can't include
×
357
                // "privatelink", issue: https://github.com/Azure/azure-storage-fuse/issues/1014
×
358
                //
×
359
                // And use public endpoint will be befine to blobfuse/blobfuse2, because it will be resolved to private endpoint
×
360
                // by private dns zone, which includes CNAME record, documented here:
×
361
                // 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
×
362
                setKeyValueInMap(parameters, serverNameField, fmt.Sprintf("%s.privatelink.blob.%s", accountName, storageEndpointSuffix))
×
363
        }
×
364

365
        accountOptions.Name = accountName
4✔
366
        if len(secrets) == 0 && useDataPlaneAPI {
5✔
367
                if accountKey == "" {
2✔
368
                        if accountName, accountKey, err = d.GetStorageAccesskey(ctx, accountOptions, secrets, secretName, secretNamespace); err != nil {
2✔
369
                                return nil, status.Errorf(codes.Internal, "failed to GetStorageAccesskey on account(%s) rg(%s), error: %v", accountOptions.Name, accountOptions.ResourceGroup, err)
1✔
370
                        }
1✔
371
                }
372
                secrets = createStorageAccountSecret(accountName, accountKey)
×
373
        }
374

375
        // replace pv/pvc name namespace metadata in subDir
376
        containerName = replaceWithMap(containerName, containerNameReplaceMap)
3✔
377
        validContainerName := containerName
3✔
378
        if validContainerName == "" {
3✔
379
                validContainerName = volName
×
380
                if containerNamePrefix != "" {
×
381
                        validContainerName = containerNamePrefix + "-" + volName
×
382
                }
×
383
                validContainerName = getValidContainerName(validContainerName, protocol)
×
384
                setKeyValueInMap(parameters, containerNameField, validContainerName)
×
385
        }
386

387
        var volumeID string
3✔
388
        mc := metrics.NewMetricContext(blobCSIDriverName, "controller_create_volume", d.cloud.ResourceGroup, d.cloud.SubscriptionID, d.Name)
3✔
389
        isOperationSucceeded := false
3✔
390
        defer func() {
6✔
391
                mc.ObserveOperationWithResult(isOperationSucceeded, VolumeID, volumeID)
3✔
392
        }()
3✔
393

394
        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✔
395
        if err := d.CreateBlobContainer(ctx, subsID, resourceGroup, accountName, validContainerName, secrets); err != nil {
4✔
396
                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✔
397
        }
1✔
398

399
        if storeAccountKey && len(req.GetSecrets()) == 0 {
4✔
400
                if accountKey == "" {
4✔
401
                        if accountName, accountKey, err = d.GetStorageAccesskey(ctx, accountOptions, secrets, secretName, secretNamespace); err != nil {
3✔
402
                                return nil, status.Errorf(codes.Internal, "failed to GetStorageAccesskey on account(%s) rg(%s), error: %v", accountOptions.Name, accountOptions.ResourceGroup, err)
1✔
403
                        }
1✔
404
                }
405

406
                secretName, err := setAzureCredentials(ctx, d.cloud.KubeClient, accountName, accountKey, secretNamespace)
1✔
407
                if err != nil {
1✔
408
                        return nil, status.Errorf(codes.Internal, "failed to store storage account key: %v", err)
×
409
                }
×
410
                if secretName != "" {
1✔
411
                        klog.V(2).Infof("store account key to k8s secret(%v) in %s namespace", secretName, secretNamespace)
×
412
                }
×
413
        }
414

415
        var uuid string
1✔
416
        if containerName != "" {
2✔
417
                // add volume name as suffix to differentiate volumeID since "containerName" is specified
1✔
418
                // not necessary for dynamic container name creation since volumeID already contains volume name
1✔
419
                uuid = volName
1✔
420
        }
1✔
421
        volumeID = fmt.Sprintf(volumeIDTemplate, resourceGroup, accountName, validContainerName, uuid, secretNamespace, subsID)
1✔
422
        klog.V(2).Infof("create container %s on storage account %s successfully", validContainerName, accountName)
1✔
423

1✔
424
        if useDataPlaneAPI {
1✔
425
                d.dataPlaneAPIVolCache.Set(volumeID, "")
×
426
                d.dataPlaneAPIVolCache.Set(accountName, "")
×
427
        }
×
428

429
        isOperationSucceeded = true
1✔
430
        // reset secretNamespace field in VolumeContext
1✔
431
        setKeyValueInMap(parameters, secretNamespaceField, secretNamespace)
1✔
432
        return &csi.CreateVolumeResponse{
1✔
433
                Volume: &csi.Volume{
1✔
434
                        VolumeId:      volumeID,
1✔
435
                        CapacityBytes: req.GetCapacityRange().GetRequiredBytes(),
1✔
436
                        VolumeContext: parameters,
1✔
437
                },
1✔
438
        }, nil
1✔
439
}
440

441
// DeleteVolume delete a volume
442
func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
6✔
443
        volumeID := req.GetVolumeId()
6✔
444
        if len(volumeID) == 0 {
7✔
445
                return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
1✔
446
        }
1✔
447

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

452
        if acquired := d.volumeLocks.TryAcquire(volumeID); !acquired {
4✔
453
                return nil, status.Errorf(codes.Aborted, volumeOperationAlreadyExistsFmt, volumeID)
×
454
        }
×
455
        defer d.volumeLocks.Release(volumeID)
4✔
456

4✔
457
        resourceGroupName, accountName, containerName, _, subsID, err := GetContainerInfo(volumeID)
4✔
458
        if err != nil {
5✔
459
                // According to CSI Driver Sanity Tester, should succeed when an invalid volume id is used
1✔
460
                klog.Errorf("GetContainerInfo(%s) in DeleteVolume failed with error: %v", volumeID, err)
1✔
461
                return &csi.DeleteVolumeResponse{}, nil
1✔
462
        }
1✔
463

464
        secrets := req.GetSecrets()
3✔
465
        if len(secrets) == 0 && d.useDataPlaneAPI(volumeID, accountName) {
4✔
466
                _, accountName, accountKey, _, _, err := d.GetAuthEnv(ctx, volumeID, "", nil, secrets)
1✔
467
                if err != nil {
2✔
468
                        return nil, status.Errorf(codes.Internal, "GetAuthEnv(%s) failed with %v", volumeID, err)
1✔
469
                }
1✔
470
                if accountName != "" && accountKey != "" {
×
471
                        secrets = createStorageAccountSecret(accountName, accountKey)
×
472
                }
×
473
        }
474

475
        mc := metrics.NewMetricContext(blobCSIDriverName, "controller_delete_volume", d.cloud.ResourceGroup, d.cloud.SubscriptionID, d.Name)
2✔
476
        isOperationSucceeded := false
2✔
477
        defer func() {
4✔
478
                mc.ObserveOperationWithResult(isOperationSucceeded, VolumeID, volumeID)
2✔
479
        }()
2✔
480

481
        if resourceGroupName == "" {
3✔
482
                resourceGroupName = d.cloud.ResourceGroup
1✔
483
        }
1✔
484
        klog.V(2).Infof("deleting container(%s) rg(%s) account(%s) volumeID(%s)", containerName, resourceGroupName, accountName, volumeID)
2✔
485
        if err := d.DeleteBlobContainer(ctx, subsID, resourceGroupName, accountName, containerName, secrets); err != nil {
4✔
486
                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✔
487
        }
2✔
488

489
        isOperationSucceeded = true
×
490
        klog.V(2).Infof("container(%s) under rg(%s) account(%s) volumeID(%s) is deleted successfully", containerName, resourceGroupName, accountName, volumeID)
×
491
        return &csi.DeleteVolumeResponse{}, nil
×
492
}
493

494
// ValidateVolumeCapabilities return the capabilities of the volume
495
func (d *Driver) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
8✔
496
        volumeID := req.GetVolumeId()
8✔
497
        if len(volumeID) == 0 {
9✔
498
                return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
1✔
499
        }
1✔
500
        if err := isValidVolumeCapabilities(req.GetVolumeCapabilities()); err != nil {
9✔
501
                return nil, status.Error(codes.InvalidArgument, err.Error())
2✔
502
        }
2✔
503

504
        resourceGroupName, accountName, containerName, _, subsID, err := GetContainerInfo(volumeID)
5✔
505
        if err != nil {
6✔
506
                klog.Errorf("GetContainerInfo(%s) in ValidateVolumeCapabilities failed with error: %v", volumeID, err)
1✔
507
                return nil, status.Error(codes.NotFound, err.Error())
1✔
508
        }
1✔
509

510
        var exist bool
4✔
511
        secrets := req.GetSecrets()
4✔
512
        if len(secrets) > 0 {
5✔
513
                container, err := getContainerReference(containerName, secrets, d.cloud.Environment)
1✔
514
                if err != nil {
2✔
515
                        return nil, status.Error(codes.Internal, err.Error())
1✔
516
                }
1✔
517
                exist, err = container.Exists()
×
518
                if err != nil {
×
519
                        return nil, status.Error(codes.Internal, err.Error())
×
520
                }
×
521
        } else {
3✔
522
                if resourceGroupName == "" {
3✔
523
                        resourceGroupName = d.cloud.ResourceGroup
×
524
                }
×
525
                blobContainer, retryErr := d.cloud.BlobClient.GetContainer(ctx, subsID, resourceGroupName, accountName, containerName)
3✔
526
                err = retryErr.Error()
3✔
527
                if err != nil {
4✔
528
                        return nil, status.Error(codes.Internal, err.Error())
1✔
529
                }
1✔
530
                if blobContainer.ContainerProperties == nil {
3✔
531
                        return nil, status.Errorf(codes.Internal, "ContainerProperties of volume(%s) is nil", volumeID)
1✔
532
                }
1✔
533
                exist = blobContainer.ContainerProperties.Deleted != nil && !*blobContainer.ContainerProperties.Deleted
1✔
534
        }
535
        if !exist {
2✔
536
                return nil, status.Errorf(codes.NotFound, "requested volume(%s) does not exist", volumeID)
1✔
537
        }
1✔
538
        klog.V(2).Infof("ValidateVolumeCapabilities on volume(%s) succeeded", volumeID)
×
539

×
540
        // blob driver supports all AccessModes, no need to check capabilities here
×
541
        return &csi.ValidateVolumeCapabilitiesResponse{
×
542
                Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
×
543
                        VolumeCapabilities: req.GetVolumeCapabilities(),
×
544
                },
×
545
                Message: "",
×
546
        }, nil
×
547
}
548

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

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

557
// ControllerGetVolume get volume
558
func (d *Driver) ControllerGetVolume(context.Context, *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) {
1✔
559
        return nil, status.Error(codes.Unimplemented, "ControllerGetVolume is not yet implemented")
1✔
560
}
1✔
561

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

567
// ListVolumes return all available volumes
568
func (d *Driver) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) {
1✔
569
        return nil, status.Error(codes.Unimplemented, "ListVolumes is not yet implemented")
1✔
570
}
1✔
571

572
// CreateSnapshot create snapshot
573
func (d *Driver) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
1✔
574
        return nil, status.Error(codes.Unimplemented, "CreateSnapshot is not yet implemented")
1✔
575
}
1✔
576

577
// DeleteSnapshot delete snapshot
578
func (d *Driver) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
1✔
579
        return nil, status.Error(codes.Unimplemented, "DeleteSnapshot is not yet implemented")
1✔
580
}
1✔
581

582
// ListSnapshots list snapshots
583
func (d *Driver) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
1✔
584
        return nil, status.Error(codes.Unimplemented, "ListSnapshots is not yet implemented")
1✔
585
}
1✔
586

587
// ControllerGetCapabilities returns the capabilities of the Controller plugin
588
func (d *Driver) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
1✔
589
        return &csi.ControllerGetCapabilitiesResponse{
1✔
590
                Capabilities: d.Cap,
1✔
591
        }, nil
1✔
592
}
1✔
593

594
// ControllerExpandVolume controller expand volume
595
func (d *Driver) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
5✔
596
        if len(req.GetVolumeId()) == 0 {
6✔
597
                return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
1✔
598
        }
1✔
599

600
        if req.GetCapacityRange() == nil {
5✔
601
                return nil, status.Error(codes.InvalidArgument, "Capacity Range missing in request")
1✔
602
        }
1✔
603

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

608
        volSizeBytes := int64(req.GetCapacityRange().GetRequiredBytes())
2✔
609
        requestGiB := int64(util.RoundUpGiB(volSizeBytes))
2✔
610

2✔
611
        if volSizeBytes > containerMaxSize {
3✔
612
                return nil, status.Errorf(codes.OutOfRange, "required bytes (%d) exceeds the maximum supported bytes (%d)", volSizeBytes, containerMaxSize)
1✔
613
        }
1✔
614

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

1✔
617
        return &csi.ControllerExpandVolumeResponse{CapacityBytes: req.GetCapacityRange().GetRequiredBytes()}, nil
1✔
618
}
619

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

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

682
// isValidVolumeCapabilities validates the given VolumeCapability array is valid
683
func isValidVolumeCapabilities(volCaps []*csi.VolumeCapability) error {
26✔
684
        if len(volCaps) == 0 {
28✔
685
                return fmt.Errorf("volume capabilities missing in request")
2✔
686
        }
2✔
687
        for _, c := range volCaps {
48✔
688
                if c.GetBlock() != nil {
26✔
689
                        return fmt.Errorf("block volume capability not supported")
2✔
690
                }
2✔
691
        }
692
        return nil
22✔
693
}
694

695
func parseDays(dayStr string) (int32, error) {
3✔
696
        days, err := strconv.Atoi(dayStr)
3✔
697
        if err != nil {
4✔
698
                return 0, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid %s:%s in storage class", softDeleteBlobsField, dayStr))
1✔
699
        }
1✔
700
        if days <= 0 || days > 365 {
3✔
701
                return 0, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid %s:%s in storage class, should be in range [1, 365]", softDeleteBlobsField, dayStr))
1✔
702
        }
1✔
703

704
        return int32(days), nil
1✔
705
}
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