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

kubernetes-sigs / blob-csi-driver / 6498619488

12 Oct 2023 04:54PM UTC coverage: 80.606%. Remained the same
6498619488

Pull #1044

github

web-flow
chore(deps): bump github.com/onsi/ginkgo/v2 from 2.11.0 to 2.13.0

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.11.0 to 2.13.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.11.0...v2.13.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #1044: chore(deps): bump github.com/onsi/ginkgo/v2 from 2.11.0 to 2.13.0

1808 of 2243 relevant lines covered (80.61%)

5.68 hits per line

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

78.62
/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, createPrivateEndpoint, enableNfsV3 *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 vnetResourceIDs []string
17✔
81
        var err error
17✔
82
        // set allowBlobPublicAccess as false by default
17✔
83
        allowBlobPublicAccess := pointer.Bool(false)
17✔
84

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

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

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

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

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

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

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

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

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

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

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

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

274
        tags, err := util.ConvertTagsToMap(customTags)
6✔
275
        if err != nil {
7✔
276
                return nil, status.Errorf(codes.InvalidArgument, err.Error())
1✔
277
        }
1✔
278

279
        if strings.TrimSpace(storageEndpointSuffix) == "" {
10✔
280
                if d.cloud.Environment.StorageEndpointSuffix != "" {
5✔
281
                        storageEndpointSuffix = d.cloud.Environment.StorageEndpointSuffix
×
282
                } else {
5✔
283
                        storageEndpointSuffix = defaultStorageEndPointSuffix
5✔
284
                }
5✔
285
        }
286

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

5✔
315
        var volumeID string
5✔
316
        mc := metrics.NewMetricContext(blobCSIDriverName, "controller_create_volume", d.cloud.ResourceGroup, d.cloud.SubscriptionID, d.Name)
5✔
317
        isOperationSucceeded := false
5✔
318
        defer func() {
10✔
319
                mc.ObserveOperationWithResult(isOperationSucceeded, VolumeID, volumeID)
5✔
320
        }()
5✔
321

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

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

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

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

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

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

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

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

1✔
420
        if useDataPlaneAPI {
1✔
421
                d.dataPlaneAPIVolCache.Set(volumeID, "")
×
422
                d.dataPlaneAPIVolCache.Set(accountName, "")
×
423
        }
×
424

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

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

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

448
        if acquired := d.volumeLocks.TryAcquire(volumeID); !acquired {
4✔
449
                return nil, status.Errorf(codes.Aborted, volumeOperationAlreadyExistsFmt, volumeID)
×
450
        }
×
451
        defer d.volumeLocks.Release(volumeID)
4✔
452

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

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

471
        mc := metrics.NewMetricContext(blobCSIDriverName, "controller_delete_volume", d.cloud.ResourceGroup, d.cloud.SubscriptionID, d.Name)
2✔
472
        isOperationSucceeded := false
2✔
473
        defer func() {
4✔
474
                mc.ObserveOperationWithResult(isOperationSucceeded, VolumeID, volumeID)
2✔
475
        }()
2✔
476

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

596
        if req.GetCapacityRange() == nil {
5✔
597
                return nil, status.Error(codes.InvalidArgument, "Capacity Range missing in request")
1✔
598
        }
1✔
599

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

604
        volSizeBytes := int64(req.GetCapacityRange().GetRequiredBytes())
2✔
605
        requestGiB := int64(util.RoundUpGiB(volSizeBytes))
2✔
606

2✔
607
        if volSizeBytes > containerMaxSize {
3✔
608
                return nil, status.Errorf(codes.OutOfRange, "required bytes (%d) exceeds the maximum supported bytes (%d)", volSizeBytes, containerMaxSize)
1✔
609
        }
1✔
610

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

1✔
613
        return &csi.ControllerExpandVolumeResponse{CapacityBytes: req.GetCapacityRange().GetRequiredBytes()}, nil
1✔
614
}
615

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

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

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

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

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