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

kubernetes-sigs / blob-csi-driver / 13965180535

20 Mar 2025 08:40AM UTC coverage: 73.927%. Remained the same
13965180535

Pull #1898

github

andyzhangx
feat: add metrics on node config
Pull Request #1898: [release-1.25] feat: add metrics on node config

2291 of 3099 relevant lines covered (73.93%)

7.14 hits per line

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

84.84
/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
        "encoding/json"
22
        "errors"
23
        "flag"
24
        "fmt"
25
        "os"
26
        "strconv"
27
        "strings"
28
        "sync"
29
        "time"
30

31
        "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
32
        azstorage "github.com/Azure/azure-sdk-for-go/storage"
33
        az "github.com/Azure/go-autorest/autorest/azure"
34
        "github.com/container-storage-interface/spec/lib/go/csi"
35
        grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
36
        "github.com/pborman/uuid"
37
        "google.golang.org/grpc"
38
        v1 "k8s.io/api/core/v1"
39
        apierror "k8s.io/apimachinery/pkg/api/errors"
40
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
41
        "k8s.io/client-go/kubernetes"
42
        "k8s.io/klog/v2"
43
        mount "k8s.io/mount-utils"
44
        utilexec "k8s.io/utils/exec"
45

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

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

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

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

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

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

144
        defaultNamespace = "default"
145

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

153
        VolumeID = "volumeid"
154

155
        defaultStorageEndPointSuffix = "core.windows.net"
156

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

161
        DefaultTokenAudience = "api://AzureADTokenExchange" //nolint:gosec // G101 ignore this!
162

163
)
164

165
var (
166
        supportedProtocolList            = []string{Fuse, Fuse2, NFS, AZNFS}
167
        retriableErrors                  = []string{accountNotProvisioned, tooManyRequests, statusCodeNotFound, containerBeingDeletedDataplaneAPIError, containerBeingDeletedManagementAPIError, clientThrottled}
168
        supportedFSGroupChangePolicyList = []string{FSGroupChangeNone, string(v1.FSGroupChangeAlways), string(v1.FSGroupChangeOnRootMismatch)}
169

170
        // 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,
171
        // set --s2s-preserve-access-tier=false to avoid BlobAccessTierNotSupportedForAccountType error in azcopy
172
        azcopyCloneVolumeOptions = []string{"--recursive", "--check-length=false", "--s2s-preserve-access-tier=false", "--log-level=ERROR"}
173
)
174

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

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

216
// Driver implements all interfaces of CSI drivers
217
type Driver struct {
218
        csicommon.CSIDriver
219
        // Embed UnimplementedXXXServer to ensure the driver returns Unimplemented for any
220
        // new RPC methods that might be introduced in future versions of the spec.
221
        csi.UnimplementedControllerServer
222
        csi.UnimplementedIdentityServer
223
        csi.UnimplementedNodeServer
224

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

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

304
        var err error
134✔
305
        getter := func(_ context.Context, _ string) (interface{}, error) { return nil, nil }
157✔
306
        if d.accountSearchCache, err = azcache.NewTimedCache(time.Minute, getter, false); err != nil {
134✔
307
                klog.Fatalf("%v", err)
×
308
        }
×
309
        if d.dataPlaneAPIVolCache, err = azcache.NewTimedCache(24*30*time.Hour, getter, false); err != nil {
134✔
310
                klog.Fatalf("%v", err)
×
311
        }
×
312
        if d.azcopySasTokenCache, err = azcache.NewTimedCache(15*time.Minute, getter, false); err != nil {
134✔
313
                klog.Fatalf("%v", err)
×
314
        }
×
315

316
        if options.VolStatsCacheExpireInMinutes <= 0 {
268✔
317
                options.VolStatsCacheExpireInMinutes = 10 // default expire in 10 minutes
134✔
318
        }
134✔
319
        if d.volStatsCache, err = azcache.NewTimedCache(time.Duration(options.VolStatsCacheExpireInMinutes)*time.Minute, getter, false); err != nil {
134✔
320
                klog.Fatalf("%v", err)
×
321
        }
×
322
        if d.subnetCache, err = azcache.NewTimedCache(10*time.Minute, getter, false); err != nil {
134✔
323
                klog.Fatalf("%v", err)
×
324
        }
×
325

326
        d.mounter = &mount.SafeFormatAndMount{
134✔
327
                Interface: mount.New(""),
134✔
328
                Exec:      utilexec.New(),
134✔
329
        }
134✔
330

134✔
331
        // Initialize default library driver
134✔
332
        d.AddControllerServiceCapabilities(
134✔
333
                []csi.ControllerServiceCapability_RPC_Type{
134✔
334
                        csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
134✔
335
                        //csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
134✔
336
                        //csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS,
134✔
337
                        csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
134✔
338
                        csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
134✔
339
                        csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
134✔
340
                })
134✔
341
        d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
134✔
342
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
134✔
343
                csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY,
134✔
344
                csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
134✔
345
                csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
134✔
346
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
134✔
347
                csi.VolumeCapability_AccessMode_MULTI_NODE_SINGLE_WRITER,
134✔
348
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
134✔
349
        })
134✔
350

134✔
351
        nodeCap := []csi.NodeServiceCapability_RPC_Type{
134✔
352
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
134✔
353
                csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
134✔
354
        }
134✔
355
        if d.enableGetVolumeStats {
134✔
356
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_GET_VOLUME_STATS)
×
357
        }
×
358
        if d.enableVolumeMountGroup {
134✔
359
                nodeCap = append(nodeCap, csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP)
×
360
        }
×
361
        d.AddNodeServiceCapabilities(nodeCap)
134✔
362

134✔
363
        return &d
134✔
364
}
365

366
// Run driver initialization
367
func (d *Driver) Run(ctx context.Context, endpoint string) error {
2✔
368
        versionMeta, err := GetVersionYAML(d.Name)
2✔
369
        if err != nil {
2✔
370
                klog.Fatalf("%v", err)
×
371
        }
×
372
        klog.Infof("\nDRIVER INFORMATION:\n-------------------\n%s\n\nStreaming logs below:", versionMeta)
2✔
373
        opts := []grpc.ServerOption{
2✔
374
                grpc.ChainUnaryInterceptor(
2✔
375
                        grpcprom.NewServerMetrics().UnaryServerInterceptor(),
2✔
376
                        csicommon.LogGRPC,
2✔
377
                ),
2✔
378
        }
2✔
379
        s := grpc.NewServer(opts...)
2✔
380
        csi.RegisterIdentityServer(s, d)
2✔
381
        csi.RegisterControllerServer(s, d)
2✔
382
        csi.RegisterNodeServer(s, d)
2✔
383

2✔
384
        go func() {
4✔
385
                //graceful shutdown
2✔
386
                <-ctx.Done()
2✔
387
                s.GracefulStop()
2✔
388
        }()
2✔
389
        // Driver d act as IdentityServer, ControllerServer and NodeServer
390
        listener, err := csicommon.Listen(ctx, endpoint)
2✔
391
        if err != nil {
2✔
392
                klog.Fatalf("failed to listen to endpoint, error: %v", err)
×
393
        }
×
394
        err = s.Serve(listener)
2✔
395
        if errors.Is(err, grpc.ErrServerStopped) {
2✔
396
                klog.Infof("gRPC server stopped serving")
×
397
                return nil
×
398
        }
×
399
        return err
2✔
400
}
401

402
// GetContainerInfo get container info according to volume id
403
// the format of VolumeId is: rg#accountName#containerName#uuid#secretNamespace#subsID
404
//
405
// e.g.
406
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#"
407
// output: rg, f5713de20cde511e8ba4900, containerName, "" , ""
408
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#"
409
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, ""
410
// input: "rg#f5713de20cde511e8ba4900#containerName#uuid#namespace#subsID"
411
// output: rg, f5713de20cde511e8ba4900, containerName, namespace, subsID
412
func GetContainerInfo(id string) (string, string, string, string, string, error) {
41✔
413
        segments := strings.Split(id, separator)
41✔
414
        if len(segments) < 3 {
51✔
415
                return "", "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
10✔
416
        }
10✔
417
        var secretNamespace, subsID string
31✔
418
        if len(segments) > 4 {
37✔
419
                secretNamespace = segments[4]
6✔
420
        }
6✔
421
        if len(segments) > 5 {
34✔
422
                subsID = segments[5]
3✔
423
        }
3✔
424
        return segments[0], segments[1], segments[2], secretNamespace, subsID, nil
31✔
425
}
426

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

448
func checkContainerNameBeginAndEnd(containerName string) bool {
12✔
449
        length := len(containerName)
12✔
450
        if (('a' <= containerName[0] && containerName[0] <= 'z') ||
12✔
451
                ('0' <= containerName[0] && containerName[0] <= '9')) &&
12✔
452
                (('a' <= containerName[length-1] && containerName[length-1] <= 'z') ||
12✔
453
                        ('0' <= containerName[length-1] && containerName[length-1] <= '9')) {
22✔
454
                return true
10✔
455
        }
10✔
456

457
        return false
2✔
458
}
459

460
// isSASToken checks if the key contains the patterns.
461
// SAS token format could refer to https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token
462
func isSASToken(key string) bool {
3✔
463
        return strings.HasPrefix(key, "?")
3✔
464
}
3✔
465

466
// GetAuthEnv return <accountName, containerName, authEnv, error>
467
func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attrib, secrets map[string]string) (string, string, string, string, []string, error) {
10✔
468
        rgName, accountName, containerName, secretNamespace, _, err := GetContainerInfo(volumeID)
10✔
469
        if err != nil {
12✔
470
                // ignore volumeID parsing error
2✔
471
                klog.V(2).Infof("parsing volumeID(%s) return with error: %v", volumeID, err)
2✔
472
                err = nil
2✔
473
        }
2✔
474

475
        var (
10✔
476
                subsID                  string
10✔
477
                accountKey              string
10✔
478
                accountSasToken         string
10✔
479
                msiSecret               string
10✔
480
                storageSPNClientSecret  string
10✔
481
                storageSPNClientID      string
10✔
482
                storageSPNTenantID      string
10✔
483
                secretName              string
10✔
484
                pvcNamespace            string
10✔
485
                keyVaultURL             string
10✔
486
                keyVaultSecretName      string
10✔
487
                keyVaultSecretVersion   string
10✔
488
                azureStorageAuthType    string
10✔
489
                authEnv                 []string
10✔
490
                getAccountKeyFromSecret bool
10✔
491
                getLatestAccountKey     bool
10✔
492
                clientID                string
10✔
493
                tenantID                string
10✔
494
                serviceAccountToken     string
10✔
495
        )
10✔
496

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

9✔
554
        if protocol == NFS {
11✔
555
                // nfs protocol does not need account key, return directly
2✔
556
                return rgName, accountName, accountKey, containerName, authEnv, err
2✔
557
        }
2✔
558

559
        if secretNamespace == "" {
13✔
560
                if pvcNamespace == "" {
12✔
561
                        secretNamespace = defaultNamespace
6✔
562
                } else {
6✔
563
                        secretNamespace = pvcNamespace
×
564
                }
×
565
        }
566

567
        if rgName == "" {
8✔
568
                rgName = d.cloud.ResourceGroup
1✔
569
        }
1✔
570

571
        if tenantID == "" {
14✔
572
                tenantID = d.cloud.TenantID
7✔
573
        }
7✔
574

575
        // if client id is specified, we only use workload identity for blobfuse auth
576
        if clientID != "" {
7✔
577
                klog.V(2).Infof("clientID(%s) is specified, use workload identity for blobfuse auth", clientID)
×
578

×
579
                workloadIdentityToken, err := parseServiceAccountToken(serviceAccountToken)
×
580
                if err != nil {
×
581
                        return rgName, accountName, accountKey, containerName, authEnv, err
×
582
                }
×
583

584
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+clientID)
×
585
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+tenantID)
×
586
                authEnv = append(authEnv, "WORKLOAD_IDENTITY_TOKEN="+workloadIdentityToken)
×
587

×
588
                return rgName, accountName, accountKey, containerName, authEnv, err
×
589
        }
590

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

662
        if containerName == "" {
4✔
663
                err = fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
664
        }
×
665

666
        if accountKey != "" {
8✔
667
                authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey)
4✔
668
        }
4✔
669

670
        if accountSasToken != "" {
5✔
671
                klog.V(2).Infof("accountSasToken is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
672
                authEnv = append(authEnv, "AZURE_STORAGE_SAS_TOKEN="+accountSasToken)
1✔
673
        }
1✔
674

675
        if msiSecret != "" {
5✔
676
                klog.V(2).Infof("msiSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
677
                authEnv = append(authEnv, "MSI_SECRET="+msiSecret)
1✔
678
        }
1✔
679

680
        if storageSPNClientSecret != "" {
5✔
681
                klog.V(2).Infof("storageSPNClientSecret is not empty, use it to access storage account(%s), container(%s)", accountName, containerName)
1✔
682
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_SECRET="+storageSPNClientSecret)
1✔
683
        }
1✔
684

685
        if storageSPNClientID != "" {
4✔
686
                klog.V(2).Infof("storageSPNClientID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNClientID, accountName, containerName)
×
687
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+storageSPNClientID)
×
688
        }
×
689

690
        if storageSPNTenantID != "" {
4✔
691
                klog.V(2).Infof("storageSPNTenantID(%s) is not empty, use it to access storage account(%s), container(%s)", storageSPNTenantID, accountName, containerName)
×
692
                authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+storageSPNTenantID)
×
693
        }
×
694

695
        return rgName, accountName, accountKey, containerName, authEnv, err
4✔
696
}
697

698
// GetStorageAccountAndContainer get storage account and container info
699
// returns <accountName, accountKey, accountSasToken, containerName>
700
// only for e2e testing
701
func (d *Driver) GetStorageAccountAndContainer(ctx context.Context, volumeID string, attrib, secrets map[string]string) (string, string, string, string, error) {
3✔
702
        var (
3✔
703
                subsID                string
3✔
704
                accountName           string
3✔
705
                accountKey            string
3✔
706
                accountSasToken       string
3✔
707
                containerName         string
3✔
708
                keyVaultURL           string
3✔
709
                keyVaultSecretName    string
3✔
710
                keyVaultSecretVersion string
3✔
711
                getLatestAccountKey   bool
3✔
712
                err                   error
3✔
713
        )
3✔
714

3✔
715
        for k, v := range attrib {
8✔
716
                switch strings.ToLower(k) {
5✔
717
                case subscriptionIDField:
×
718
                        subsID = v
×
719
                case containerNameField:
1✔
720
                        containerName = v
1✔
721
                case keyVaultURLField:
×
722
                        keyVaultURL = v
×
723
                case keyVaultSecretNameField:
1✔
724
                        keyVaultSecretName = v
1✔
725
                case keyVaultSecretVersionField:
1✔
726
                        keyVaultSecretVersion = v
1✔
727
                case storageAccountField:
×
728
                        accountName = v
×
729
                case storageAccountNameField: // for compatibility
1✔
730
                        accountName = v
1✔
731
                case getLatestAccountKeyField:
1✔
732
                        if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
2✔
733
                                return "", "", "", "", fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
1✔
734
                        }
1✔
735
                }
736
        }
737

738
        // 1. If keyVaultURL is not nil, preferentially use the key stored in key vault.
739
        // 2. Then if secrets map is not nil, use the key stored in the secrets map.
740
        // 3. Finally if both keyVaultURL and secrets map are nil, get the key from Azure.
741
        if keyVaultURL != "" {
2✔
742
                key, err := d.getKeyVaultSecretContent(ctx, keyVaultURL, keyVaultSecretName, keyVaultSecretVersion)
×
743
                if err != nil {
×
744
                        return "", "", "", "", err
×
745
                }
×
746
                if isSASToken(key) {
×
747
                        accountSasToken = key
×
748
                } else {
×
749
                        accountKey = key
×
750
                }
×
751
        } else {
2✔
752
                if len(secrets) == 0 {
4✔
753
                        var rgName string
2✔
754
                        rgName, accountName, containerName, _, _, err = GetContainerInfo(volumeID)
2✔
755
                        if err != nil {
2✔
756
                                return "", "", "", "", err
×
757
                        }
×
758

759
                        if rgName == "" {
2✔
760
                                rgName = d.cloud.ResourceGroup
×
761
                        }
×
762

763
                        accountKey, err = d.cloud.GetStorageAccesskey(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
764
                        if err != nil {
3✔
765
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
766
                        }
1✔
767
                }
768
        }
769

770
        if containerName == "" {
1✔
771
                return "", "", "", "", fmt.Errorf("could not find containerName from attributes(%v) or volumeID(%v)", attrib, volumeID)
×
772
        }
×
773

774
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
775
}
776

777
func IsCorruptedDir(dir string) bool {
4✔
778
        _, pathErr := mount.PathExists(dir)
4✔
779
        return pathErr != nil && mount.IsCorruptedMnt(pathErr)
4✔
780
}
4✔
781

782
func isRetriableError(err error) bool {
5✔
783
        if err != nil {
9✔
784
                for _, v := range retriableErrors {
19✔
785
                        if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(v)) {
18✔
786
                                return true
3✔
787
                        }
3✔
788
                }
789
        }
790
        return false
2✔
791
}
792

793
func isSupportedProtocol(protocol string) bool {
19✔
794
        if protocol == "" {
20✔
795
                return true
1✔
796
        }
1✔
797
        for _, v := range supportedProtocolList {
46✔
798
                if protocol == v || protocol == NFSv3 {
44✔
799
                        return true
16✔
800
                }
16✔
801
        }
802
        return false
2✔
803
}
804

805
func isSupportedAccessTier(accessTier string) bool {
22✔
806
        if accessTier == "" {
37✔
807
                return true
15✔
808
        }
15✔
809
        for _, tier := range armstorage.PossibleAccessTierValues() {
32✔
810
                if accessTier == string(tier) {
28✔
811
                        return true
3✔
812
                }
3✔
813
        }
814
        return false
4✔
815
}
816

817
// container names can contain only lowercase letters, numbers, and hyphens,
818
// and must begin and end with a letter or a number
819
func isSupportedContainerNamePrefix(prefix string) bool {
21✔
820
        if prefix == "" {
34✔
821
                return true
13✔
822
        }
13✔
823
        if len(prefix) > 20 {
9✔
824
                return false
1✔
825
        }
1✔
826
        if prefix[0] == '-' {
8✔
827
                return false
1✔
828
        }
1✔
829
        for _, v := range prefix {
19✔
830
                if v != '-' && (v < '0' || v > '9') && (v < 'a' || v > 'z') {
17✔
831
                        return false
4✔
832
                }
4✔
833
        }
834
        return true
2✔
835
}
836

837
// isNFSProtocol checks if the protocol is NFS or AZNFS
838
func isNFSProtocol(protocol string) bool {
20✔
839
        protocol = strings.ToLower(protocol)
20✔
840
        return protocol == NFS || protocol == AZNFS || protocol == NFSv3
20✔
841
}
20✔
842

843
// get storage account from secrets map
844
func getStorageAccount(secrets map[string]string) (string, string, error) {
22✔
845
        if secrets == nil {
23✔
846
                return "", "", fmt.Errorf("unexpected: getStorageAccount secrets is nil")
1✔
847
        }
1✔
848

849
        var accountName, accountKey string
21✔
850
        for k, v := range secrets {
64✔
851
                v = strings.TrimSpace(v)
43✔
852
                switch strings.ToLower(k) {
43✔
853
                case accountNameField:
7✔
854
                        accountName = v
7✔
855
                case defaultSecretAccountName: // for compatibility with built-in azurefile plugin
13✔
856
                        accountName = v
13✔
857
                case accountKeyField:
7✔
858
                        accountKey = v
7✔
859
                case defaultSecretAccountKey: // for compatibility with built-in azurefile plugin
12✔
860
                        accountKey = v
12✔
861
                }
862
        }
863

864
        if accountName == "" {
25✔
865
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountNameField, defaultSecretAccountName)
4✔
866
        }
4✔
867
        if accountKey == "" {
21✔
868
                return accountName, accountKey, fmt.Errorf("could not find %s or %s field in secrets", accountKeyField, defaultSecretAccountKey)
4✔
869
        }
4✔
870

871
        accountName = strings.TrimSpace(accountName)
13✔
872
        klog.V(4).Infof("got storage account(%s) from secret", accountName)
13✔
873
        return accountName, accountKey, nil
13✔
874
}
875

876
func getContainerReference(containerName string, secrets map[string]string, env az.Environment) (*azstorage.Container, error) {
9✔
877
        accountName, accountKey, rerr := getStorageAccount(secrets)
9✔
878
        if rerr != nil {
11✔
879
                return nil, rerr
2✔
880
        }
2✔
881
        client, err := azstorage.NewBasicClientOnSovereignCloud(accountName, accountKey, env)
7✔
882
        if err != nil {
13✔
883
                return nil, err
6✔
884
        }
6✔
885
        blobClient := client.GetBlobService()
1✔
886
        container := blobClient.GetContainerReference(containerName)
1✔
887
        if container == nil {
1✔
888
                return nil, fmt.Errorf("ContainerReference of %s is nil", containerName)
×
889
        }
×
890
        return container, nil
1✔
891
}
892

893
func setAzureCredentials(ctx context.Context, kubeClient kubernetes.Interface, accountName, accountKey, secretNamespace string) (string, error) {
6✔
894
        if kubeClient == nil {
8✔
895
                klog.Warningf("could not create secret: kubeClient is nil")
2✔
896
                return "", nil
2✔
897
        }
2✔
898
        if accountName == "" || accountKey == "" {
6✔
899
                return "", fmt.Errorf("the account info is not enough, accountName(%v), accountKey(%v)", accountName, accountKey)
2✔
900
        }
2✔
901
        secretName := fmt.Sprintf(secretNameTemplate, accountName)
2✔
902
        secret := &v1.Secret{
2✔
903
                ObjectMeta: metav1.ObjectMeta{
2✔
904
                        Namespace: secretNamespace,
2✔
905
                        Name:      secretName,
2✔
906
                },
2✔
907
                Data: map[string][]byte{
2✔
908
                        defaultSecretAccountName: []byte(accountName),
2✔
909
                        defaultSecretAccountKey:  []byte(accountKey),
2✔
910
                },
2✔
911
                Type: "Opaque",
2✔
912
        }
2✔
913
        _, err := kubeClient.CoreV1().Secrets(secretNamespace).Create(ctx, secret, metav1.CreateOptions{})
2✔
914
        if apierror.IsAlreadyExists(err) {
3✔
915
                err = nil
1✔
916
        }
1✔
917
        if err != nil {
2✔
918
                return "", fmt.Errorf("couldn't create secret %w", err)
×
919
        }
×
920
        return secretName, err
2✔
921
}
922

923
// GetStorageAccesskey get Azure storage account key from
924
//  1. secrets (if not empty)
925
//  2. use k8s client identity to read from k8s secret
926
//  3. use cluster identity to get from storage account directly
927
func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *azure.AccountOptions, secrets map[string]string, secretName, secretNamespace string) (string, string, error) {
12✔
928
        if len(secrets) > 0 {
17✔
929
                return getStorageAccount(secrets)
5✔
930
        }
5✔
931

932
        // read from k8s secret first
933
        if secretName == "" {
12✔
934
                secretName = fmt.Sprintf(secretNameTemplate, accountOptions.Name)
5✔
935
        }
5✔
936
        _, accountKey, _, _, _, _, _, err := d.GetInfoFromSecret(ctx, secretName, secretNamespace) //nolint
7✔
937
        if err != nil {
12✔
938
                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✔
939
                accountKey, err = d.cloud.GetStorageAccesskey(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
5✔
940
        }
5✔
941
        return accountOptions.Name, accountKey, err
7✔
942
}
943

944
// GetInfoFromSecret get info from k8s secret
945
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
946
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
947
        if d.KubeClient == nil {
26✔
948
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
949
        }
10✔
950

951
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
952
        if err != nil {
8✔
953
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
954
        }
2✔
955

956
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
957
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
958
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
959
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
960
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
961
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
962
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
963

4✔
964
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
965
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
966
}
967

968
// getSubnetResourceID get default subnet resource ID from cloud provider config
969
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
970
        subsID := d.cloud.SubscriptionID
6✔
971
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
972
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
973
        }
4✔
974

975
        if len(vnetResourceGroup) == 0 {
10✔
976
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
977
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
978
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
979
                }
3✔
980
        }
981

982
        if len(vnetName) == 0 {
10✔
983
                vnetName = d.cloud.VnetName
4✔
984
        }
4✔
985

986
        if len(subnetName) == 0 {
10✔
987
                subnetName = d.cloud.SubnetName
4✔
988
        }
4✔
989
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
990
}
991

992
func (d *Driver) useDataPlaneAPI(ctx context.Context, volumeID, accountName string) bool {
9✔
993
        cache, err := d.dataPlaneAPIVolCache.Get(ctx, volumeID, azcache.CacheReadTypeDefault)
9✔
994
        if err != nil {
9✔
995
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
996
        }
×
997
        if cache != nil {
12✔
998
                return true
3✔
999
        }
3✔
1000
        cache, err = d.dataPlaneAPIVolCache.Get(ctx, accountName, azcache.CacheReadTypeDefault)
6✔
1001
        if err != nil {
6✔
1002
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
1003
        }
×
1004
        if cache != nil {
6✔
1005
                return true
×
1006
        }
×
1007
        return false
6✔
1008
}
1009

1010
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
1011
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
1012
        var defaultMountOptions = map[string]string{
4✔
1013
                "--pre-mount-validate": "true",
4✔
1014
                "--use-https":          "true",
4✔
1015
                "--tmp-path":           tmpPath,
4✔
1016
                "--container-name":     containerName,
4✔
1017
                // prevent billing charges on mounting
4✔
1018
                "--cancel-list-on-mount-seconds": "10",
4✔
1019
                // allow remounting using a non-empty tmp-path
4✔
1020
                "--empty-dir-check": "false",
4✔
1021
        }
4✔
1022

4✔
1023
        // stores the mount options already included in mountOptions
4✔
1024
        included := make(map[string]bool)
4✔
1025

4✔
1026
        for _, mountOption := range mountOptions {
11✔
1027
                for k := range defaultMountOptions {
49✔
1028
                        if strings.HasPrefix(mountOption, k) {
46✔
1029
                                included[k] = true
4✔
1030
                        }
4✔
1031
                }
1032
        }
1033

1034
        allMountOptions := mountOptions
4✔
1035

4✔
1036
        for k, v := range defaultMountOptions {
28✔
1037
                if _, isIncluded := included[k]; !isIncluded {
44✔
1038
                        if v != "" {
40✔
1039
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
1040
                        } else {
20✔
1041
                                allMountOptions = append(allMountOptions, k)
×
1042
                        }
×
1043
                }
1044
        }
1045

1046
        return allMountOptions
4✔
1047
}
1048

1049
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1050
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
1051
        info, err := os.Lstat(targetPath)
3✔
1052
        if err != nil {
4✔
1053
                return err
1✔
1054
        }
1✔
1055
        perm := info.Mode() & os.ModePerm
2✔
1056
        if perm != mode {
3✔
1057
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
1058
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
1059
                        return err
×
1060
                }
×
1061
        } else {
1✔
1062
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
1063
        }
1✔
1064
        return nil
2✔
1065
}
1066

1067
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1068
        secret := make(map[string]string)
1✔
1069
        secret[defaultSecretAccountName] = account
1✔
1070
        secret[defaultSecretAccountKey] = key
1✔
1071
        return secret
1✔
1072
}
1✔
1073

1074
// setKeyValueInMap set key/value pair in map
1075
// key in the map is case insensitive, if key already exists, overwrite existing value
1076
func setKeyValueInMap(m map[string]string, key, value string) {
7✔
1077
        if m == nil {
8✔
1078
                return
1✔
1079
        }
1✔
1080
        for k := range m {
17✔
1081
                if strings.EqualFold(k, key) {
13✔
1082
                        m[k] = value
2✔
1083
                        return
2✔
1084
                }
2✔
1085
        }
1086
        m[key] = value
4✔
1087
}
1088

1089
// getValueInMap get value from map by key
1090
// key in the map is case insensitive
1091
func getValueInMap(m map[string]string, key string) string {
12✔
1092
        if m == nil {
13✔
1093
                return ""
1✔
1094
        }
1✔
1095
        for k, v := range m {
23✔
1096
                if strings.EqualFold(k, key) {
16✔
1097
                        return v
4✔
1098
                }
4✔
1099
        }
1100
        return ""
7✔
1101
}
1102

1103
// replaceWithMap replace key with value for str
1104
func replaceWithMap(str string, m map[string]string) string {
14✔
1105
        for k, v := range m {
19✔
1106
                if k != "" {
9✔
1107
                        str = strings.ReplaceAll(str, k, v)
4✔
1108
                }
4✔
1109
        }
1110
        return str
14✔
1111
}
1112

1113
func isSupportedFSGroupChangePolicy(policy string) bool {
27✔
1114
        if policy == "" {
47✔
1115
                return true
20✔
1116
        }
20✔
1117
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1118
                if policy == v {
21✔
1119
                        return true
3✔
1120
                }
3✔
1121
        }
1122
        return false
4✔
1123
}
1124

1125
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
7✔
1126
        if vc.GetAccessMode() == nil {
9✔
1127
                return false
2✔
1128
        }
2✔
1129
        mode := vc.GetAccessMode().GetMode()
5✔
1130
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
5✔
1131
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
5✔
1132
}
1133

1134
// generateVolumeName returns a PV name with clusterName prefix. The function
1135
// should be used to generate a name of GCE PD or Cinder volume. It basically
1136
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
1137
// string fits given length and cuts "dynamic" if not.
1138
func generateVolumeName(clusterName, pvName string, maxLength int) string {
3✔
1139
        prefix := clusterName + "-dynamic"
3✔
1140
        pvLen := len(pvName)
3✔
1141
        // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
3✔
1142
        // +1 for the '-' dash
3✔
1143
        if pvLen+1+len(prefix) > maxLength {
5✔
1144
                prefix = prefix[:maxLength-pvLen-1]
2✔
1145
        }
2✔
1146
        return prefix + "-" + pvName
3✔
1147
}
1148

1149
// serviceAccountToken represents the service account token sent from NodePublishVolume Request.
1150
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1151
type serviceAccountToken struct {
1152
        APIAzureADTokenExchange struct {
1153
                Token               string    `json:"token"`
1154
                ExpirationTimestamp time.Time `json:"expirationTimestamp"`
1155
        } `json:"api://AzureADTokenExchange"`
1156
}
1157

1158
// parseServiceAccountToken parses the bound service account token from the token passed from NodePublishVolume Request.
1159
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1160
func parseServiceAccountToken(tokenStr string) (string, error) {
5✔
1161
        if len(tokenStr) == 0 {
6✔
1162
                return "", fmt.Errorf("service account token is empty")
1✔
1163
        }
1✔
1164
        token := serviceAccountToken{}
4✔
1165
        if err := json.Unmarshal([]byte(tokenStr), &token); err != nil {
5✔
1166
                return "", fmt.Errorf("failed to unmarshal service account tokens, error: %w", err)
1✔
1167
        }
1✔
1168
        if token.APIAzureADTokenExchange.Token == "" {
5✔
1169
                return "", fmt.Errorf("token for audience %s not found", DefaultTokenAudience)
2✔
1170
        }
2✔
1171
        return token.APIAzureADTokenExchange.Token, nil
1✔
1172
}
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