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

kubernetes-sigs / blob-csi-driver / 4889231241

05 May 2023 02:18AM UTC coverage: 81.423%. Remained the same
4889231241

push

github

GitHub
Merge pull request #904 from k8s-infra-cherrypick-robot/cherry-pick-892-to-release-1.19

1797 of 2207 relevant lines covered (81.42%)

5.29 hits per line

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

88.07
/pkg/blob/blob.go
1
/*
2
Copyright 2019 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
        "fmt"
21
        "os"
22
        "strings"
23
        "sync"
24
        "time"
25

26
        "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage"
27
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
28
        az "github.com/Azure/go-autorest/autorest/azure"
29
        "github.com/container-storage-interface/spec/lib/go/csi"
30
        "github.com/pborman/uuid"
31
        "golang.org/x/net/context"
32

33
        v1 "k8s.io/api/core/v1"
34
        "k8s.io/apimachinery/pkg/api/errors"
35
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36
        "k8s.io/client-go/kubernetes"
37
        "k8s.io/klog/v2"
38
        k8sutil "k8s.io/kubernetes/pkg/volume/util"
39
        mount "k8s.io/mount-utils"
40
        utilexec "k8s.io/utils/exec"
41

42
        csicommon "sigs.k8s.io/blob-csi-driver/pkg/csi-common"
43
        "sigs.k8s.io/blob-csi-driver/pkg/util"
44
        azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
45
        azure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
46
)
47

48
const (
49
        // DefaultDriverName holds the name of the csi-driver
50
        DefaultDriverName            = "blob.csi.azure.com"
51
        blobCSIDriverName            = "blob_csi_driver"
52
        separator                    = "#"
53
        volumeIDTemplate             = "%s#%s#%s#%s#%s#%s"
54
        secretNameTemplate           = "azure-storage-account-%s-secret"
55
        serverNameField              = "server"
56
        storageEndpointSuffixField   = "storageendpointsuffix"
57
        tagsField                    = "tags"
58
        matchTagsField               = "matchtags"
59
        protocolField                = "protocol"
60
        accountNameField             = "accountname"
61
        accountKeyField              = "accountkey"
62
        storageAccountField          = "storageaccount"
63
        storageAccountTypeField      = "storageaccounttype"
64
        skuNameField                 = "skuname"
65
        subscriptionIDField          = "subscriptionid"
66
        resourceGroupField           = "resourcegroup"
67
        locationField                = "location"
68
        secretNameField              = "secretname"
69
        secretNamespaceField         = "secretnamespace"
70
        containerNameField           = "containername"
71
        containerNamePrefixField     = "containernameprefix"
72
        storeAccountKeyField         = "storeaccountkey"
73
        isHnsEnabledField            = "ishnsenabled"
74
        getAccountKeyFromSecretField = "getaccountkeyfromsecret"
75
        keyVaultURLField             = "keyvaulturl"
76
        keyVaultSecretNameField      = "keyvaultsecretname"
77
        keyVaultSecretVersionField   = "keyvaultsecretversion"
78
        storageAccountNameField      = "storageaccountname"
79
        allowBlobPublicAccessField   = "allowblobpublicaccess"
80
        requireInfraEncryptionField  = "requireinfraencryption"
81
        ephemeralField               = "csi.storage.k8s.io/ephemeral"
82
        podNamespaceField            = "csi.storage.k8s.io/pod.namespace"
83
        mountOptionsField            = "mountoptions"
84
        falseValue                   = "false"
85
        trueValue                    = "true"
86
        defaultSecretAccountName     = "azurestorageaccountname"
87
        defaultSecretAccountKey      = "azurestorageaccountkey"
88
        Fuse                         = "fuse"
89
        Fuse2                        = "fuse2"
90
        NFS                          = "nfs"
91
        vnetResourceGroupField       = "vnetresourcegroup"
92
        vnetNameField                = "vnetname"
93
        subnetNameField              = "subnetname"
94
        accessTierField              = "accesstier"
95
        networkEndpointTypeField     = "networkendpointtype"
96
        mountPermissionsField        = "mountpermissions"
97
        useDataPlaneAPIField         = "usedataplaneapi"
98

99
        // See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
100
        containerNameMinLength = 3
101
        containerNameMaxLength = 63
102

103
        accountNotProvisioned                   = "StorageAccountIsNotProvisioned"
104
        tooManyRequests                         = "TooManyRequests"
105
        clientThrottled                         = "client throttled"
106
        containerBeingDeletedDataplaneAPIError  = "ContainerBeingDeleted"
107
        containerBeingDeletedManagementAPIError = "container is being deleted"
108
        statusCodeNotFound                      = "StatusCode=404"
109
        httpCodeNotFound                        = "HTTPStatusCode: 404"
110

111
        // containerMaxSize is the max size of the blob container. See https://docs.microsoft.com/en-us/azure/storage/blobs/scalability-targets#scale-targets-for-blob-storage
112
        containerMaxSize = 100 * util.TiB
113

114
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
115

116
        defaultNamespace = "default"
117

118
        pvcNameKey           = "csi.storage.k8s.io/pvc/name"
119
        pvcNamespaceKey      = "csi.storage.k8s.io/pvc/namespace"
120
        pvNameKey            = "csi.storage.k8s.io/pv/name"
121
        pvcNameMetadata      = "${pvc.metadata.name}"
122
        pvcNamespaceMetadata = "${pvc.metadata.namespace}"
123
        pvNameMetadata       = "${pv.metadata.name}"
124

125
        VolumeID = "volumeid"
126

127
        defaultStorageEndPointSuffix = "core.windows.net"
128
)
129

130
var (
131
        supportedProtocolList = []string{Fuse, Fuse2, NFS}
132
        retriableErrors       = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
133
)
134

135
// DriverOptions defines driver parameters specified in driver deployment
136
type DriverOptions struct {
137
        NodeID                                 string
138
        DriverName                             string
139
        CloudConfigSecretName                  string
140
        CloudConfigSecretNamespace             string
141
        CustomUserAgent                        string
142
        UserAgentSuffix                        string
143
        BlobfuseProxyEndpoint                  string
144
        EnableBlobfuseProxy                    bool
145
        BlobfuseProxyConnTimout                int
146
        EnableBlobMockMount                    bool
147
        AllowEmptyCloudConfig                  bool
148
        AllowInlineVolumeKeyAccessWithIdentity bool
149
        EnableGetVolumeStats                   bool
150
        AppendTimeStampInCacheDir              bool
151
        MountPermissions                       uint64
152
        KubeAPIQPS                             float64
153
        KubeAPIBurst                           int
154
}
155

156
// Driver implements all interfaces of CSI drivers
157
type Driver struct {
158
        csicommon.CSIDriver
159

160
        cloud                      *azure.Cloud
161
        cloudConfigSecretName      string
162
        cloudConfigSecretNamespace string
163
        customUserAgent            string
164
        userAgentSuffix            string
165
        blobfuseProxyEndpoint      string
166
        // enableBlobMockMount is only for testing, DO NOT set as true in non-testing scenario
167
        enableBlobMockMount                    bool
168
        enableBlobfuseProxy                    bool
169
        allowEmptyCloudConfig                  bool
170
        enableGetVolumeStats                   bool
171
        allowInlineVolumeKeyAccessWithIdentity bool
172
        appendTimeStampInCacheDir              bool
173
        blobfuseProxyConnTimout                int
174
        mountPermissions                       uint64
175
        kubeAPIQPS                             float64
176
        kubeAPIBurst                           int
177
        mounter                                *mount.SafeFormatAndMount
178
        volLockMap                             *util.LockMap
179
        // A map storing all volumes with ongoing operations so that additional operations
180
        // for that same volume (as defined by VolumeID) return an Aborted error
181
        volumeLocks *volumeLocks
182
        // only for nfs feature
183
        subnetLockMap *util.LockMap
184
        // a map storing all volumes created by this driver <volumeName, accountName>
185
        volMap sync.Map
186
        // a timed cache storing all volumeIDs and storage accounts that are using data plane API
187
        dataPlaneAPIVolCache *azcache.TimedCache
188
        // a timed cache storing account search history (solve account list throttling issue)
189
        accountSearchCache *azcache.TimedCache
190
}
191

192
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
193
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
194
func NewDriver(options *DriverOptions) *Driver {
104✔
195
        d := Driver{
104✔
196
                volLockMap:                             util.NewLockMap(),
104✔
197
                subnetLockMap:                          util.NewLockMap(),
104✔
198
                volumeLocks:                            newVolumeLocks(),
104✔
199
                cloudConfigSecretName:                  options.CloudConfigSecretName,
104✔
200
                cloudConfigSecretNamespace:             options.CloudConfigSecretNamespace,
104✔
201
                customUserAgent:                        options.CustomUserAgent,
104✔
202
                userAgentSuffix:                        options.UserAgentSuffix,
104✔
203
                blobfuseProxyEndpoint:                  options.BlobfuseProxyEndpoint,
104✔
204
                enableBlobfuseProxy:                    options.EnableBlobfuseProxy,
104✔
205
                allowInlineVolumeKeyAccessWithIdentity: options.AllowInlineVolumeKeyAccessWithIdentity,
104✔
206
                blobfuseProxyConnTimout:                options.BlobfuseProxyConnTimout,
104✔
207
                enableBlobMockMount:                    options.EnableBlobMockMount,
104✔
208
                allowEmptyCloudConfig:                  options.AllowEmptyCloudConfig,
104✔
209
                enableGetVolumeStats:                   options.EnableGetVolumeStats,
104✔
210
                mountPermissions:                       options.MountPermissions,
104✔
211
                kubeAPIQPS:                             options.KubeAPIQPS,
104✔
212
                kubeAPIBurst:                           options.KubeAPIBurst,
104✔
213
        }
104✔
214
        d.Name = options.DriverName
104✔
215
        d.Version = driverVersion
104✔
216
        d.NodeID = options.NodeID
104✔
217

104✔
218
        var err error
104✔
219
        getter := func(key string) (interface{}, error) { return nil, nil }
107✔
220
        if d.accountSearchCache, err = azcache.NewTimedcache(time.Minute, getter); err != nil {
104✔
221
                klog.Fatalf("%v", err)
×
222
        }
×
223
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedcache(10*time.Minute, getter); err != nil {
104✔
224
                klog.Fatalf("%v", err)
×
225
        }
×
226
        return &d
104✔
227
}
228

229
// Run driver initialization
230
func (d *Driver) Run(endpoint, kubeconfig string, testBool bool) {
2✔
231
        versionMeta, err := GetVersionYAML(d.Name)
2✔
232
        if err != nil {
2✔
233
                klog.Fatalf("%v", err)
×
234
        }
×
235
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
236

2✔
237
        userAgent := GetUserAgent(d.Name, d.customUserAgent, d.userAgentSuffix)
2✔
238
        klog.V(2).Infof("driver userAgent: %s", userAgent)
2✔
239
        d.cloud, err = getCloudProvider(kubeconfig, d.NodeID, d.cloudConfigSecretName, d.cloudConfigSecretNamespace, userAgent, d.allowEmptyCloudConfig, d.kubeAPIQPS, d.kubeAPIBurst)
2✔
240
        if err != nil {
2✔
241
                klog.Fatalf("failed to get Azure Cloud Provider, error: %v", err)
×
242
        }
×
243
        klog.V(2).Infof("cloud: %s, location: %s, rg: %s, VnetName: %s, VnetResourceGroup: %s, SubnetName: %s", d.cloud.Cloud, d.cloud.Location, d.cloud.ResourceGroup, d.cloud.VnetName, d.cloud.VnetResourceGroup, d.cloud.SubnetName)
2✔
244

2✔
245
        d.mounter = &mount.SafeFormatAndMount{
2✔
246
                Interface: mount.New(""),
2✔
247
                Exec:      utilexec.New(),
2✔
248
        }
2✔
249

2✔
250
        // Initialize default library driver
2✔
251
        d.AddControllerServiceCapabilities(
2✔
252
                []csi.ControllerServiceCapability_RPC_Type{
2✔
253
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
2✔
254
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
2✔
255
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
2✔
256
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
2✔
257
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
2✔
258
                })
2✔
259
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
2✔
260
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
2✔
261
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
2✔
262
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
2✔
263
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
2✔
264
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
2✔
265
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
2✔
266
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
2✔
267
        })
2✔
268

2✔
269
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
2✔
270
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
2✔
271
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
2✔
272
        }
2✔
273
        if d.enableGetVolumeStats {
2✔
274
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
275
        }
×
276
        d.AddNodeServiceCapabilities(nodeCap)
2✔
277

2✔
278
        s := csicommon.NewNonBlockingGRPCServer()
2✔
279
        // Driver d act as IdentityServer, ControllerServer and NodeServer
2✔
280
        s.Start(endpoint, d, d, d, testBool)
2✔
281
        s.Wait()
2✔
282
}
283

284
// GetContainerInfo get container info according to volume id
285
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
286
//
287
// e.g.
288
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
289
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
290
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
291
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
292
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
293
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
294
func GetContainerInfo(id string) (string, string, string, string, string, error) {
32✔
295
        segments := strings.Split(id, separator)
32✔
296
        if len(segments) < 3 {
39✔
297
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
7✔
298
        }
7✔
299
        var secretNamespace, subsID string
25✔
300
        if len(segments) > 4 {
31✔
301
                secretNamespace = segments[4]
6✔
302
        }
6✔
303
        if len(segments) > 5 {
28✔
304
                subsID = segments[5]
3✔
305
        }
3✔
306
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
25✔
307
}
308

309
// A container name must be a valid DNS name, conforming to the following naming rules:
310
//  1. Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
311
//  2. Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
312
//  3. All letters in a container name must be lowercase.
313
//  4. Container names must be from 3 through 63 characters long.
314
//
315
// See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
316
func getValidContainerName(volumeName, protocol string) string {
5✔
317
        containerName := strings.ToLower(volumeName)
5✔
318
        if len(containerName) > containerNameMaxLength {
6✔
319
                containerName = containerName[0:containerNameMaxLength]
1✔
320
        }
1✔
321
        if !checkContainerNameBeginAndEnd(containerName) || len(containerName) < containerNameMinLength {
5✔
322
                // now we set as 63 for maximum container name length
×
323
                // todo: get cluster name
×
324
                containerName = k8sutil.GenerateVolumeName(fmt.Sprintf("pvc-%s", protocol), uuid.NewUUID().String(), 63)
×
325
                klog.Warningf("requested volume name (%s) is invalid, regenerated as (%q)", volumeName, containerName)
×
326
        }
×
327
        return strings.Replace(containerName, "--", "-", -1)
5✔
328
}
329

330
func checkContainerNameBeginAndEnd(containerName string) bool {
11✔
331
        length := len(containerName)
11✔
332
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
11✔
333
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
11✔
334
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
11✔
335
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
20✔
336
                return true
9✔
337
        }
9✔
338

339
        return false
2✔
340
}
341

342
// isSASToken checks if the key contains the patterns.
343
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
344
func isSASToken(key string) bool {
3✔
345
        return strings.HasPrefix(key, "?")
3✔
346
}
3✔
347

348
// GetAuthEnv return <accountName, containerName, authEnv, error>
349
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
9✔
350
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
9✔
351
        if err != nil {
11✔
352
                // ignore volumeID parsing error
2✔
353
                klog.V(2).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
2✔
354
                err = nil
2✔
355
        }
2✔
356

357
        var (
9✔
358
                subsID                  string
9✔
359
                accountKey              string
9✔
360
                accountSasToken         string
9✔
361
                secretName              string
9✔
362
                pvcNamespace            string
9✔
363
                keyVaultURL             string
9✔
364
                keyVaultSecretName      string
9✔
365
                keyVaultSecretVersion   string
9✔
366
                azureStorageAuthType    string
9✔
367
                authEnv                 []string
9✔
368
                getAccountKeyFromSecret bool
9✔
369
        )
9✔
370

9✔
371
        for k, v := range attrib {
34✔
372
                switch strings.ToLower(k) {
25✔
373
                case subscriptionIDField:
1✔
374
                        subsID = v
1✔
375
                case resourceGroupField:
×
376
                        rgName = v
×
377
                case containerNameField:
3✔
378
                        containerName = v
3✔
379
                case keyVaultURLField:
1✔
380
                        keyVaultURL = v
1✔
381
                case keyVaultSecretNameField:
1✔
382
                        keyVaultSecretName = v
1✔
383
                case keyVaultSecretVersionField:
1✔
384
                        keyVaultSecretVersion = v
1✔
385
                case storageAccountField:
2✔
386
                        accountName = v
2✔
387
                case storageAccountNameField: // for compatibility
1✔
388
                        accountName = v
1✔
389
                case secretNameField:
1✔
390
                        secretName = v
1✔
391
                case secretNamespaceField:
1✔
392
                        secretNamespace = v
1✔
393
                case pvcNamespaceKey:
1✔
394
                        pvcNamespace = v
1✔
395
                case getAccountKeyFromSecretField:
1✔
396
                        getAccountKeyFromSecret = strings.EqualFold(v, trueValue)
1✔
397
                case "azurestorageauthtype":
×
398
                        azureStorageAuthType = v
×
399
                        authEnv = append(authEnv, "AZURE_STORAGE_AUTH_TYPE="+v)
×
400
                case "azurestorageidentityclientid":
1✔
401
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_CLIENT_ID="+v)
1✔
402
                case "azurestorageidentityobjectid":
1✔
403
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_OBJECT_ID="+v)
1✔
404
                case "azurestorageidentityresourceid":
1✔
405
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_RESOURCE_ID="+v)
1✔
406
                case "msiendpoint":
1✔
407
                        authEnv = append(authEnv, "MSI_ENDPOINT="+v)
1✔
408
                case "azurestoragespnclientid":
1✔
409
                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+v)
1✔
410
                case "azurestoragespntenantid":
1✔
411
                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+v)
1✔
412
                case "azurestorageaadendpoint":
1✔
413
                        authEnv = append(authEnv, "AZURE_STORAGE_AAD_ENDPOINT="+v)
1✔
414
                }
415
        }
416
        klog.V(2).Infof("volumeID(%s) authEnv: %s", volumeID, authEnv)
9✔
417

9✔
418
        if protocol == NFS {
11✔
419
                // nfs protocol does not need account key, return directly
2✔
420
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
421
        }
2✔
422

423
        if secretNamespace == "" {
13✔
424
                if pvcNamespace == "" {
12✔
425
                        secretNamespace = defaultNamespace
6✔
426
                } else {
6✔
427
                        secretNamespace = pvcNamespace
×
428
                }
×
429
        }
430

431
        if rgName == "" {
8✔
432
                rgName = d.cloud.ResourceGroup
1✔
433
        }
1✔
434

435
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
436
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
437
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
438
        if keyVaultURL != "" {
8✔
439
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
1✔
440
                if err != nil {
2✔
441
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
442
                }
1✔
443
                if isSASToken(key) {
×
444
                        accountSasToken = key
×
445
                } else {
×
446
                        accountKey = key
×
447
                }
×
448
        } else {
6✔
449
                if len(secrets) == 0 {
11✔
450
                        if secretName == "" && accountName != "" {
9✔
451
                                secretName = fmt.Sprintf(secretNameTemplate, accountName)
4✔
452
                        }
4✔
453
                        // if msi is specified, don't list account key using cluster identity
454
                        if secretName != "" && !strings.EqualFold(azureStorageAuthType, "msi") {
10✔
455
                                // read from k8s secret first
5✔
456
                                var name string
5✔
457
                                name, accountKey, err = d.GetStorageAccountFromSecret(secretName, secretNamespace)
5✔
458
                                if name != "" {
5✔
459
                                        accountName = name
×
460
                                }
×
461
                                if err != nil && !getAccountKeyFromSecret {
10✔
462
                                        klog.V(2).Infof("get account(%s) key from secret(%s, %s) failed with error: %v, use cluster identity to get account key instead",
5✔
463
                                                accountName, secretNamespace, secretName, err)
5✔
464
                                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName)
5✔
465
                                        if err != nil {
7✔
466
                                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
2✔
467
                                        }
2✔
468
                                }
469
                        }
470
                } else {
1✔
471
                        for k, v := range secrets {
8✔
472
                                v = strings.TrimSpace(v)
7✔
473
                                switch strings.ToLower(k) {
7✔
474
                                case accountNameField:
1✔
475
                                        accountName = v
1✔
476
                                case defaultSecretAccountName: // for compatibility with built-in blobfuse plugin
1✔
477
                                        accountName = v
1✔
478
                                case accountKeyField:
1✔
479
                                        accountKey = v
1✔
480
                                case defaultSecretAccountKey: // for compatibility with built-in blobfuse plugin
1✔
481
                                        accountKey = v
1✔
482
                                case "azurestorageaccountsastoken":
1✔
483
                                        accountSasToken = v
1✔
484
                                case "msisecret":
1✔
485
                                        authEnv = append(authEnv, "MSI_SECRET="+v)
1✔
486
                                case "azurestoragespnclientsecret":
1✔
487
                                        authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+v)
1✔
488
                                }
489
                        }
490
                }
491
        }
492

493
        if containerName == "" {
4✔
494
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
495
        }
×
496

497
        if accountSasToken != "" {
5✔
498
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
499
        }
1✔
500

501
        if accountKey != "" {
8✔
502
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
503
        }
4✔
504

505
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
506
}
507

508
// GetStorageAccountAndContainer get storage account and container info
509
// returns <accountName, accountKey, accountSasToken, containerName>
510
// only for e2e testing
511
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
2✔
512
        var (
2✔
513
                subsID                string
2✔
514
                accountName           string
2✔
515
                accountKey            string
2✔
516
                accountSasToken       string
2✔
517
                containerName         string
2✔
518
                keyVaultURL           string
2✔
519
                keyVaultSecretName    string
2✔
520
                keyVaultSecretVersion string
2✔
521
                err                   error
2✔
522
        )
2✔
523

2✔
524
        for k, v := range attrib {
6✔
525
                switch strings.ToLower(k) {
4✔
526
                case subscriptionIDField:
×
527
                        subsID = v
×
528
                case containerNameField:
1✔
529
                        containerName = v
1✔
530
                case keyVaultURLField:
×
531
                        keyVaultURL = v
×
532
                case keyVaultSecretNameField:
1✔
533
                        keyVaultSecretName = v
1✔
534
                case keyVaultSecretVersionField:
1✔
535
                        keyVaultSecretVersion = v
1✔
536
                case storageAccountField:
×
537
                        accountName = v
×
538
                case storageAccountNameField: // for compatibility
1✔
539
                        accountName = v
1✔
540
                }
541
        }
542

543
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
544
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
545
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
546
        if keyVaultURL != "" {
2✔
547
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
548
                if err != nil {
×
549
                        return "", "", "", "", err
×
550
                }
×
551
                if isSASToken(key) {
×
552
                        accountSasToken = key
×
553
                } else {
×
554
                        accountKey = key
×
555
                }
×
556
        } else {
2✔
557
                if len(secrets) == 0 {
4✔
558
                        var rgName string
2✔
559
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
560
                        if err != nil {
2✔
561
                                return "", "", "", "", err
×
562
                        }
×
563

564
                        if rgName == "" {
2✔
565
                                rgName = d.cloud.ResourceGroup
×
566
                        }
×
567

568
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName)
2✔
569
                        if err != nil {
3✔
570
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
571
                        }
1✔
572
                }
573
        }
574

575
        if containerName == "" {
1✔
576
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
577
        }
×
578

579
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
580
}
581

582
func IsCorruptedDir(dir string) bool {
4✔
583
        _, pathErr := mount.PathExists(dir)
4✔
584
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
585
}
4✔
586

587
func isRetriableError(err error) bool {
5✔
588
        if err != nil {
9✔
589
                for _, v := range retriableErrors {
19✔
590
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
591
                                return true
3✔
592
                        }
3✔
593
                }
594
        }
595
        return false
2✔
596
}
597

598
func isSupportedProtocol(protocol string) bool {
15✔
599
        if protocol == "" {
16✔
600
                return true
1✔
601
        }
1✔
602
        for _, v := range supportedProtocolList {
36✔
603
                if protocol == v {
34✔
604
                        return true
12✔
605
                }
12✔
606
        }
607
        return false
2✔
608
}
609

610
func isSupportedAccessTier(accessTier string) bool {
18✔
611
        if accessTier == "" {
29✔
612
                return true
11✔
613
        }
11✔
614
        for _, tier := range storage.PossibleAccessTierValues() {
25✔
615
                if accessTier == string(tier) {
21✔
616
                        return true
3✔
617
                }
3✔
618
        }
619
        return false
4✔
620
}
621

622
// container names can contain only lowercase letters, numbers, and hyphens,
623
// and must begin and end with a letter or a number
624
func isSupportedContainerNamePrefix(prefix string) bool {
17✔
625
        if prefix == "" {
26✔
626
                return true
9✔
627
        }
9✔
628
        if len(prefix) > 20 {
9✔
629
                return false
1✔
630
        }
1✔
631
        if prefix[0] == '-' {
8✔
632
                return false
1✔
633
        }
1✔
634
        for _, v := range prefix {
19✔
635
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
636
                        return false
4✔
637
                }
4✔
638
        }
639
        return true
2✔
640
}
641

642
// get storage account from secrets map
643
func getStorageAccount(secrets map[string]string) (string, string, error) {
18✔
644
        if secrets == nil {
19✔
645
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
646
        }
1✔
647

648
        var accountName, accountKey string
17✔
649
        for k, v := range secrets {
53✔
650
                v = strings.TrimSpace(v)
36✔
651
                switch strings.ToLower(k) {
36✔
652
                case accountNameField:
7✔
653
                        accountName = v
7✔
654
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
9✔
655
                        accountName = v
9✔
656
                case accountKeyField:
7✔
657
                        accountKey = v
7✔
658
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
9✔
659
                        accountKey = v
9✔
660
                }
661
        }
662

663
        if accountName == "" {
21✔
664
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field secrets(%v)", accountNameField, defaultSecretAccountName, secrets)
4✔
665
        }
4✔
666
        if accountKey == "" {
16✔
667
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets(%v)", accountKeyField, defaultSecretAccountKey, secrets)
3✔
668
        }
3✔
669

670
        accountName = strings.TrimSpace(accountName)
10✔
671
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
10✔
672
        return accountName, accountKey, nil
10✔
673
}
674

675
func getContainerReference(containerName string, secrets map[string]string, env az.Environment) (*azstorage.Container, error) {
9✔
676
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
677
        if rerr != nil {
11✔
678
                return nil, rerr
2✔
679
        }
2✔
680
        client, err := azstorage.NewBasicClientOnSovereignCloud(accountName, accountKey, env)
7✔
681
        if err != nil {
13✔
682
                return nil, err
6✔
683
        }
6✔
684
        blobClient := client.GetBlobService()
1✔
685
        container := blobClient.GetContainerReference(containerName)
1✔
686
        if container == nil {
1✔
687
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
688
        }
×
689
        return container, nil
1✔
690
}
691

692
func setAzureCredentials(kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
693
        if kubeClient == nil {
8✔
694
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
695
                return "", nil
2✔
696
        }
2✔
697
        if accountName == "" || accountKey == "" {
6✔
698
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
699
        }
2✔
700
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
701
        secret := &v1.Secret{
2✔
702
                ObjectMeta: metav1.ObjectMeta{
2✔
703
                        Namespace: secretNamespace,
2✔
704
                        Name:      secretName,
2✔
705
                },
2✔
706
                Data: map[string][]byte{
2✔
707
                        defaultSecretAccountName: []byte(accountName),
2✔
708
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
709
                },
2✔
710
                Type: "Opaque",
2✔
711
        }
2✔
712
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(context.TODO(), secret, metav1.CreateOptions{})
2✔
713
        if errors.IsAlreadyExists(err) {
3✔
714
                err = nil
1✔
715
        }
1✔
716
        if err != nil {
2✔
717
                return "", fmt.Errorf("couldn't create secret %w", err)
×
718
        }
×
719
        return secretName, err
2✔
720
}
721

722
// GetStorageAccesskey get Azure storage account key from
723
//  1. secrets (if not empty)
724
//  2. use k8s client identity to read from k8s secret
725
//  3. use cluster identity to get from storage account directly
726
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *azure.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
7✔
727
        if len(secrets) > 0 {
8✔
728
                return getStorageAccount(secrets)
1✔
729
        }
1✔
730

731
        // read from k8s secret first
732
        if secretName == "" {
10✔
733
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
4✔
734
        }
4✔
735
        _, accountKey, err := d.GetStorageAccountFromSecret(secretName, secretNamespace)
6✔
736
        if err != nil {
10✔
737
                klog.V(2).Infof("could not get account(%s) key from secret(%s) namespace(%s), error: %v, use cluster identity to get account key instead", accountOptions.Name, secretName, secretNamespace, err)
4✔
738
                accountKey, err = d.cloud.GetStorageAccesskey(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup)
4✔
739
        }
4✔
740
        return accountOptions.Name, accountKey, err
6✔
741
}
742

743
// GetStorageAccountFromSecret get storage account key from k8s secret
744
// return <accountName, accountKey, error>
745
func (d *Driver) GetStorageAccountFromSecret(secretName, secretNamespace string) (string, string, error) {
14✔
746
        if d.cloud.KubeClient == nil {
23✔
747
                return "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
9✔
748
        }
9✔
749

750
        secret, err := d.cloud.KubeClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{})
5✔
751
        if err != nil {
7✔
752
                return "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
753
        }
2✔
754

755
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
3✔
756
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
3✔
757

3✔
758
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
3✔
759
        return accountName, accountKey, nil
3✔
760
}
761

762
// getSubnetResourceID get default subnet resource ID from cloud provider config
763
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
764
        subsID := d.cloud.SubscriptionID
6✔
765
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
766
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
767
        }
4✔
768

769
        if len(vnetResourceGroup) == 0 {
11✔
770
                vnetResourceGroup = d.cloud.ResourceGroup
5✔
771
                if len(d.cloud.VnetResourceGroup) > 0 {
8✔
772
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
773
                }
3✔
774
        }
775

776
        if len(vnetName) == 0 {
11✔
777
                vnetName = d.cloud.VnetName
5✔
778
        }
5✔
779

780
        if len(subnetName) == 0 {
11✔
781
                subnetName = d.cloud.SubnetName
5✔
782
        }
5✔
783
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
784
}
785

786
func (d *Driver) useDataPlaneAPI(volumeID, accountName string) bool {
4✔
787
        cache, err := d.dataPlaneAPIVolCache.Get(volumeID, azcache.CacheReadTypeDefault)
4✔
788
        if err != nil {
4✔
789
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
790
        }
×
791
        if cache != nil {
7✔
792
                return true
3✔
793
        }
3✔
794
        cache, err = d.dataPlaneAPIVolCache.Get(accountName, azcache.CacheReadTypeDefault)
1✔
795
        if err != nil {
1✔
796
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
797
        }
×
798
        if cache != nil {
1✔
799
                return true
×
800
        }
×
801
        return false
1✔
802
}
803

804
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
805
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
806
        var defaultMountOptions = map[string]string{
4✔
807
                "--pre-mount-validate": "true",
4✔
808
                "--use-https":          "true",
4✔
809
                "--tmp-path":           tmpPath,
4✔
810
                "--container-name":     containerName,
4✔
811
                // prevent billing charges on mounting
4✔
812
                "--cancel-list-on-mount-seconds": "10",
4✔
813
                // allow remounting using a non-empty tmp-path
4✔
814
                "--empty-dir-check": "false",
4✔
815
        }
4✔
816

4✔
817
        // stores the mount options already included in mountOptions
4✔
818
        included := make(map[string]bool)
4✔
819

4✔
820
        for _, mountOption := range mountOptions {
11✔
821
                for k := range defaultMountOptions {
49✔
822
                        if strings.HasPrefix(mountOption, k) {
46✔
823
                                included[k] = true
4✔
824
                        }
4✔
825
                }
826
        }
827

828
        allMountOptions := mountOptions
4✔
829

4✔
830
        for k, v := range defaultMountOptions {
28✔
831
                if _, isIncluded := included[k]; !isIncluded {
44✔
832
                        if v != "" {
40✔
833
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
834
                        } else {
20✔
835
                                allMountOptions = append(allMountOptions, k)
×
836
                        }
×
837
                }
838
        }
839

840
        return allMountOptions
4✔
841
}
842

843
// chmodIfPermissionMismatch only perform chmod when permission mismatches
844
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
845
        info, err := os.Lstat(targetPath)
3✔
846
        if err != nil {
4✔
847
                return err
1✔
848
        }
1✔
849
        perm := info.Mode() & os.ModePerm
2✔
850
        if perm != mode {
3✔
851
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
852
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
853
                        return err
×
854
                }
×
855
        } else {
1✔
856
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
857
        }
1✔
858
        return nil
2✔
859
}
860

861
func createStorageAccountSecret(account, key string) map[string]string {
1✔
862
        secret := make(map[string]string)
1✔
863
        secret[defaultSecretAccountName] = account
1✔
864
        secret[defaultSecretAccountKey] = key
1✔
865
        return secret
1✔
866
}
1✔
867

868
// setKeyValueInMap set key/value pair in map
869
// key in the map is case insensitive, if key already exists, overwrite existing value
870
func setKeyValueInMap(m map[string]string, key, value string) {
6✔
871
        if m == nil {
7✔
872
                return
1✔
873
        }
1✔
874
        for k := range m {
16✔
875
                if strings.EqualFold(k, key) {
13✔
876
                        m[k] = value
2✔
877
                        return
2✔
878
                }
2✔
879
        }
880
        m[key] = value
3✔
881
}
882

883
// replaceWithMap replace key with value for str
884
func replaceWithMap(str string, m map[string]string) string {
11✔
885
        for k, v := range m {
16✔
886
                if k != "" {
9✔
887
                        str = strings.ReplaceAll(str, k, v)
4✔
888
                }
4✔
889
        }
890
        return str
11✔
891
}
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