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

kubernetes-sigs / blob-csi-driver / 13390042311

18 Feb 2025 11:59AM UTC coverage: 71.503%. Remained the same
13390042311

Pull #1842

github

andyzhangx
feat: optimize azcopy perf in volume cloning scenario
Pull Request #1842: [release-1.23] feat: optimize azcopy perf in volume cloning scenario

2070 of 2895 relevant lines covered (71.5%)

6.49 hits per line

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

84.72
/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
        "context"
21
        "errors"
22
        "flag"
23
        "fmt"
24
        "os"
25
        "strconv"
26
        "strings"
27
        "sync"
28
        "time"
29

30
        "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-09-01/storage"
31
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
32
        az "github.com/Azure/go-autorest/autorest/azure"
33
        "github.com/container-storage-interface/spec/lib/go/csi"
34
        "github.com/pborman/uuid"
35
        "google.golang.org/grpc"
36
        v1 "k8s.io/api/core/v1"
37
        apierror "k8s.io/apimachinery/pkg/api/errors"
38
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
39
        "k8s.io/client-go/kubernetes"
40
        "k8s.io/klog/v2"
41
        k8sutil "k8s.io/kubernetes/pkg/volume/util"
42
        mount "k8s.io/mount-utils"
43
        utilexec "k8s.io/utils/exec"
44

45
        csicommon "sigs.k8s.io/blob-csi-driver/pkg/csi-common"
46
        "sigs.k8s.io/blob-csi-driver/pkg/util"
47
        azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
48
        "sigs.k8s.io/cloud-provider-azure/pkg/provider"
49
        azure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
50
)
51

52
const (
53
        // DefaultDriverName holds the name of the csi-driver
54
        DefaultDriverName              = "blob.csi.azure.com"
55
        blobCSIDriverName              = "blob_csi_driver"
56
        separator                      = "#"
57
        volumeIDTemplate               = "%s#%s#%s#%s#%s#%s"
58
        secretNameTemplate             = "azure-storage-account-%s-secret"
59
        serverNameField                = "server"
60
        storageEndpointSuffixField     = "storageendpointsuffix"
61
        tagsField                      = "tags"
62
        matchTagsField                 = "matchtags"
63
        protocolField                  = "protocol"
64
        accountNameField               = "accountname"
65
        accountKeyField                = "accountkey"
66
        storageAccountField            = "storageaccount"
67
        storageAccountTypeField        = "storageaccounttype"
68
        skuNameField                   = "skuname"
69
        subscriptionIDField            = "subscriptionid"
70
        resourceGroupField             = "resourcegroup"
71
        locationField                  = "location"
72
        secretNameField                = "secretname"
73
        secretNamespaceField           = "secretnamespace"
74
        containerNameField             = "containername"
75
        containerNamePrefixField       = "containernameprefix"
76
        storeAccountKeyField           = "storeaccountkey"
77
        getLatestAccountKeyField       = "getlatestaccountkey"
78
        isHnsEnabledField              = "ishnsenabled"
79
        softDeleteBlobsField           = "softdeleteblobs"
80
        softDeleteContainersField      = "softdeletecontainers"
81
        enableBlobVersioningField      = "enableblobversioning"
82
        getAccountKeyFromSecretField   = "getaccountkeyfromsecret"
83
        storageSPNClientIDField        = "azurestoragespnclientid"
84
        storageSPNTenantIDField        = "azurestoragespntenantid"
85
        storageAuthTypeField           = "azurestorageauthtype"
86
        storageIentityClientIDField    = "azurestorageidentityclientid"
87
        storageIdentityObjectIDField   = "azurestorageidentityobjectid"
88
        storageIdentityResourceIDField = "azurestorageidentityresourceid"
89
        msiEndpointField               = "msiendpoint"
90
        storageAADEndpointField        = "azurestorageaadendpoint"
91
        keyVaultURLField               = "keyvaulturl"
92
        keyVaultSecretNameField        = "keyvaultsecretname"
93
        keyVaultSecretVersionField     = "keyvaultsecretversion"
94
        storageAccountNameField        = "storageaccountname"
95
        allowBlobPublicAccessField     = "allowblobpublicaccess"
96
        allowSharedKeyAccessField      = "allowsharedkeyaccess"
97
        requireInfraEncryptionField    = "requireinfraencryption"
98
        ephemeralField                 = "csi.storage.k8s.io/ephemeral"
99
        podNamespaceField              = "csi.storage.k8s.io/pod.namespace"
100
        serviceAccountTokenField       = "csi.storage.k8s.io/serviceAccount.tokens"
101
        clientIDField                  = "clientID"
102
        tenantIDField                  = "tenantID"
103
        mountOptionsField              = "mountoptions"
104
        falseValue                     = "false"
105
        trueValue                      = "true"
106
        defaultSecretAccountName       = "azurestorageaccountname"
107
        defaultSecretAccountKey        = "azurestorageaccountkey"
108
        accountSasTokenField           = "azurestorageaccountsastoken"
109
        msiSecretField                 = "msisecret"
110
        storageSPNClientSecretField    = "azurestoragespnclientsecret"
111
        Fuse                           = "fuse"
112
        Fuse2                          = "fuse2"
113
        NFS                            = "nfs"
114
        AZNFS                          = "aznfs"
115
        NFSv3                          = "nfsv3"
116
        vnetResourceGroupField         = "vnetresourcegroup"
117
        vnetNameField                  = "vnetname"
118
        subnetNameField                = "subnetname"
119
        accessTierField                = "accesstier"
120
        networkEndpointTypeField       = "networkendpointtype"
121
        mountPermissionsField          = "mountpermissions"
122
        useDataPlaneAPIField           = "usedataplaneapi"
123

124
        // See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
125
        containerNameMinLength = 3
126
        containerNameMaxLength = 63
127

128
        accountNotProvisioned                   = "StorageAccountIsNotProvisioned"
129
        tooManyRequests                         = "TooManyRequests"
130
        clientThrottled                         = "client throttled"
131
        containerBeingDeletedDataplaneAPIError  = "ContainerBeingDeleted"
132
        containerBeingDeletedManagementAPIError = "container is being deleted"
133
        statusCodeNotFound                      = "StatusCode=404"
134
        httpCodeNotFound                        = "HTTPStatusCode: 404"
135

136
        // 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
137
        containerMaxSize = 100 * util.TiB
138

139
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
140

141
        defaultNamespace = "default"
142

143
        pvcNameKey           = "csi.storage.k8s.io/pvc/name"
144
        pvcNamespaceKey      = "csi.storage.k8s.io/pvc/namespace"
145
        pvNameKey            = "csi.storage.k8s.io/pv/name"
146
        pvcNameMetadata      = "${pvc.metadata.name}"
147
        pvcNamespaceMetadata = "${pvc.metadata.namespace}"
148
        pvNameMetadata       = "${pv.metadata.name}"
149

150
        VolumeID = "volumeid"
151

152
        defaultStorageEndPointSuffix = "core.windows.net"
153
)
154

155
var (
156
        supportedProtocolList = []string{Fuse, Fuse2, NFS, AZNFS}
157
        retriableErrors       = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
158

159
        // azcopyCloneVolumeOptions used in volume cloning between different storage account and --check-length to false because volume data may be in changing state, copy volume is not same as current source volume,
160
        // set --s2s-preserve-access-tier=false to avoid BlobAccessTierNotSupportedForAccountType error in azcopy
161
        azcopyCloneVolumeOptions = []string{"--recursive", "--check-length=false", "--s2s-preserve-access-tier=false", "--log-level=ERROR"}
162
)
163

164
// DriverOptions defines driver parameters specified in driver deployment
165
type DriverOptions struct {
166
        NodeID                                 string
167
        DriverName                             string
168
        BlobfuseProxyEndpoint                  string
169
        EnableBlobfuseProxy                    bool
170
        BlobfuseProxyConnTimout                int
171
        EnableBlobMockMount                    bool
172
        AllowInlineVolumeKeyAccessWithIdentity bool
173
        EnableGetVolumeStats                   bool
174
        AppendTimeStampInCacheDir              bool
175
        AppendMountErrorHelpLink               bool
176
        MountPermissions                       uint64
177
        EnableAznfsMount                       bool
178
        VolStatsCacheExpireInMinutes           int
179
        SasTokenExpirationMinutes              int
180
        WaitForAzCopyTimeoutMinutes            int
181
}
182

183
func (option *DriverOptions) AddFlags() {
1✔
184
        flag.StringVar(&option.BlobfuseProxyEndpoint, "blobfuse-proxy-endpoint", "unix://tmp/blobfuse-proxy.sock", "blobfuse-proxy endpoint")
1✔
185
        flag.StringVar(&option.NodeID, "nodeid", "", "node id")
1✔
186
        flag.StringVar(&option.DriverName, "drivername", DefaultDriverName, "name of the driver")
1✔
187
        flag.BoolVar(&option.EnableBlobfuseProxy, "enable-blobfuse-proxy", false, "using blobfuse proxy for mounts")
1✔
188
        flag.IntVar(&option.BlobfuseProxyConnTimout, "blobfuse-proxy-connect-timeout", 5, "blobfuse proxy connection timeout(seconds)")
1✔
189
        flag.BoolVar(&option.EnableBlobMockMount, "enable-blob-mock-mount", false, "enable mock mount(only for testing)")
1✔
190
        flag.BoolVar(&option.EnableGetVolumeStats, "enable-get-volume-stats", false, "allow GET_VOLUME_STATS on agent node")
1✔
191
        flag.BoolVar(&option.AppendTimeStampInCacheDir, "append-timestamp-cache-dir", false, "append timestamp into cache directory on agent node")
1✔
192
        flag.Uint64Var(&option.MountPermissions, "mount-permissions", 0777, "mounted folder permissions")
1✔
193
        flag.BoolVar(&option.AllowInlineVolumeKeyAccessWithIdentity, "allow-inline-volume-key-access-with-idenitity", false, "allow accessing storage account key using cluster identity for inline volume")
1✔
194
        flag.BoolVar(&option.AppendMountErrorHelpLink, "append-mount-error-help-link", true, "Whether to include a link for help with mount errors when a mount error occurs.")
1✔
195
        flag.BoolVar(&option.EnableAznfsMount, "enable-aznfs-mount", false, "replace nfs mount with aznfs mount")
1✔
196
        flag.IntVar(&option.VolStatsCacheExpireInMinutes, "vol-stats-cache-expire-in-minutes", 10, "The cache expire time in minutes for volume stats cache")
1✔
197
        flag.IntVar(&option.SasTokenExpirationMinutes, "sas-token-expiration-minutes", 1440, "sas token expiration minutes during volume cloning")
1✔
198
        flag.IntVar(&option.WaitForAzCopyTimeoutMinutes, "wait-for-azcopy-timeout-minutes", 18, "timeout in minutes for waiting for azcopy to finish")
1✔
199
}
1✔
200

201
// Driver implements all interfaces of CSI drivers
202
type Driver struct {
203
        csicommon.CSIDriver
204

205
        cloud                 *azure.Cloud
206
        KubeClient            kubernetes.Interface
207
        blobfuseProxyEndpoint string
208
        // enableBlobMockMount is only for testing, DO NOT set as true in non-testing scenario
209
        enableBlobMockMount                    bool
210
        enableBlobfuseProxy                    bool
211
        enableGetVolumeStats                   bool
212
        allowInlineVolumeKeyAccessWithIdentity bool
213
        appendTimeStampInCacheDir              bool
214
        appendMountErrorHelpLink               bool
215
        blobfuseProxyConnTimout                int
216
        mountPermissions                       uint64
217
        enableAznfsMount                       bool
218
        mounter                                *mount.SafeFormatAndMount
219
        volLockMap                             *util.LockMap
220
        // A map storing all volumes with ongoing operations so that additional operations
221
        // for that same volume (as defined by VolumeID) return an Aborted error
222
        volumeLocks *volumeLocks
223
        // only for nfs feature
224
        subnetLockMap *util.LockMap
225
        // a map storing all volumes created by this driver <volumeName, accountName>
226
        volMap sync.Map
227
        // a timed cache storing all volumeIDs and storage accounts that are using data plane API
228
        dataPlaneAPIVolCache azcache.Resource
229
        // a timed cache storing account search history (solve account list throttling issue)
230
        accountSearchCache azcache.Resource
231
        // a timed cache storing volume stats <volumeID, volumeStats>
232
        volStatsCache azcache.Resource
233
        // a timed cache storing account which should use sastoken for azcopy based volume cloning
234
        azcopySasTokenCache azcache.Resource
235
        // sas expiry time for azcopy in volume clone
236
        sasTokenExpirationMinutes int
237
        // timeout in minutes for waiting for azcopy to finish
238
        waitForAzCopyTimeoutMinutes int
239
        // azcopy for provide exec mock for ut
240
        azcopy *util.Azcopy
241
}
242

243
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
244
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
245
func NewDriver(options *DriverOptions, kubeClient kubernetes.Interface, cloud *provider.Cloud) *Driver {
125✔
246
        var err error
125✔
247
        d := Driver{
125✔
248
                volLockMap:                             util.NewLockMap(),
125✔
249
                subnetLockMap:                          util.NewLockMap(),
125✔
250
                volumeLocks:                            newVolumeLocks(),
125✔
251
                blobfuseProxyEndpoint:                  options.BlobfuseProxyEndpoint,
125✔
252
                enableBlobfuseProxy:                    options.EnableBlobfuseProxy,
125✔
253
                allowInlineVolumeKeyAccessWithIdentity: options.AllowInlineVolumeKeyAccessWithIdentity,
125✔
254
                blobfuseProxyConnTimout:                options.BlobfuseProxyConnTimout,
125✔
255
                enableBlobMockMount:                    options.EnableBlobMockMount,
125✔
256
                enableGetVolumeStats:                   options.EnableGetVolumeStats,
125✔
257
                appendMountErrorHelpLink:               options.AppendMountErrorHelpLink,
125✔
258
                mountPermissions:                       options.MountPermissions,
125✔
259
                enableAznfsMount:                       options.EnableAznfsMount,
125✔
260
                sasTokenExpirationMinutes:              options.SasTokenExpirationMinutes,
125✔
261
                waitForAzCopyTimeoutMinutes:            options.WaitForAzCopyTimeoutMinutes,
125✔
262
                azcopy:                                 &util.Azcopy{},
125✔
263
                KubeClient:                             kubeClient,
125✔
264
                cloud:                                  cloud,
125✔
265
        }
125✔
266
        d.Name = options.DriverName
125✔
267
        d.Version = driverVersion
125✔
268
        d.NodeID = options.NodeID
125✔
269

125✔
270
        getter := func(_ string) (interface{}, error) { return nil, nil }
146✔
271
        if d.accountSearchCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
125✔
272
                klog.Fatalf("%v", err)
×
273
        }
×
274
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedCache(24*30*time.Hour, getter, false); err != nil {
125✔
275
                klog.Fatalf("%v", err)
×
276
        }
×
277
        if d.azcopySasTokenCache, err = azcache.NewTimedCache(15*time.Minute, getter, false); err != nil {
125✔
278
                klog.Fatalf("%v", err)
×
279
        }
×
280

281
        if options.VolStatsCacheExpireInMinutes <= 0 {
250✔
282
                options.VolStatsCacheExpireInMinutes = 10 // default expire in 10 minutes
125✔
283
        }
125✔
284
        if d.volStatsCache, err = azcache.NewTimedCache(time.Duration(options.VolStatsCacheExpireInMinutes)*time.Minute, getter, false); err != nil {
125✔
285
                klog.Fatalf("%v", err)
×
286
        }
×
287
        d.mounter = &mount.SafeFormatAndMount{
125✔
288
                Interface: mount.New(""),
125✔
289
                Exec:      utilexec.New(),
125✔
290
        }
125✔
291

125✔
292
        // Initialize default library driver
125✔
293
        d.AddControllerServiceCapabilities(
125✔
294
                []csi.ControllerServiceCapability_RPC_Type{
125✔
295
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
125✔
296
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
125✔
297
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
125✔
298
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
125✔
299
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
125✔
300
                        csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
125✔
301
                })
125✔
302
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
125✔
303
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
125✔
304
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
125✔
305
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
125✔
306
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
125✔
307
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
125✔
308
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
125✔
309
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
125✔
310
        })
125✔
311

125✔
312
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
125✔
313
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
125✔
314
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
125✔
315
        }
125✔
316
        if d.enableGetVolumeStats {
125✔
317
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
318
        }
×
319
        d.AddNodeServiceCapabilities(nodeCap)
125✔
320

125✔
321
        return &d
125✔
322
}
323

324
// Run driver initialization
325
func (d *Driver) Run(ctx context.Context, endpoint string) error {
2✔
326
        versionMeta, err := GetVersionYAML(d.Name)
2✔
327
        if err != nil {
2✔
328
                klog.Fatalf("%v", err)
×
329
        }
×
330
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
331
        grpcInterceptor := grpc.UnaryInterceptor(csicommon.LogGRPC)
2✔
332
        opts := []grpc.ServerOption{
2✔
333
                grpcInterceptor,
2✔
334
        }
2✔
335
        s := grpc.NewServer(opts...)
2✔
336
        csi.RegisterIdentityServer(s, d)
2✔
337
        csi.RegisterControllerServer(s, d)
2✔
338
        csi.RegisterNodeServer(s, d)
2✔
339

2✔
340
        go func() {
4✔
341
                //graceful shutdown
2✔
342
                <-ctx.Done()
2✔
343
                s.GracefulStop()
2✔
344
        }()
2✔
345
        // Driver d act as IdentityServer, ControllerServer and NodeServer
346
        listener, err := csicommon.Listen(ctx, endpoint)
2✔
347
        if err != nil {
2✔
348
                klog.Fatalf("failed to listen to endpoint, error: %v", err)
×
349
        }
×
350
        err = s.Serve(listener)
2✔
351
        if errors.Is(err, grpc.ErrServerStopped) {
2✔
352
                klog.Infof("gRPC server stopped serving")
×
353
                return nil
×
354
        }
×
355
        return err
2✔
356
}
357

358
// GetContainerInfo get container info according to volume id
359
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
360
//
361
// e.g.
362
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
363
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
364
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
365
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
366
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
367
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
368
func GetContainerInfo(id string) (string, string, string, string, string, error) {
40✔
369
        segments := strings.Split(id, separator)
40✔
370
        if len(segments) < 3 {
49✔
371
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
9✔
372
        }
9✔
373
        var secretNamespace, subsID string
31✔
374
        if len(segments) > 4 {
37✔
375
                secretNamespace = segments[4]
6✔
376
        }
6✔
377
        if len(segments) > 5 {
34✔
378
                subsID = segments[5]
3✔
379
        }
3✔
380
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
31✔
381
}
382

383
// A container name must be a valid DNS name, conforming to the following naming rules:
384
//  1. Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
385
//  2. Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
386
//  3. All letters in a container name must be lowercase.
387
//  4. Container names must be from 3 through 63 characters long.
388
//
389
// See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
390
func getValidContainerName(volumeName, protocol string) string {
6✔
391
        containerName := strings.ToLower(volumeName)
6✔
392
        if len(containerName) > containerNameMaxLength {
7✔
393
                containerName = containerName[0:containerNameMaxLength]
1✔
394
        }
1✔
395
        if !checkContainerNameBeginAndEnd(containerName) || len(containerName) < containerNameMinLength {
6✔
396
                // now we set as 63 for maximum container name length
×
397
                // todo: get cluster name
×
398
                containerName = k8sutil.GenerateVolumeName(fmt.Sprintf("pvc-%s", protocol), uuid.NewUUID().String(), 63)
×
399
                klog.Warningf("requested volume name (%s) is invalid, regenerated as (%q)", volumeName, containerName)
×
400
        }
×
401
        return strings.Replace(containerName, "--", "-", -1)
6✔
402
}
403

404
func checkContainerNameBeginAndEnd(containerName string) bool {
12✔
405
        length := len(containerName)
12✔
406
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
12✔
407
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
12✔
408
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
12✔
409
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
22✔
410
                return true
10✔
411
        }
10✔
412

413
        return false
2✔
414
}
415

416
// isSASToken checks if the key contains the patterns.
417
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
418
func isSASToken(key string) bool {
3✔
419
        return strings.HasPrefix(key, "?")
3✔
420
}
3✔
421

422
// GetAuthEnv return <accountName, containerName, authEnv, error>
423
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
10✔
424
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
10✔
425
        if err != nil {
12✔
426
                // ignore volumeID parsing error
2✔
427
                klog.V(2).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
2✔
428
                err = nil
2✔
429
        }
2✔
430

431
        var (
10✔
432
                subsID                  string
10✔
433
                accountKey              string
10✔
434
                accountSasToken         string
10✔
435
                msiSecret               string
10✔
436
                storageSPNClientSecret  string
10✔
437
                storageSPNClientID      string
10✔
438
                storageSPNTenantID      string
10✔
439
                secretName              string
10✔
440
                pvcNamespace            string
10✔
441
                keyVaultURL             string
10✔
442
                keyVaultSecretName      string
10✔
443
                keyVaultSecretVersion   string
10✔
444
                azureStorageAuthType    string
10✔
445
                authEnv                 []string
10✔
446
                getAccountKeyFromSecret bool
10✔
447
                getLatestAccountKey     bool
10✔
448
                clientID                string
10✔
449
                tenantID                string
10✔
450
                serviceAccountToken     string
10✔
451
        )
10✔
452

10✔
453
        for k, v := range attrib {
36✔
454
                switch strings.ToLower(k) {
26✔
455
                case subscriptionIDField:
1✔
456
                        subsID = v
1✔
457
                case resourceGroupField:
×
458
                        rgName = v
×
459
                case containerNameField:
3✔
460
                        containerName = v
3✔
461
                case keyVaultURLField:
1✔
462
                        keyVaultURL = v
1✔
463
                case keyVaultSecretNameField:
1✔
464
                        keyVaultSecretName = v
1✔
465
                case keyVaultSecretVersionField:
1✔
466
                        keyVaultSecretVersion = v
1✔
467
                case storageAccountField:
2✔
468
                        accountName = v
2✔
469
                case storageAccountNameField: // for compatibility
1✔
470
                        accountName = v
1✔
471
                case secretNameField:
1✔
472
                        secretName = v
1✔
473
                case secretNamespaceField:
1✔
474
                        secretNamespace = v
1✔
475
                case pvcNamespaceKey:
1✔
476
                        pvcNamespace = v
1✔
477
                case getAccountKeyFromSecretField:
1✔
478
                        getAccountKeyFromSecret = strings.EqualFold(v, trueValue)
1✔
479
                case storageAuthTypeField:
×
480
                        azureStorageAuthType = v
×
481
                        authEnv = append(authEnv, "AZURE_STORAGE_AUTH_TYPE="+v)
×
482
                case storageIentityClientIDField:
1✔
483
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_CLIENT_ID="+v)
1✔
484
                case storageIdentityObjectIDField:
1✔
485
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_OBJECT_ID="+v)
1✔
486
                case storageIdentityResourceIDField:
1✔
487
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_RESOURCE_ID="+v)
1✔
488
                case msiEndpointField:
1✔
489
                        authEnv = append(authEnv, "MSI_ENDPOINT="+v)
1✔
490
                case storageSPNClientIDField:
1✔
491
                        storageSPNClientID = v
1✔
492
                case storageSPNTenantIDField:
1✔
493
                        storageSPNTenantID = v
1✔
494
                case storageAADEndpointField:
1✔
495
                        authEnv = append(authEnv, "AZURE_STORAGE_AAD_ENDPOINT="+v)
1✔
496
                case getLatestAccountKeyField:
1✔
497
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
498
                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
499
                        }
1✔
500
                case strings.ToLower(clientIDField):
×
501
                        clientID = v
×
502
                case strings.ToLower(tenantIDField):
×
503
                        tenantID = v
×
504
                case strings.ToLower(serviceAccountTokenField):
×
505
                        serviceAccountToken = v
×
506
                }
507
        }
508
        klog.V(2).Infof("volumeID(%s) authEnv: %s", volumeID, authEnv)
9✔
509

9✔
510
        if protocol == NFS {
11✔
511
                // nfs protocol does not need account key, return directly
2✔
512
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
513
        }
2✔
514

515
        if secretNamespace == "" {
13✔
516
                if pvcNamespace == "" {
12✔
517
                        secretNamespace = defaultNamespace
6✔
518
                } else {
6✔
519
                        secretNamespace = pvcNamespace
×
520
                }
×
521
        }
522

523
        if rgName == "" {
8✔
524
                rgName = d.cloud.ResourceGroup
1✔
525
        }
1✔
526

527
        if tenantID == "" {
14✔
528
                tenantID = d.cloud.TenantID
7✔
529
        }
7✔
530

531
        // if client id is specified, we only use service account token to get account key
532
        if clientID != "" {
7✔
533
                klog.V(2).Infof("clientID(%s) is specified, use service account token to get account key", clientID)
×
534
                if subsID == "" {
×
535
                        subsID = d.cloud.SubscriptionID
×
536
                }
×
537
                accountKey, err := d.cloud.GetStorageAccesskeyFromServiceAccountToken(ctx, subsID, accountName, rgName, clientID, tenantID, serviceAccountToken)
×
538
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
×
539
                return rgName, accountName, accountKey, containerName, authEnv, err
×
540
        }
541

542
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
543
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
544
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
545
        if keyVaultURL != "" {
8✔
546
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
1✔
547
                if err != nil {
2✔
548
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
549
                }
1✔
550
                if isSASToken(key) {
×
551
                        accountSasToken = key
×
552
                } else {
×
553
                        accountKey = key
×
554
                }
×
555
        } else {
6✔
556
                if len(secrets) == 0 {
11✔
557
                        if secretName == "" && accountName != "" {
9✔
558
                                secretName = fmt.Sprintf(secretNameTemplate, accountName)
4✔
559
                        }
4✔
560
                        if secretName != "" {
10✔
561
                                // read from k8s secret first
5✔
562
                                var name, spnClientID, spnTenantID string
5✔
563
                                name, accountKey, accountSasToken, msiSecret, storageSPNClientSecret, spnClientID, spnTenantID, err = d.GetInfoFromSecret(ctx, secretName, secretNamespace)
5✔
564
                                if name != "" {
5✔
565
                                        accountName = name
×
566
                                }
×
567
                                if spnClientID != "" {
5✔
568
                                        storageSPNClientID = spnClientID
×
569
                                }
×
570
                                if spnTenantID != "" {
5✔
571
                                        storageSPNTenantID = spnTenantID
×
572
                                }
×
573
                                if err != nil && strings.EqualFold(azureStorageAuthType, "msi") {
5✔
574
                                        klog.V(2).Infof("ignore error(%v) since secret is optional for auth type(%s)", err, azureStorageAuthType)
×
575
                                        err = nil
×
576
                                }
×
577
                                if err != nil && !getAccountKeyFromSecret && (azureStorageAuthType == "" || strings.EqualFold(azureStorageAuthType, "key")) {
10✔
578
                                        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✔
579
                                                accountName, secretNamespace, secretName, err)
5✔
580
                                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
5✔
581
                                        if err != nil {
7✔
582
                                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
2✔
583
                                        }
2✔
584
                                }
585
                        }
586
                } else {
1✔
587
                        for k, v := range secrets {
8✔
588
                                v = strings.TrimSpace(v)
7✔
589
                                switch strings.ToLower(k) {
7✔
590
                                case accountNameField:
1✔
591
                                        accountName = v
1✔
592
                                case defaultSecretAccountName: // for compatibility with built-in blobfuse plugin
1✔
593
                                        accountName = v
1✔
594
                                case accountKeyField:
1✔
595
                                        accountKey = v
1✔
596
                                case defaultSecretAccountKey: // for compatibility with built-in blobfuse plugin
1✔
597
                                        accountKey = v
1✔
598
                                case accountSasTokenField:
1✔
599
                                        accountSasToken = v
1✔
600
                                case msiSecretField:
1✔
601
                                        msiSecret = v
1✔
602
                                case storageSPNClientSecretField:
1✔
603
                                        storageSPNClientSecret = v
1✔
604
                                case storageSPNClientIDField:
×
605
                                        storageSPNClientID = v
×
606
                                case storageSPNTenantIDField:
×
607
                                        storageSPNTenantID = v
×
608
                                }
609
                        }
610
                }
611
        }
612

613
        if containerName == "" {
4✔
614
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
615
        }
×
616

617
        if accountKey != "" {
8✔
618
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
619
        }
4✔
620

621
        if accountSasToken != "" {
5✔
622
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
623
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
624
        }
1✔
625

626
        if msiSecret != "" {
5✔
627
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
628
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
629
        }
1✔
630

631
        if storageSPNClientSecret != "" {
5✔
632
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
633
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
634
        }
1✔
635

636
        if storageSPNClientID != "" {
4✔
637
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
638
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
639
        }
×
640

641
        if storageSPNTenantID != "" {
4✔
642
                klog.V(2).Infof("storageSPNTenantID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNTenantID, accountName, containerName)
×
643
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+storageSPNTenantID)
×
644
        }
×
645

646
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
647
}
648

649
// GetStorageAccountAndContainer get storage account and container info
650
// returns <accountName, accountKey, accountSasToken, containerName>
651
// only for e2e testing
652
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
653
        var (
3✔
654
                subsID                string
3✔
655
                accountName           string
3✔
656
                accountKey            string
3✔
657
                accountSasToken       string
3✔
658
                containerName         string
3✔
659
                keyVaultURL           string
3✔
660
                keyVaultSecretName    string
3✔
661
                keyVaultSecretVersion string
3✔
662
                getLatestAccountKey   bool
3✔
663
                err                   error
3✔
664
        )
3✔
665

3✔
666
        for k, v := range attrib {
8✔
667
                switch strings.ToLower(k) {
5✔
668
                case subscriptionIDField:
×
669
                        subsID = v
×
670
                case containerNameField:
1✔
671
                        containerName = v
1✔
672
                case keyVaultURLField:
×
673
                        keyVaultURL = v
×
674
                case keyVaultSecretNameField:
1✔
675
                        keyVaultSecretName = v
1✔
676
                case keyVaultSecretVersionField:
1✔
677
                        keyVaultSecretVersion = v
1✔
678
                case storageAccountField:
×
679
                        accountName = v
×
680
                case storageAccountNameField: // for compatibility
1✔
681
                        accountName = v
1✔
682
                case getLatestAccountKeyField:
1✔
683
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
684
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
685
                        }
1✔
686
                }
687
        }
688

689
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
690
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
691
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
692
        if keyVaultURL != "" {
2✔
693
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
694
                if err != nil {
×
695
                        return "", "", "", "", err
×
696
                }
×
697
                if isSASToken(key) {
×
698
                        accountSasToken = key
×
699
                } else {
×
700
                        accountKey = key
×
701
                }
×
702
        } else {
2✔
703
                if len(secrets) == 0 {
4✔
704
                        var rgName string
2✔
705
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
706
                        if err != nil {
2✔
707
                                return "", "", "", "", err
×
708
                        }
×
709

710
                        if rgName == "" {
2✔
711
                                rgName = d.cloud.ResourceGroup
×
712
                        }
×
713

714
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
715
                        if err != nil {
3✔
716
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
717
                        }
1✔
718
                }
719
        }
720

721
        if containerName == "" {
1✔
722
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
723
        }
×
724

725
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
726
}
727

728
func IsCorruptedDir(dir string) bool {
4✔
729
        _, pathErr := mount.PathExists(dir)
4✔
730
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
731
}
4✔
732

733
func isRetriableError(err error) bool {
5✔
734
        if err != nil {
9✔
735
                for _, v := range retriableErrors {
19✔
736
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
737
                                return true
3✔
738
                        }
3✔
739
                }
740
        }
741
        return false
2✔
742
}
743

744
func isSupportedProtocol(protocol string) bool {
18✔
745
        if protocol == "" {
19✔
746
                return true
1✔
747
        }
1✔
748
        for _, v := range supportedProtocolList {
44✔
749
                if protocol == v || protocol == NFSv3 {
42✔
750
                        return true
15✔
751
                }
15✔
752
        }
753
        return false
2✔
754
}
755

756
func isSupportedAccessTier(accessTier string) bool {
21✔
757
        if accessTier == "" {
35✔
758
                return true
14✔
759
        }
14✔
760
        for _, tier := range storage.PossibleAccessTierValues() {
25✔
761
                if accessTier == string(tier) {
21✔
762
                        return true
3✔
763
                }
3✔
764
        }
765
        return false
4✔
766
}
767

768
// container names can contain only lowercase letters, numbers, and hyphens,
769
// and must begin and end with a letter or a number
770
func isSupportedContainerNamePrefix(prefix string) bool {
20✔
771
        if prefix == "" {
32✔
772
                return true
12✔
773
        }
12✔
774
        if len(prefix) > 20 {
9✔
775
                return false
1✔
776
        }
1✔
777
        if prefix[0] == '-' {
8✔
778
                return false
1✔
779
        }
1✔
780
        for _, v := range prefix {
19✔
781
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
782
                        return false
4✔
783
                }
4✔
784
        }
785
        return true
2✔
786
}
787

788
// isNFSProtocol checks if the protocol is NFS or AZNFS
789
func isNFSProtocol(protocol string) bool {
19✔
790
        protocol = strings.ToLower(protocol)
19✔
791
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
19✔
792
}
19✔
793

794
// get storage account from secrets map
795
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
796
        if secrets == nil {
23✔
797
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
798
        }
1✔
799

800
        var accountName, accountKey string
21✔
801
        for k, v := range secrets {
64✔
802
                v = strings.TrimSpace(v)
43✔
803
                switch strings.ToLower(k) {
43✔
804
                case accountNameField:
7✔
805
                        accountName = v
7✔
806
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
13✔
807
                        accountName = v
13✔
808
                case accountKeyField:
7✔
809
                        accountKey = v
7✔
810
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
12✔
811
                        accountKey = v
12✔
812
                }
813
        }
814

815
        if accountName == "" {
25✔
816
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
817
        }
4✔
818
        if accountKey == "" {
21✔
819
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
820
        }
4✔
821

822
        accountName = strings.TrimSpace(accountName)
13✔
823
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
824
        return accountName, accountKey, nil
13✔
825
}
826

827
func getContainerReference(containerName string, secrets map[string]string, env az.Environment) (*azstorage.Container, error) {
9✔
828
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
829
        if rerr != nil {
11✔
830
                return nil, rerr
2✔
831
        }
2✔
832
        client, err := azstorage.NewBasicClientOnSovereignCloud(accountName, accountKey, env)
7✔
833
        if err != nil {
13✔
834
                return nil, err
6✔
835
        }
6✔
836
        blobClient := client.GetBlobService()
1✔
837
        container := blobClient.GetContainerReference(containerName)
1✔
838
        if container == nil {
1✔
839
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
840
        }
×
841
        return container, nil
1✔
842
}
843

844
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
845
        if kubeClient == nil {
8✔
846
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
847
                return "", nil
2✔
848
        }
2✔
849
        if accountName == "" || accountKey == "" {
6✔
850
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
851
        }
2✔
852
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
853
        secret := &v1.Secret{
2✔
854
                ObjectMeta: metav1.ObjectMeta{
2✔
855
                        Namespace: secretNamespace,
2✔
856
                        Name:      secretName,
2✔
857
                },
2✔
858
                Data: map[string][]byte{
2✔
859
                        defaultSecretAccountName: []byte(accountName),
2✔
860
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
861
                },
2✔
862
                Type: "Opaque",
2✔
863
        }
2✔
864
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
865
        if apierror.IsAlreadyExists(err) {
3✔
866
                err = nil
1✔
867
        }
1✔
868
        if err != nil {
2✔
869
                return "", fmt.Errorf("couldn't create secret %w", err)
×
870
        }
×
871
        return secretName, err
2✔
872
}
873

874
// GetStorageAccesskey get Azure storage account key from
875
//  1. secrets (if not empty)
876
//  2. use k8s client identity to read from k8s secret
877
//  3. use cluster identity to get from storage account directly
878
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *azure.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
12✔
879
        if len(secrets) > 0 {
17✔
880
                return getStorageAccount(secrets)
5✔
881
        }
5✔
882

883
        // read from k8s secret first
884
        if secretName == "" {
12✔
885
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
5✔
886
        }
5✔
887
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
7✔
888
        if err != nil {
12✔
889
                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)
5✔
890
                accountKey, err = d.cloud.GetStorageAccesskey(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
5✔
891
        }
5✔
892
        return accountOptions.Name, accountKey, err
7✔
893
}
894

895
// GetInfoFromSecret get info from k8s secret
896
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
897
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
898
        if d.cloud.KubeClient == nil {
26✔
899
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
900
        }
10✔
901

902
        secret, err := d.cloud.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
903
        if err != nil {
8✔
904
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
905
        }
2✔
906

907
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
908
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
909
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
910
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
911
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
912
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
913
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
914

4✔
915
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
916
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
917
}
918

919
// getSubnetResourceID get default subnet resource ID from cloud provider config
920
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
921
        subsID := d.cloud.SubscriptionID
6✔
922
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
923
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
924
        }
4✔
925

926
        if len(vnetResourceGroup) == 0 {
11✔
927
                vnetResourceGroup = d.cloud.ResourceGroup
5✔
928
                if len(d.cloud.VnetResourceGroup) > 0 {
8✔
929
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
930
                }
3✔
931
        }
932

933
        if len(vnetName) == 0 {
11✔
934
                vnetName = d.cloud.VnetName
5✔
935
        }
5✔
936

937
        if len(subnetName) == 0 {
11✔
938
                subnetName = d.cloud.SubnetName
5✔
939
        }
5✔
940
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
941
}
942

943
func (d *Driver) useDataPlaneAPI(volumeID, accountName string) bool {
9✔
944
        cache, err := d.dataPlaneAPIVolCache.Get(volumeID, azcache.CacheReadTypeDefault)
9✔
945
        if err != nil {
9✔
946
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
947
        }
×
948
        if cache != nil {
12✔
949
                return true
3✔
950
        }
3✔
951
        cache, err = d.dataPlaneAPIVolCache.Get(accountName, azcache.CacheReadTypeDefault)
6✔
952
        if err != nil {
6✔
953
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
954
        }
×
955
        if cache != nil {
6✔
956
                return true
×
957
        }
×
958
        return false
6✔
959
}
960

961
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
962
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
963
        var defaultMountOptions = map[string]string{
4✔
964
                "--pre-mount-validate": "true",
4✔
965
                "--use-https":          "true",
4✔
966
                "--tmp-path":           tmpPath,
4✔
967
                "--container-name":     containerName,
4✔
968
                // prevent billing charges on mounting
4✔
969
                "--cancel-list-on-mount-seconds": "10",
4✔
970
                // allow remounting using a non-empty tmp-path
4✔
971
                "--empty-dir-check": "false",
4✔
972
        }
4✔
973

4✔
974
        // stores the mount options already included in mountOptions
4✔
975
        included := make(map[string]bool)
4✔
976

4✔
977
        for _, mountOption := range mountOptions {
11✔
978
                for k := range defaultMountOptions {
49✔
979
                        if strings.HasPrefix(mountOption, k) {
46✔
980
                                included[k] = true
4✔
981
                        }
4✔
982
                }
983
        }
984

985
        allMountOptions := mountOptions
4✔
986

4✔
987
        for k, v := range defaultMountOptions {
28✔
988
                if _, isIncluded := included[k]; !isIncluded {
44✔
989
                        if v != "" {
40✔
990
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
991
                        } else {
20✔
992
                                allMountOptions = append(allMountOptions, k)
×
993
                        }
×
994
                }
995
        }
996

997
        return allMountOptions
4✔
998
}
999

1000
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1001
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
1002
        info, err := os.Lstat(targetPath)
3✔
1003
        if err != nil {
4✔
1004
                return err
1✔
1005
        }
1✔
1006
        perm := info.Mode() & os.ModePerm
2✔
1007
        if perm != mode {
3✔
1008
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
1009
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
1010
                        return err
×
1011
                }
×
1012
        } else {
1✔
1013
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
1014
        }
1✔
1015
        return nil
2✔
1016
}
1017

1018
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1019
        secret := make(map[string]string)
1✔
1020
        secret[defaultSecretAccountName] = account
1✔
1021
        secret[defaultSecretAccountKey] = key
1✔
1022
        return secret
1✔
1023
}
1✔
1024

1025
// setKeyValueInMap set key/value pair in map
1026
// key in the map is case insensitive, if key already exists, overwrite existing value
1027
func setKeyValueInMap(m map[string]string, key, value string) {
7✔
1028
        if m == nil {
8✔
1029
                return
1✔
1030
        }
1✔
1031
        for k := range m {
17✔
1032
                if strings.EqualFold(k, key) {
13✔
1033
                        m[k] = value
2✔
1034
                        return
2✔
1035
                }
2✔
1036
        }
1037
        m[key] = value
4✔
1038
}
1039

1040
// getValueInMap get value from map by key
1041
// key in the map is case insensitive
1042
func getValueInMap(m map[string]string, key string) string {
11✔
1043
        if m == nil {
12✔
1044
                return ""
1✔
1045
        }
1✔
1046
        for k, v := range m {
21✔
1047
                if strings.EqualFold(k, key) {
15✔
1048
                        return v
4✔
1049
                }
4✔
1050
        }
1051
        return ""
6✔
1052
}
1053

1054
// replaceWithMap replace key with value for str
1055
func replaceWithMap(str string, m map[string]string) string {
15✔
1056
        for k, v := range m {
20✔
1057
                if k != "" {
9✔
1058
                        str = strings.ReplaceAll(str, k, v)
4✔
1059
                }
4✔
1060
        }
1061
        return str
15✔
1062
}
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