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

kubernetes-sigs / blob-csi-driver / 10860612705

14 Sep 2024 07:27AM UTC coverage: 73.624%. Remained the same
10860612705

push

github

web-flow
Merge pull request #1595 from andyzhangx/upgrade-node-driver-registrar-1.24

[release-1.24] fix: upgrade node-driver-registrar to fix register timeout issue

2247 of 3052 relevant lines covered (73.62%)

6.98 hits per line

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

84.78
/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
        // a timed cache storing subnet operations
251
        subnetCache azcache.Resource
252
        // sas expiry time for azcopy in volume clone
253
        sasTokenExpirationMinutes int
254
        // timeout in minutes for waiting for azcopy to finish
255
        waitForAzCopyTimeoutMinutes int
256
        // azcopy for provide exec mock for ut
257
        azcopy *util.Azcopy
258
}
259

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

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

304
        if options.VolStatsCacheExpireInMinutes <= 0 {
268✔
305
                options.VolStatsCacheExpireInMinutes = 10 // default expire in 10 minutes
134✔
306
        }
134✔
307
        if d.volStatsCache, err = azcache.NewTimedCache(time.Duration(options.VolStatsCacheExpireInMinutes)*time.Minute, getter, false); err != nil {
134✔
308
                klog.Fatalf("%v", err)
×
309
        }
×
310
        if d.subnetCache, err = azcache.NewTimedCache(10*time.Minute, getter, false); err != nil {
134✔
311
                klog.Fatalf("%v", err)
×
312
        }
×
313

314
        d.mounter = &mount.SafeFormatAndMount{
134✔
315
                Interface: mount.New(""),
134✔
316
                Exec:      utilexec.New(),
134✔
317
        }
134✔
318

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

134✔
339
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
134✔
340
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
134✔
341
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
134✔
342
        }
134✔
343
        if d.enableGetVolumeStats {
134✔
344
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
345
        }
×
346
        if d.enableVolumeMountGroup {
134✔
347
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP)
×
348
        }
×
349
        d.AddNodeServiceCapabilities(nodeCap)
134✔
350

134✔
351
        return &d
134✔
352
}
353

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

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

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

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

434
func checkContainerNameBeginAndEnd(containerName string) bool {
12✔
435
        length := len(containerName)
12✔
436
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
12✔
437
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
12✔
438
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
12✔
439
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
22✔
440
                return true
10✔
441
        }
10✔
442

443
        return false
2✔
444
}
445

446
// isSASToken checks if the key contains the patterns.
447
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
448
func isSASToken(key string) bool {
3✔
449
        return strings.HasPrefix(key, "?")
3✔
450
}
3✔
451

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

461
        var (
10✔
462
                subsID                  string
10✔
463
                accountKey              string
10✔
464
                accountSasToken         string
10✔
465
                msiSecret               string
10✔
466
                storageSPNClientSecret  string
10✔
467
                storageSPNClientID      string
10✔
468
                storageSPNTenantID      string
10✔
469
                secretName              string
10✔
470
                pvcNamespace            string
10✔
471
                keyVaultURL             string
10✔
472
                keyVaultSecretName      string
10✔
473
                keyVaultSecretVersion   string
10✔
474
                azureStorageAuthType    string
10✔
475
                authEnv                 []string
10✔
476
                getAccountKeyFromSecret bool
10✔
477
                getLatestAccountKey     bool
10✔
478
                clientID                string
10✔
479
                tenantID                string
10✔
480
                serviceAccountToken     string
10✔
481
        )
10✔
482

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

9✔
540
        if protocol == NFS {
11✔
541
                // nfs protocol does not need account key, return directly
2✔
542
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
543
        }
2✔
544

545
        if secretNamespace == "" {
13✔
546
                if pvcNamespace == "" {
12✔
547
                        secretNamespace = defaultNamespace
6✔
548
                } else {
6✔
549
                        secretNamespace = pvcNamespace
×
550
                }
×
551
        }
552

553
        if rgName == "" {
8✔
554
                rgName = d.cloud.ResourceGroup
1✔
555
        }
1✔
556

557
        if tenantID == "" {
14✔
558
                tenantID = d.cloud.TenantID
7✔
559
        }
7✔
560

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

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

643
        if containerName == "" {
4✔
644
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
645
        }
×
646

647
        if accountKey != "" {
8✔
648
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
649
        }
4✔
650

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

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

661
        if storageSPNClientSecret != "" {
5✔
662
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
663
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
664
        }
1✔
665

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

671
        if storageSPNTenantID != "" {
4✔
672
                klog.V(2).Infof("storageSPNTenantID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNTenantID, accountName, containerName)
×
673
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+storageSPNTenantID)
×
674
        }
×
675

676
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
677
}
678

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

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

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

740
                        if rgName == "" {
2✔
741
                                rgName = d.cloud.ResourceGroup
×
742
                        }
×
743

744
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
745
                        if err != nil {
3✔
746
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
747
                        }
1✔
748
                }
749
        }
750

751
        if containerName == "" {
1✔
752
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
753
        }
×
754

755
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
756
}
757

758
func IsCorruptedDir(dir string) bool {
4✔
759
        _, pathErr := mount.PathExists(dir)
4✔
760
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
761
}
4✔
762

763
func isRetriableError(err error) bool {
5✔
764
        if err != nil {
9✔
765
                for _, v := range retriableErrors {
19✔
766
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
767
                                return true
3✔
768
                        }
3✔
769
                }
770
        }
771
        return false
2✔
772
}
773

774
func isSupportedProtocol(protocol string) bool {
18✔
775
        if protocol == "" {
19✔
776
                return true
1✔
777
        }
1✔
778
        for _, v := range supportedProtocolList {
44✔
779
                if protocol == v || protocol == NFSv3 {
42✔
780
                        return true
15✔
781
                }
15✔
782
        }
783
        return false
2✔
784
}
785

786
func isSupportedAccessTier(accessTier string) bool {
21✔
787
        if accessTier == "" {
35✔
788
                return true
14✔
789
        }
14✔
790
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
791
                if accessTier == string(tier) {
28✔
792
                        return true
3✔
793
                }
3✔
794
        }
795
        return false
4✔
796
}
797

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

818
// isNFSProtocol checks if the protocol is NFS or AZNFS
819
func isNFSProtocol(protocol string) bool {
19✔
820
        protocol = strings.ToLower(protocol)
19✔
821
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
19✔
822
}
19✔
823

824
// get storage account from secrets map
825
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
826
        if secrets == nil {
23✔
827
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
828
        }
1✔
829

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

845
        if accountName == "" {
25✔
846
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
847
        }
4✔
848
        if accountKey == "" {
21✔
849
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
850
        }
4✔
851

852
        accountName = strings.TrimSpace(accountName)
13✔
853
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
854
        return accountName, accountKey, nil
13✔
855
}
856

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

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

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

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

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

932
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
933
        if err != nil {
8✔
934
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
935
        }
2✔
936

937
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
938
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
939
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
940
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
941
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
942
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
943
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
944

4✔
945
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
946
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
947
}
948

949
// getSubnetResourceID get default subnet resource ID from cloud provider config
950
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
951
        subsID := d.cloud.SubscriptionID
6✔
952
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
953
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
954
        }
4✔
955

956
        if len(vnetResourceGroup) == 0 {
10✔
957
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
958
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
959
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
960
                }
3✔
961
        }
962

963
        if len(vnetName) == 0 {
10✔
964
                vnetName = d.cloud.VnetName
4✔
965
        }
4✔
966

967
        if len(subnetName) == 0 {
10✔
968
                subnetName = d.cloud.SubnetName
4✔
969
        }
4✔
970
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
971
}
972

973
func (d *Driver) useDataPlaneAPI(volumeID, accountName string) bool {
9✔
974
        cache, err := d.dataPlaneAPIVolCache.Get(volumeID, azcache.CacheReadTypeDefault)
9✔
975
        if err != nil {
9✔
976
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
977
        }
×
978
        if cache != nil {
12✔
979
                return true
3✔
980
        }
3✔
981
        cache, err = d.dataPlaneAPIVolCache.Get(accountName, azcache.CacheReadTypeDefault)
6✔
982
        if err != nil {
6✔
983
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
984
        }
×
985
        if cache != nil {
6✔
986
                return true
×
987
        }
×
988
        return false
6✔
989
}
990

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

4✔
1004
        // stores the mount options already included in mountOptions
4✔
1005
        included := make(map[string]bool)
4✔
1006

4✔
1007
        for _, mountOption := range mountOptions {
11✔
1008
                for k := range defaultMountOptions {
49✔
1009
                        if strings.HasPrefix(mountOption, k) {
46✔
1010
                                included[k] = true
4✔
1011
                        }
4✔
1012
                }
1013
        }
1014

1015
        allMountOptions := mountOptions
4✔
1016

4✔
1017
        for k, v := range defaultMountOptions {
28✔
1018
                if _, isIncluded := included[k]; !isIncluded {
44✔
1019
                        if v != "" {
40✔
1020
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
1021
                        } else {
20✔
1022
                                allMountOptions = append(allMountOptions, k)
×
1023
                        }
×
1024
                }
1025
        }
1026

1027
        return allMountOptions
4✔
1028
}
1029

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

1048
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1049
        secret := make(map[string]string)
1✔
1050
        secret[defaultSecretAccountName] = account
1✔
1051
        secret[defaultSecretAccountKey] = key
1✔
1052
        return secret
1✔
1053
}
1✔
1054

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

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

1084
// replaceWithMap replace key with value for str
1085
func replaceWithMap(str string, m map[string]string) string {
15✔
1086
        for k, v := range m {
20✔
1087
                if k != "" {
9✔
1088
                        str = strings.ReplaceAll(str, k, v)
4✔
1089
                }
4✔
1090
        }
1091
        return str
15✔
1092
}
1093

1094
func isSupportedFSGroupChangePolicy(policy string) bool {
26✔
1095
        if policy == "" {
45✔
1096
                return true
19✔
1097
        }
19✔
1098
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1099
                if policy == v {
21✔
1100
                        return true
3✔
1101
                }
3✔
1102
        }
1103
        return false
4✔
1104
}
1105

1106
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
7✔
1107
        if vc.GetAccessMode() == nil {
9✔
1108
                return false
2✔
1109
        }
2✔
1110
        mode := vc.GetAccessMode().GetMode()
5✔
1111
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
5✔
1112
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
5✔
1113
}
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