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

kubernetes-sigs / blob-csi-driver / 14065506428

25 Mar 2025 04:52PM UTC coverage: 78.258%. Remained the same
14065506428

Pull #1911

github

web-flow
chore(deps): bump github.com/onsi/ginkgo/v2 from 2.23.2 to 2.23.3

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.23.2 to 2.23.3.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.23.2...v2.23.3)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #1911: chore(deps): bump github.com/onsi/ginkgo/v2 from 2.23.2 to 2.23.3

2336 of 2985 relevant lines covered (78.26%)

7.62 hits per line

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

84.75
/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/storage"
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
        DefaultTokenAudience = "api://AzureADTokenExchange" //nolint:gosec // G101 ignore this!
161

162
)
163

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

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

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

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

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

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

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

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

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

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

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

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

137✔
362
        return &d
137✔
363
}
364

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

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

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

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

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

456
        return false
2✔
457
}
458

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

758
                        if rgName == "" {
2✔
759
                                rgName = d.cloud.ResourceGroup
×
760
                        }
×
761
                        accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, subsID, accountName, rgName, getLatestAccountKey)
2✔
762
                        if err != nil {
3✔
763
                                return "", "", "", "", fmt.Errorf("no key for storage account(%s) under resource group(%s), err %w", accountName, rgName, err)
1✔
764
                        }
1✔
765
                }
766
        }
767

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

772
        return accountName, accountKey, accountSasToken, containerName, nil
1✔
773
}
774

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

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

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

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

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

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

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

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

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

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

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

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

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

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

942
// GetStorageAccesskeyWithSubsID get Azure storage account key from storage account directly
943
func (d *Driver) GetStorageAccesskeyWithSubsID(ctx context.Context, subsID, account, resourceGroup string, getLatestAccountKey bool) (string, error) {
15✔
944
        if d.cloud == nil || d.cloud.ComputeClientFactory == nil {
17✔
945
                return "", fmt.Errorf("could not get account key: cloud or ComputeClientFactory is nil")
2✔
946
        }
2✔
947
        accountClient, err := d.cloud.ComputeClientFactory.GetAccountClientForSub(subsID)
13✔
948
        if err != nil {
13✔
949
                return "", err
×
950
        }
×
951
        return d.cloud.GetStorageAccesskey(ctx, accountClient, account, resourceGroup, getLatestAccountKey)
13✔
952
}
953

954
// GetInfoFromSecret get info from k8s secret
955
// return <accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, error>
956
func (d *Driver) GetInfoFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, string, string, string, string, error) {
16✔
957
        if d.KubeClient == nil {
26✔
958
                return "", "", "", "", "", "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
10✔
959
        }
10✔
960

961
        secret, err := d.KubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
6✔
962
        if err != nil {
8✔
963
                return "", "", "", "", "", "", "", fmt.Errorf("could not get secret(%v): %w", secretName, err)
2✔
964
        }
2✔
965

966
        accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
4✔
967
        accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
4✔
968
        accountSasToken := strings.TrimSpace(string(secret.Data[accountSasTokenField][:]))
4✔
969
        msiSecret := strings.TrimSpace(string(secret.Data[msiSecretField][:]))
4✔
970
        spnClientSecret := strings.TrimSpace(string(secret.Data[storageSPNClientSecretField][:]))
4✔
971
        spnClientID := strings.TrimSpace(string(secret.Data[storageSPNClientIDField][:]))
4✔
972
        spnTenantID := strings.TrimSpace(string(secret.Data[storageSPNTenantIDField][:]))
4✔
973

4✔
974
        klog.V(4).Infof("got storage account(%s) from secret(%s) namespace(%s)", accountName, secretName, secretNamespace)
4✔
975
        return accountName, accountKey, accountSasToken, msiSecret, spnClientSecret, spnClientID, spnTenantID, nil
4✔
976
}
977

978
// getSubnetResourceID get default subnet resource ID from cloud provider config
979
func (d *Driver) getSubnetResourceID(vnetResourceGroup, vnetName, subnetName string) string {
6✔
980
        subsID := d.cloud.SubscriptionID
6✔
981
        if len(d.cloud.NetworkResourceSubscriptionID) > 0 {
10✔
982
                subsID = d.cloud.NetworkResourceSubscriptionID
4✔
983
        }
4✔
984

985
        if len(vnetResourceGroup) == 0 {
10✔
986
                vnetResourceGroup = d.cloud.ResourceGroup
4✔
987
                if len(d.cloud.VnetResourceGroup) > 0 {
7✔
988
                        vnetResourceGroup = d.cloud.VnetResourceGroup
3✔
989
                }
3✔
990
        }
991

992
        if len(vnetName) == 0 {
10✔
993
                vnetName = d.cloud.VnetName
4✔
994
        }
4✔
995

996
        if len(subnetName) == 0 {
10✔
997
                subnetName = d.cloud.SubnetName
4✔
998
        }
4✔
999
        return fmt.Sprintf(subnetTemplate, subsID, vnetResourceGroup, vnetName, subnetName)
6✔
1000
}
1001

1002
func (d *Driver) useDataPlaneAPI(ctx context.Context, volumeID, accountName string) bool {
9✔
1003
        cache, err := d.dataPlaneAPIVolCache.Get(ctx, volumeID, azcache.CacheReadTypeDefault)
9✔
1004
        if err != nil {
9✔
1005
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", volumeID, err)
×
1006
        }
×
1007
        if cache != nil {
12✔
1008
                return true
3✔
1009
        }
3✔
1010
        cache, err = d.dataPlaneAPIVolCache.Get(ctx, accountName, azcache.CacheReadTypeDefault)
6✔
1011
        if err != nil {
6✔
1012
                klog.Errorf("get(%s) from dataPlaneAPIVolCache failed with error: %v", accountName, err)
×
1013
        }
×
1014
        if cache != nil {
6✔
1015
                return true
×
1016
        }
×
1017
        return false
6✔
1018
}
1019

1020
// appendDefaultMountOptions return mount options combined with mountOptions and defaultMountOptions
1021
func appendDefaultMountOptions(mountOptions []string, tmpPath, containerName string) []string {
4✔
1022
        var defaultMountOptions = map[string]string{
4✔
1023
                "--pre-mount-validate": "true",
4✔
1024
                "--use-https":          "true",
4✔
1025
                "--tmp-path":           tmpPath,
4✔
1026
                "--container-name":     containerName,
4✔
1027
                // prevent billing charges on mounting
4✔
1028
                "--cancel-list-on-mount-seconds": "10",
4✔
1029
                // allow remounting using a non-empty tmp-path
4✔
1030
                "--empty-dir-check": "false",
4✔
1031
        }
4✔
1032

4✔
1033
        // stores the mount options already included in mountOptions
4✔
1034
        included := make(map[string]bool)
4✔
1035

4✔
1036
        for _, mountOption := range mountOptions {
11✔
1037
                for k := range defaultMountOptions {
49✔
1038
                        if strings.HasPrefix(mountOption, k) {
46✔
1039
                                included[k] = true
4✔
1040
                        }
4✔
1041
                }
1042
        }
1043

1044
        allMountOptions := mountOptions
4✔
1045

4✔
1046
        for k, v := range defaultMountOptions {
28✔
1047
                if _, isIncluded := included[k]; !isIncluded {
44✔
1048
                        if v != "" {
40✔
1049
                                allMountOptions = append(allMountOptions, fmt.Sprintf("%s=%s", k, v))
20✔
1050
                        } else {
20✔
1051
                                allMountOptions = append(allMountOptions, k)
×
1052
                        }
×
1053
                }
1054
        }
1055

1056
        return allMountOptions
4✔
1057
}
1058

1059
// chmodIfPermissionMismatch only perform chmod when permission mismatches
1060
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
3✔
1061
        info, err := os.Lstat(targetPath)
3✔
1062
        if err != nil {
4✔
1063
                return err
1✔
1064
        }
1✔
1065
        perm := info.Mode() & os.ModePerm
2✔
1066
        if perm != mode {
3✔
1067
                klog.V(2).Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
1✔
1068
                if err := os.Chmod(targetPath, mode); err != nil {
1✔
1069
                        return err
×
1070
                }
×
1071
        } else {
1✔
1072
                klog.V(2).Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
1✔
1073
        }
1✔
1074
        return nil
2✔
1075
}
1076

1077
func createStorageAccountSecret(account, key string) map[string]string {
1✔
1078
        secret := make(map[string]string)
1✔
1079
        secret[defaultSecretAccountName] = account
1✔
1080
        secret[defaultSecretAccountKey] = key
1✔
1081
        return secret
1✔
1082
}
1✔
1083

1084
// setKeyValueInMap set key/value pair in map
1085
// key in the map is case insensitive, if key already exists, overwrite existing value
1086
func setKeyValueInMap(m map[string]string, key, value string) {
7✔
1087
        if m == nil {
8✔
1088
                return
1✔
1089
        }
1✔
1090
        for k := range m {
17✔
1091
                if strings.EqualFold(k, key) {
13✔
1092
                        m[k] = value
2✔
1093
                        return
2✔
1094
                }
2✔
1095
        }
1096
        m[key] = value
4✔
1097
}
1098

1099
// getValueInMap get value from map by key
1100
// key in the map is case insensitive
1101
func getValueInMap(m map[string]string, key string) string {
12✔
1102
        if m == nil {
13✔
1103
                return ""
1✔
1104
        }
1✔
1105
        for k, v := range m {
23✔
1106
                if strings.EqualFold(k, key) {
16✔
1107
                        return v
4✔
1108
                }
4✔
1109
        }
1110
        return ""
7✔
1111
}
1112

1113
// replaceWithMap replace key with value for str
1114
func replaceWithMap(str string, m map[string]string) string {
14✔
1115
        for k, v := range m {
19✔
1116
                if k != "" {
9✔
1117
                        str = strings.ReplaceAll(str, k, v)
4✔
1118
                }
4✔
1119
        }
1120
        return str
14✔
1121
}
1122

1123
func isSupportedFSGroupChangePolicy(policy string) bool {
27✔
1124
        if policy == "" {
47✔
1125
                return true
20✔
1126
        }
20✔
1127
        for _, v := range supportedFSGroupChangePolicyList {
25✔
1128
                if policy == v {
21✔
1129
                        return true
3✔
1130
                }
3✔
1131
        }
1132
        return false
4✔
1133
}
1134

1135
func isReadOnlyFromCapability(vc *csi.VolumeCapability) bool {
7✔
1136
        if vc.GetAccessMode() == nil {
9✔
1137
                return false
2✔
1138
        }
2✔
1139
        mode := vc.GetAccessMode().GetMode()
5✔
1140
        return (mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY ||
5✔
1141
                mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY)
5✔
1142
}
1143

1144
// generateVolumeName returns a PV name with clusterName prefix. The function
1145
// should be used to generate a name of GCE PD or Cinder volume. It basically
1146
// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
1147
// string fits given length and cuts "dynamic" if not.
1148
func generateVolumeName(clusterName, pvName string, maxLength int) string {
3✔
1149
        prefix := clusterName + "-dynamic"
3✔
1150
        pvLen := len(pvName)
3✔
1151
        // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
3✔
1152
        // +1 for the '-' dash
3✔
1153
        if pvLen+1+len(prefix) > maxLength {
5✔
1154
                prefix = prefix[:maxLength-pvLen-1]
2✔
1155
        }
2✔
1156
        return prefix + "-" + pvName
3✔
1157
}
1158

1159
// serviceAccountToken represents the service account token sent from NodePublishVolume Request.
1160
// ref: https://kubernetes-csi.github.io/docs/token-requests.html
1161
type serviceAccountToken struct {
1162
        APIAzureADTokenExchange struct {
1163
                Token               string    `json:"token"`
1164
                ExpirationTimestamp time.Time `json:"expirationTimestamp"`
1165
        } `json:"api://AzureADTokenExchange"`
1166
}
1167

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