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

kubernetes-sigs / blob-csi-driver / 10213342106

02 Aug 2024 09:30AM UTC coverage: 74.235% (+1.0%) from 73.249%
10213342106

Pull #1532

github

k8s-infra-cherrypick-robot
fix volume cloning and add e2e
Pull Request #1532: [release-1.24] cleanup: refactor volume cloning

37 of 51 new or added lines in 1 file covered. (72.55%)

1 existing line in 1 file now uncovered.

2233 of 3008 relevant lines covered (74.24%)

7.06 hits per line

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

84.85
/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/sdk/resourcemanager/storage/armstorage"
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
        "sigs.k8s.io/cloud-provider-azure/pkg/azclient"
48
        azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
49
        "sigs.k8s.io/cloud-provider-azure/pkg/provider"
50
        azure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
51
)
52

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

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

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

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

141
        subnetTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s/subnets/%s"
142

143
        defaultNamespace = "default"
144

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

152
        VolumeID = "volumeid"
153

154
        defaultStorageEndPointSuffix = "core.windows.net"
155

156
        FSGroupChangeNone = "None"
157
        // define tag value delimiter and default is comma
158
        tagValueDelimiterField = "tagValueDelimiter"
159
)
160

161
var (
162
        supportedProtocolList            = []string{Fuse, Fuse2, NFS, AZNFS}
163
        retriableErrors                  = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
164
        supportedFSGroupChangePolicyList = []string{FSGroupChangeNone, string(v1.FSGroupChangeAlways), string(v1.FSGroupChangeOnRootMismatch)}
165

166
        // 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,
167
        // set --s2s-preserve-access-tier=false to avoid BlobAccessTierNotSupportedForAccountType error in azcopy
168
        azcopyCloneVolumeOptions = []string{"--recursive", "--check-length=false", "--s2s-preserve-access-tier=false"}
169
)
170

171
// DriverOptions defines driver parameters specified in driver deployment
172
type DriverOptions struct {
173
        NodeID                                 string
174
        DriverName                             string
175
        BlobfuseProxyEndpoint                  string
176
        EnableBlobfuseProxy                    bool
177
        BlobfuseProxyConnTimout                int
178
        EnableBlobMockMount                    bool
179
        AllowInlineVolumeKeyAccessWithIdentity bool
180
        EnableGetVolumeStats                   bool
181
        AppendTimeStampInCacheDir              bool
182
        AppendMountErrorHelpLink               bool
183
        MountPermissions                       uint64
184
        EnableAznfsMount                       bool
185
        VolStatsCacheExpireInMinutes           int
186
        SasTokenExpirationMinutes              int
187
        WaitForAzCopyTimeoutMinutes            int
188
        EnableVolumeMountGroup                 bool
189
        FSGroupChangePolicy                    string
190
}
191

192
func (option *DriverOptions) AddFlags() {
1✔
193
        flag.StringVar(&option.BlobfuseProxyEndpoint, "blobfuse-proxy-endpoint", "unix://tmp/blobfuse-proxy.sock", "blobfuse-proxy endpoint")
1✔
194
        flag.StringVar(&option.NodeID, "nodeid", "", "node id")
1✔
195
        flag.StringVar(&option.DriverName, "drivername", DefaultDriverName, "name of the driver")
1✔
196
        flag.BoolVar(&option.EnableBlobfuseProxy, "enable-blobfuse-proxy", false, "using blobfuse proxy for mounts")
1✔
197
        flag.IntVar(&option.BlobfuseProxyConnTimout, "blobfuse-proxy-connect-timeout", 5, "blobfuse proxy connection timeout(seconds)")
1✔
198
        flag.BoolVar(&option.EnableBlobMockMount, "enable-blob-mock-mount", false, "enable mock mount(only for testing)")
1✔
199
        flag.BoolVar(&option.EnableGetVolumeStats, "enable-get-volume-stats", false, "allow GET_VOLUME_STATS on agent node")
1✔
200
        flag.BoolVar(&option.AppendTimeStampInCacheDir, "append-timestamp-cache-dir", false, "append timestamp into cache directory on agent node")
1✔
201
        flag.Uint64Var(&option.MountPermissions, "mount-permissions", 0777, "mounted folder permissions")
1✔
202
        flag.BoolVar(&option.AllowInlineVolumeKeyAccessWithIdentity, "allow-inline-volume-key-access-with-idenitity", false, "allow accessing storage account key using cluster identity for inline volume")
1✔
203
        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✔
204
        flag.BoolVar(&option.EnableAznfsMount, "enable-aznfs-mount", false, "replace nfs mount with aznfs mount")
1✔
205
        flag.IntVar(&option.VolStatsCacheExpireInMinutes, "vol-stats-cache-expire-in-minutes", 10, "The cache expire time in minutes for volume stats cache")
1✔
206
        flag.IntVar(&option.SasTokenExpirationMinutes, "sas-token-expiration-minutes", 1440, "sas token expiration minutes during volume cloning")
1✔
207
        flag.IntVar(&option.WaitForAzCopyTimeoutMinutes, "wait-for-azcopy-timeout-minutes", 18, "timeout in minutes for waiting for azcopy to finish")
1✔
208
        flag.BoolVar(&option.EnableVolumeMountGroup, "enable-volume-mount-group", true, "indicates whether enabling VOLUME_MOUNT_GROUP")
1✔
209
        flag.StringVar(&option.FSGroupChangePolicy, "fsgroup-change-policy", "", "indicates how the volume's ownership will be changed by the driver, OnRootMismatch is the default value")
1✔
210
}
1✔
211

212
// Driver implements all interfaces of CSI drivers
213
type Driver struct {
214
        csicommon.CSIDriver
215

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

258
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
259
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
260
func NewDriver(options *DriverOptions, kubeClient kubernetes.Interface, cloud *provider.Cloud) *Driver {
134✔
261
        d := Driver{
134✔
262
                volLockMap:                             util.NewLockMap(),
134✔
263
                subnetLockMap:                          util.NewLockMap(),
134✔
264
                volumeLocks:                            newVolumeLocks(),
134✔
265
                blobfuseProxyEndpoint:                  options.BlobfuseProxyEndpoint,
134✔
266
                enableBlobfuseProxy:                    options.EnableBlobfuseProxy,
134✔
267
                allowInlineVolumeKeyAccessWithIdentity: options.AllowInlineVolumeKeyAccessWithIdentity,
134✔
268
                blobfuseProxyConnTimout:                options.BlobfuseProxyConnTimout,
134✔
269
                enableBlobMockMount:                    options.EnableBlobMockMount,
134✔
270
                enableGetVolumeStats:                   options.EnableGetVolumeStats,
134✔
271
                enableVolumeMountGroup:                 options.EnableVolumeMountGroup,
134✔
272
                appendMountErrorHelpLink:               options.AppendMountErrorHelpLink,
134✔
273
                mountPermissions:                       options.MountPermissions,
134✔
274
                enableAznfsMount:                       options.EnableAznfsMount,
134✔
275
                sasTokenExpirationMinutes:              options.SasTokenExpirationMinutes,
134✔
276
                waitForAzCopyTimeoutMinutes:            options.WaitForAzCopyTimeoutMinutes,
134✔
277
                fsGroupChangePolicy:                    options.FSGroupChangePolicy,
134✔
278
                azcopy:                                 &util.Azcopy{},
134✔
279
                KubeClient:                             kubeClient,
134✔
280
                cloud:                                  cloud,
134✔
281
        }
134✔
282
        d.Name = options.DriverName
134✔
283
        d.Version = driverVersion
134✔
284
        d.NodeID = options.NodeID
134✔
285
        if d.cloud != nil {
268✔
286
                d.clientFactory = d.cloud.ComputeClientFactory
134✔
287
                d.networkClientFactory = d.cloud.NetworkClientFactory
134✔
288
        }
134✔
289

290
        var err error
134✔
291
        getter := func(key string) (interface{}, error) { return nil, nil }
158✔
292
        if d.accountSearchCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
134✔
293
                klog.Fatalf("%v", err)
×
294
        }
×
295
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedCache(24*30*time.Hour, getter, false); err != nil {
134✔
296
                klog.Fatalf("%v", err)
×
297
        }
×
298
        if d.azcopySasTokenCache, err = azcache.NewTimedCache(15*time.Minute, getter, false); err != nil {
134✔
299
                klog.Fatalf("%v", err)
×
300
        }
×
301

302
        if options.VolStatsCacheExpireInMinutes <= 0 {
268✔
303
                options.VolStatsCacheExpireInMinutes = 10 // default expire in 10 minutes
134✔
304
        }
134✔
305
        if d.volStatsCache, err = azcache.NewTimedCache(time.Duration(options.VolStatsCacheExpireInMinutes)*time.Minute, getter, false); err != nil {
134✔
306
                klog.Fatalf("%v", err)
×
307
        }
×
308
        d.mounter = &mount.SafeFormatAndMount{
134✔
309
                Interface: mount.New(""),
134✔
310
                Exec:      utilexec.New(),
134✔
311
        }
134✔
312

134✔
313
        // Initialize default library driver
134✔
314
        d.AddControllerServiceCapabilities(
134✔
315
                []csi.ControllerServiceCapability_RPC_Type{
134✔
316
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
134✔
317
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
134✔
318
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
134✔
319
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
134✔
320
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
134✔
321
                        csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
134✔
322
                })
134✔
323
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
134✔
324
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
134✔
325
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
134✔
326
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
134✔
327
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
134✔
328
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
134✔
329
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
134✔
330
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
134✔
331
        })
134✔
332

134✔
333
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
134✔
334
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
134✔
335
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
134✔
336
        }
134✔
337
        if d.enableGetVolumeStats {
134✔
338
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
339
        }
×
340
        if d.enableVolumeMountGroup {
134✔
341
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP)
×
342
        }
×
343
        d.AddNodeServiceCapabilities(nodeCap)
134✔
344

134✔
345
        return &d
134✔
346
}
347

348
// Run driver initialization
349
func (d *Driver) Run(ctx context.Context, endpoint string) error {
2✔
350
        versionMeta, err := GetVersionYAML(d.Name)
2✔
351
        if err != nil {
2✔
352
                klog.Fatalf("%v", err)
×
353
        }
×
354
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
355
        grpcInterceptor := grpc.UnaryInterceptor(csicommon.LogGRPC)
2✔
356
        opts := []grpc.ServerOption{
2✔
357
                grpcInterceptor,
2✔
358
        }
2✔
359
        s := grpc.NewServer(opts...)
2✔
360
        csi.RegisterIdentityServer(s, d)
2✔
361
        csi.RegisterControllerServer(s, d)
2✔
362
        csi.RegisterNodeServer(s, d)
2✔
363

2✔
364
        go func() {
4✔
365
                //graceful shutdown
2✔
366
                <-ctx.Done()
2✔
367
                s.GracefulStop()
2✔
368
        }()
2✔
369
        // Driver d act as IdentityServer, ControllerServer and NodeServer
370
        listener, err := csicommon.Listen(ctx, endpoint)
2✔
371
        if err != nil {
2✔
372
                klog.Fatalf("failed to listen to endpoint, error: %v", err)
×
373
        }
×
374
        err = s.Serve(listener)
2✔
375
        if errors.Is(err, grpc.ErrServerStopped) {
2✔
376
                klog.Infof("gRPC server stopped serving")
×
377
                return nil
×
378
        }
×
379
        return err
2✔
380
}
381

382
// GetContainerInfo get container info according to volume id
383
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
384
//
385
// e.g.
386
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
387
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
388
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
389
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
390
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
391
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
392
func GetContainerInfo(id string) (string, string, string, string, string, error) {
40✔
393
        segments := strings.Split(id, separator)
40✔
394
        if len(segments) < 3 {
49✔
395
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
9✔
396
        }
9✔
397
        var secretNamespace, subsID string
31✔
398
        if len(segments) > 4 {
37✔
399
                secretNamespace = segments[4]
6✔
400
        }
6✔
401
        if len(segments) > 5 {
34✔
402
                subsID = segments[5]
3✔
403
        }
3✔
404
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
31✔
405
}
406

407
// A container name must be a valid DNS name, conforming to the following naming rules:
408
//  1. Container names must start with a letter or number, and can contain only letters, numbers, and the dash (-) character.
409
//  2. Every dash (-) character must be immediately preceded and followed by a letter or number; consecutive dashes are not permitted in container names.
410
//  3. All letters in a container name must be lowercase.
411
//  4. Container names must be from 3 through 63 characters long.
412
//
413
// See https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names
414
func getValidContainerName(volumeName, protocol string) string {
6✔
415
        containerName := strings.ToLower(volumeName)
6✔
416
        if len(containerName) > containerNameMaxLength {
7✔
417
                containerName = containerName[0:containerNameMaxLength]
1✔
418
        }
1✔
419
        if !checkContainerNameBeginAndEnd(containerName) || len(containerName) < containerNameMinLength {
6✔
420
                // now we set as 63 for maximum container name length
×
421
                // todo: get cluster name
×
422
                containerName = k8sutil.GenerateVolumeName(fmt.Sprintf("pvc-%s", protocol), uuid.NewUUID().String(), 63)
×
423
                klog.Warningf("requested volume name (%s) is invalid, regenerated as (%q)", volumeName, containerName)
×
424
        }
×
425
        return strings.Replace(containerName, "--", "-", -1)
6✔
426
}
427

428
func checkContainerNameBeginAndEnd(containerName string) bool {
12✔
429
        length := len(containerName)
12✔
430
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
12✔
431
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
12✔
432
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
12✔
433
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
22✔
434
                return true
10✔
435
        }
10✔
436

437
        return false
2✔
438
}
439

440
// isSASToken checks if the key contains the patterns.
441
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
442
func isSASToken(key string) bool {
3✔
443
        return strings.HasPrefix(key, "?")
3✔
444
}
3✔
445

446
// GetAuthEnv return <accountName, containerName, authEnv, error>
447
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
10✔
448
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
10✔
449
        if err != nil {
12✔
450
                // ignore volumeID parsing error
2✔
451
                klog.V(2).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
2✔
452
                err = nil
2✔
453
        }
2✔
454

455
        var (
10✔
456
                subsID                  string
10✔
457
                accountKey              string
10✔
458
                accountSasToken         string
10✔
459
                msiSecret               string
10✔
460
                storageSPNClientSecret  string
10✔
461
                storageSPNClientID      string
10✔
462
                storageSPNTenantID      string
10✔
463
                secretName              string
10✔
464
                pvcNamespace            string
10✔
465
                keyVaultURL             string
10✔
466
                keyVaultSecretName      string
10✔
467
                keyVaultSecretVersion   string
10✔
468
                azureStorageAuthType    string
10✔
469
                authEnv                 []string
10✔
470
                getAccountKeyFromSecret bool
10✔
471
                getLatestAccountKey     bool
10✔
472
                clientID                string
10✔
473
                tenantID                string
10✔
474
                serviceAccountToken     string
10✔
475
        )
10✔
476

10✔
477
        for k, v := range attrib {
36✔
478
                switch strings.ToLower(k) {
26✔
479
                case subscriptionIDField:
1✔
480
                        subsID = v
1✔
481
                case resourceGroupField:
×
482
                        rgName = v
×
483
                case containerNameField:
3✔
484
                        containerName = v
3✔
485
                case keyVaultURLField:
1✔
486
                        keyVaultURL = v
1✔
487
                case keyVaultSecretNameField:
1✔
488
                        keyVaultSecretName = v
1✔
489
                case keyVaultSecretVersionField:
1✔
490
                        keyVaultSecretVersion = v
1✔
491
                case storageAccountField:
2✔
492
                        accountName = v
2✔
493
                case storageAccountNameField: // for compatibility
1✔
494
                        accountName = v
1✔
495
                case secretNameField:
1✔
496
                        secretName = v
1✔
497
                case secretNamespaceField:
1✔
498
                        secretNamespace = v
1✔
499
                case pvcNamespaceKey:
1✔
500
                        pvcNamespace = v
1✔
501
                case getAccountKeyFromSecretField:
1✔
502
                        getAccountKeyFromSecret = strings.EqualFold(v, trueValue)
1✔
503
                case storageAuthTypeField:
×
504
                        azureStorageAuthType = v
×
505
                        authEnv = append(authEnv, "AZURE_STORAGE_AUTH_TYPE="+v)
×
506
                case storageIdentityClientIDField:
1✔
507
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_CLIENT_ID="+v)
1✔
508
                case storageIdentityObjectIDField:
1✔
509
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_OBJECT_ID="+v)
1✔
510
                case storageIdentityResourceIDField:
1✔
511
                        authEnv = append(authEnv, "AZURE_STORAGE_IDENTITY_RESOURCE_ID="+v)
1✔
512
                case msiEndpointField:
1✔
513
                        authEnv = append(authEnv, "MSI_ENDPOINT="+v)
1✔
514
                case storageSPNClientIDField:
1✔
515
                        storageSPNClientID = v
1✔
516
                case storageSPNTenantIDField:
1✔
517
                        storageSPNTenantID = v
1✔
518
                case storageAADEndpointField:
1✔
519
                        authEnv = append(authEnv, "AZURE_STORAGE_AAD_ENDPOINT="+v)
1✔
520
                case getLatestAccountKeyField:
1✔
521
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
522
                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
523
                        }
1✔
524
                case strings.ToLower(clientIDField):
×
525
                        clientID = v
×
526
                case strings.ToLower(tenantIDField):
×
527
                        tenantID = v
×
528
                case strings.ToLower(serviceAccountTokenField):
×
529
                        serviceAccountToken = v
×
530
                }
531
        }
532
        klog.V(2).Infof("volumeID(%s) authEnv: %s", volumeID, authEnv)
9✔
533

9✔
534
        if protocol == NFS {
11✔
535
                // nfs protocol does not need account key, return directly
2✔
536
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
537
        }
2✔
538

539
        if secretNamespace == "" {
13✔
540
                if pvcNamespace == "" {
12✔
541
                        secretNamespace = defaultNamespace
6✔
542
                } else {
6✔
543
                        secretNamespace = pvcNamespace
×
544
                }
×
545
        }
546

547
        if rgName == "" {
8✔
548
                rgName = d.cloud.ResourceGroup
1✔
549
        }
1✔
550

551
        if tenantID == "" {
14✔
552
                tenantID = d.cloud.TenantID
7✔
553
        }
7✔
554

555
        // if client id is specified, we only use service account token to get account key
556
        if clientID != "" {
7✔
557
                klog.V(2).Infof("clientID(%s) is specified, use service account token to get account key", clientID)
×
558
                if subsID == "" {
×
559
                        subsID = d.cloud.SubscriptionID
×
560
                }
×
561
                accountKey, err := d.cloud.GetStorageAccesskeyFromServiceAccountToken(ctx, subsID, accountName, rgName, clientID, tenantID, serviceAccountToken)
×
562
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
×
563
                return rgName, accountName, accountKey, containerName, authEnv, err
×
564
        }
565

566
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
567
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
568
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
569
        if keyVaultURL != "" {
8✔
570
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
1✔
571
                if err != nil {
2✔
572
                        return rgName, accountName, accountKey, containerName, authEnv, err
1✔
573
                }
1✔
574
                if isSASToken(key) {
×
575
                        accountSasToken = key
×
576
                } else {
×
577
                        accountKey = key
×
578
                }
×
579
        } else {
6✔
580
                if len(secrets) == 0 {
11✔
581
                        if secretName == "" && accountName != "" {
9✔
582
                                secretName = fmt.Sprintf(secretNameTemplate, accountName)
4✔
583
                        }
4✔
584
                        if secretName != "" {
10✔
585
                                // read from k8s secret first
5✔
586
                                var name, spnClientID, spnTenantID string
5✔
587
                                name, accountKey, accountSasToken, msiSecret, storageSPNClientSecret, spnClientID, spnTenantID, err = d.GetInfoFromSecret(ctx, secretName, secretNamespace)
5✔
588
                                if name != "" {
5✔
589
                                        accountName = name
×
590
                                }
×
591
                                if spnClientID != "" {
5✔
592
                                        storageSPNClientID = spnClientID
×
593
                                }
×
594
                                if spnTenantID != "" {
5✔
595
                                        storageSPNTenantID = spnTenantID
×
596
                                }
×
597
                                if err != nil && strings.EqualFold(azureStorageAuthType, "msi") {
5✔
598
                                        klog.V(2).Infof("ignore error(%v) since secret is optional for auth type(%s)", err, azureStorageAuthType)
×
599
                                        err = nil
×
600
                                }
×
601
                                if err != nil && !getAccountKeyFromSecret && (azureStorageAuthType == "" || strings.EqualFold(azureStorageAuthType, "key")) {
10✔
602
                                        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✔
603
                                                accountName, secretNamespace, secretName, err)
5✔
604
                                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
5✔
605
                                        if err != nil {
7✔
606
                                                return rgName, accountName, accountKey, containerName, authEnv, fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
2✔
607
                                        }
2✔
608
                                }
609
                        }
610
                } else {
1✔
611
                        for k, v := range secrets {
8✔
612
                                v = strings.TrimSpace(v)
7✔
613
                                switch strings.ToLower(k) {
7✔
614
                                case accountNameField:
1✔
615
                                        accountName = v
1✔
616
                                case defaultSecretAccountName: // for compatibility with built-in blobfuse plugin
1✔
617
                                        accountName = v
1✔
618
                                case accountKeyField:
1✔
619
                                        accountKey = v
1✔
620
                                case defaultSecretAccountKey: // for compatibility with built-in blobfuse plugin
1✔
621
                                        accountKey = v
1✔
622
                                case accountSasTokenField:
1✔
623
                                        accountSasToken = v
1✔
624
                                case msiSecretField:
1✔
625
                                        msiSecret = v
1✔
626
                                case storageSPNClientSecretField:
1✔
627
                                        storageSPNClientSecret = v
1✔
628
                                case storageSPNClientIDField:
×
629
                                        storageSPNClientID = v
×
630
                                case storageSPNTenantIDField:
×
631
                                        storageSPNTenantID = v
×
632
                                }
633
                        }
634
                }
635
        }
636

637
        if containerName == "" {
4✔
638
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
639
        }
×
640

641
        if accountKey != "" {
8✔
642
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
643
        }
4✔
644

645
        if accountSasToken != "" {
5✔
646
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
647
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
648
        }
1✔
649

650
        if msiSecret != "" {
5✔
651
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
652
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
653
        }
1✔
654

655
        if storageSPNClientSecret != "" {
5✔
656
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
657
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
658
        }
1✔
659

660
        if storageSPNClientID != "" {
4✔
661
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
662
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
663
        }
×
664

665
        if storageSPNTenantID != "" {
4✔
666
                klog.V(2).Infof("storageSPNTenantID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNTenantID, accountName, containerName)
×
667
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+storageSPNTenantID)
×
668
        }
×
669

670
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
671
}
672

673
// GetStorageAccountAndContainer get storage account and container info
674
// returns <accountName, accountKey, accountSasToken, containerName>
675
// only for e2e testing
676
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
677
        var (
3✔
678
                subsID                string
3✔
679
                accountName           string
3✔
680
                accountKey            string
3✔
681
                accountSasToken       string
3✔
682
                containerName         string
3✔
683
                keyVaultURL           string
3✔
684
                keyVaultSecretName    string
3✔
685
                keyVaultSecretVersion string
3✔
686
                getLatestAccountKey   bool
3✔
687
                err                   error
3✔
688
        )
3✔
689

3✔
690
        for k, v := range attrib {
8✔
691
                switch strings.ToLower(k) {
5✔
692
                case subscriptionIDField:
×
693
                        subsID = v
×
694
                case containerNameField:
1✔
695
                        containerName = v
1✔
696
                case keyVaultURLField:
×
697
                        keyVaultURL = v
×
698
                case keyVaultSecretNameField:
1✔
699
                        keyVaultSecretName = v
1✔
700
                case keyVaultSecretVersionField:
1✔
701
                        keyVaultSecretVersion = v
1✔
702
                case storageAccountField:
×
703
                        accountName = v
×
704
                case storageAccountNameField: // for compatibility
1✔
705
                        accountName = v
1✔
706
                case getLatestAccountKeyField:
1✔
707
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
708
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
709
                        }
1✔
710
                }
711
        }
712

713
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
714
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
715
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
716
        if keyVaultURL != "" {
2✔
717
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
718
                if err != nil {
×
719
                        return "", "", "", "", err
×
720
                }
×
721
                if isSASToken(key) {
×
722
                        accountSasToken = key
×
723
                } else {
×
724
                        accountKey = key
×
725
                }
×
726
        } else {
2✔
727
                if len(secrets) == 0 {
4✔
728
                        var rgName string
2✔
729
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
730
                        if err != nil {
2✔
731
                                return "", "", "", "", err
×
732
                        }
×
733

734
                        if rgName == "" {
2✔
735
                                rgName = d.cloud.ResourceGroup
×
736
                        }
×
737

738
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
739
                        if err != nil {
3✔
740
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
741
                        }
1✔
742
                }
743
        }
744

745
        if containerName == "" {
1✔
746
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
747
        }
×
748

749
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
750
}
751

752
func IsCorruptedDir(dir string) bool {
4✔
753
        _, pathErr := mount.PathExists(dir)
4✔
754
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
755
}
4✔
756

757
func isRetriableError(err error) bool {
5✔
758
        if err != nil {
9✔
759
                for _, v := range retriableErrors {
19✔
760
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
761
                                return true
3✔
762
                        }
3✔
763
                }
764
        }
765
        return false
2✔
766
}
767

768
func isSupportedProtocol(protocol string) bool {
18✔
769
        if protocol == "" {
19✔
770
                return true
1✔
771
        }
1✔
772
        for _, v := range supportedProtocolList {
44✔
773
                if protocol == v || protocol == NFSv3 {
42✔
774
                        return true
15✔
775
                }
15✔
776
        }
777
        return false
2✔
778
}
779

780
func isSupportedAccessTier(accessTier string) bool {
21✔
781
        if accessTier == "" {
35✔
782
                return true
14✔
783
        }
14✔
784
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
785
                if accessTier == string(tier) {
28✔
786
                        return true
3✔
787
                }
3✔
788
        }
789
        return false
4✔
790
}
791

792
// container names can contain only lowercase letters, numbers, and hyphens,
793
// and must begin and end with a letter or a number
794
func isSupportedContainerNamePrefix(prefix string) bool {
20✔
795
        if prefix == "" {
32✔
796
                return true
12✔
797
        }
12✔
798
        if len(prefix) > 20 {
9✔
799
                return false
1✔
800
        }
1✔
801
        if prefix[0] == '-' {
8✔
802
                return false
1✔
803
        }
1✔
804
        for _, v := range prefix {
19✔
805
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
806
                        return false
4✔
807
                }
4✔
808
        }
809
        return true
2✔
810
}
811

812
// isNFSProtocol checks if the protocol is NFS or AZNFS
813
func isNFSProtocol(protocol string) bool {
19✔
814
        protocol = strings.ToLower(protocol)
19✔
815
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
19✔
816
}
19✔
817

818
// get storage account from secrets map
819
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
820
        if secrets == nil {
23✔
821
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
822
        }
1✔
823

824
        var accountName, accountKey string
21✔
825
        for k, v := range secrets {
64✔
826
                v = strings.TrimSpace(v)
43✔
827
                switch strings.ToLower(k) {
43✔
828
                case accountNameField:
7✔
829
                        accountName = v
7✔
830
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
13✔
831
                        accountName = v
13✔
832
                case accountKeyField:
7✔
833
                        accountKey = v
7✔
834
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
12✔
835
                        accountKey = v
12✔
836
                }
837
        }
838

839
        if accountName == "" {
25✔
840
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
841
        }
4✔
842
        if accountKey == "" {
21✔
843
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
844
        }
4✔
845

846
        accountName = strings.TrimSpace(accountName)
13✔
847
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
848
        return accountName, accountKey, nil
13✔
849
}
850

851
func getContainerReference(containerName string, secrets map[string]string, env az.Environment) (*azstorage.Container, error) {
9✔
852
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
853
        if rerr != nil {
11✔
854
                return nil, rerr
2✔
855
        }
2✔
856
        client, err := azstorage.NewBasicClientOnSovereignCloud(accountName, accountKey, env)
7✔
857
        if err != nil {
13✔
858
                return nil, err
6✔
859
        }
6✔
860
        blobClient := client.GetBlobService()
1✔
861
        container := blobClient.GetContainerReference(containerName)
1✔
862
        if container == nil {
1✔
863
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
864
        }
×
865
        return container, nil
1✔
866
}
867

868
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
869
        if kubeClient == nil {
8✔
870
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
871
                return "", nil
2✔
872
        }
2✔
873
        if accountName == "" || accountKey == "" {
6✔
874
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
875
        }
2✔
876
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
877
        secret := &v1.Secret{
2✔
878
                ObjectMeta: metav1.ObjectMeta{
2✔
879
                        Namespace: secretNamespace,
2✔
880
                        Name:      secretName,
2✔
881
                },
2✔
882
                Data: map[string][]byte{
2✔
883
                        defaultSecretAccountName: []byte(accountName),
2✔
884
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
885
                },
2✔
886
                Type: "Opaque",
2✔
887
        }
2✔
888
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
889
        if apierror.IsAlreadyExists(err) {
3✔
890
                err = nil
1✔
891
        }
1✔
892
        if err != nil {
2✔
893
                return "", fmt.Errorf("couldn't create secret %w", err)
×
894
        }
×
895
        return secretName, err
2✔
896
}
897

898
// GetStorageAccesskey get Azure storage account key from
899
//  1. secrets (if not empty)
900
//  2. use k8s client identity to read from k8s secret
901
//  3. use cluster identity to get from storage account directly
902
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *azure.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
12✔
903
        if len(secrets) > 0 {
17✔
904
                return getStorageAccount(secrets)
5✔
905
        }
5✔
906

907
        // read from k8s secret first
908
        if secretName == "" {
12✔
909
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
5✔
910
        }
5✔
911
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
7✔
912
        if err != nil {
12✔
913
                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✔
914
                accountKey, err = d.cloud.GetStorageAccesskey(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
5✔
915
        }
5✔
916
        return accountOptions.Name, accountKey, err
7✔
917
}
918

919
// GetInfoFromSecret get info from k8s secret
920
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
921
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
922
        if d.KubeClient == nil {
26✔
923
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
924
        }
10✔
925

926
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
927
        if err != nil {
8✔
928
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
929
        }
2✔
930

931
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
932
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
933
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
934
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
935
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
936
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
937
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
938

4✔
939
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
940
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
941
}
942

943
// getSubnetResourceID get default subnet resource ID from cloud provider config
944
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
945
        subsID := d.cloud.SubscriptionID
6✔
946
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
947
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
948
        }
4✔
949

950
        if len(vnetResourceGroup) == 0 {
11✔
951
                vnetResourceGroup = d.cloud.ResourceGroup
5✔
952
                if len(d.cloud.VnetResourceGroup) > 0 {
8✔
953
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
954
                }
3✔
955
        }
956

957
        if len(vnetName) == 0 {
11✔
958
                vnetName = d.cloud.VnetName
5✔
959
        }
5✔
960

961
        if len(subnetName) == 0 {
11✔
962
                subnetName = d.cloud.SubnetName
5✔
963
        }
5✔
964
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
965
}
966

967
func (d *Driver) useDataPlaneAPI(volumeID, accountName string) bool {
10✔
968
        cache, err := d.dataPlaneAPIVolCache.Get(volumeID, azcache.CacheReadTypeDefault)
10✔
969
        if err != nil {
10✔
970
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
971
        }
×
972
        if cache != nil {
13✔
973
                return true
3✔
974
        }
3✔
975
        cache, err = d.dataPlaneAPIVolCache.Get(accountName, azcache.CacheReadTypeDefault)
7✔
976
        if err != nil {
7✔
977
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
978
        }
×
979
        if cache != nil {
7✔
980
                return true
×
981
        }
×
982
        return false
7✔
983
}
984

985
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
986
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
987
        var defaultMountOptions = map[string]string{
4✔
988
                "--pre-mount-validate": "true",
4✔
989
                "--use-https":          "true",
4✔
990
                "--tmp-path":           tmpPath,
4✔
991
                "--container-name":     containerName,
4✔
992
                // prevent billing charges on mounting
4✔
993
                "--cancel-list-on-mount-seconds": "10",
4✔
994
                // allow remounting using a non-empty tmp-path
4✔
995
                "--empty-dir-check": "false",
4✔
996
        }
4✔
997

4✔
998
        // stores the mount options already included in mountOptions
4✔
999
        included := make(map[string]bool)
4✔
1000

4✔
1001
        for _, mountOption := range mountOptions {
11✔
1002
                for k := range defaultMountOptions {
49✔
1003
                        if strings.HasPrefix(mountOption, k) {
46✔
1004
                                included[k] = true
4✔
1005
                        }
4✔
1006
                }
1007
        }
1008

1009
        allMountOptions := mountOptions
4✔
1010

4✔
1011
        for k, v := range defaultMountOptions {
28✔
1012
                if _, isIncluded := included[k]; !isIncluded {
44✔
1013
                        if v != "" {
40✔
1014
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
1015
                        } else {
20✔
1016
                                allMountOptions = append(allMountOptions, k)
×
1017
                        }
×
1018
                }
1019
        }
1020

1021
        return allMountOptions
4✔
1022
}
1023

1024
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1025
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
1026
        info, err := os.Lstat(targetPath)
3✔
1027
        if err != nil {
4✔
1028
                return err
1✔
1029
        }
1✔
1030
        perm := info.Mode() & os.ModePerm
2✔
1031
        if perm != mode {
3✔
1032
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
1033
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
1034
                        return err
×
1035
                }
×
1036
        } else {
1✔
1037
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
1038
        }
1✔
1039
        return nil
2✔
1040
}
1041

1042
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1043
        secret := make(map[string]string)
1✔
1044
        secret[defaultSecretAccountName] = account
1✔
1045
        secret[defaultSecretAccountKey] = key
1✔
1046
        return secret
1✔
1047
}
1✔
1048

1049
// setKeyValueInMap set key/value pair in map
1050
// key in the map is case insensitive, if key already exists, overwrite existing value
1051
func setKeyValueInMap(m map[string]string, key, value string) {
7✔
1052
        if m == nil {
8✔
1053
                return
1✔
1054
        }
1✔
1055
        for k := range m {
17✔
1056
                if strings.EqualFold(k, key) {
13✔
1057
                        m[k] = value
2✔
1058
                        return
2✔
1059
                }
2✔
1060
        }
1061
        m[key] = value
4✔
1062
}
1063

1064
// getValueInMap get value from map by key
1065
// key in the map is case insensitive
1066
func getValueInMap(m map[string]string, key string) string {
12✔
1067
        if m == nil {
13✔
1068
                return ""
1✔
1069
        }
1✔
1070
        for k, v := range m {
23✔
1071
                if strings.EqualFold(k, key) {
16✔
1072
                        return v
4✔
1073
                }
4✔
1074
        }
1075
        return ""
7✔
1076
}
1077

1078
// replaceWithMap replace key with value for str
1079
func replaceWithMap(str string, m map[string]string) string {
15✔
1080
        for k, v := range m {
20✔
1081
                if k != "" {
9✔
1082
                        str = strings.ReplaceAll(str, k, v)
4✔
1083
                }
4✔
1084
        }
1085
        return str
15✔
1086
}
1087

1088
func isSupportedFSGroupChangePolicy(policy string) bool {
26✔
1089
        if policy == "" {
45✔
1090
                return true
19✔
1091
        }
19✔
1092
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1093
                if policy == v {
21✔
1094
                        return true
3✔
1095
                }
3✔
1096
        }
1097
        return false
4✔
1098
}
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