Skip to content

Commit ebf6e29

Browse files
authored
Merge pull request #1265 from amacaskill/automated-cherry-pick-of-#1263-upstream-release-1.22
Automated cherry pick of #1263: Change bucket access check from ListObjects to GetStorageLayout
2 parents ed4e23b + 96df752 commit ebf6e29

3 files changed

Lines changed: 85 additions & 28 deletions

File tree

pkg/cloud_provider/storage/storage.go

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ import (
2929

3030
"cloud.google.com/go/iam"
3131
"cloud.google.com/go/storage"
32+
control "cloud.google.com/go/storage/control/apiv2"
33+
"cloud.google.com/go/storage/control/apiv2/controlpb"
3234
"cloud.google.com/go/storage/experimental"
3335
foUtils "github.com/googlecloudplatform/gcs-fuse-csi-driver/pkg/util/goclientobjectcommands"
3436
"golang.org/x/oauth2"
3537
"google.golang.org/api/iterator"
3638
"google.golang.org/api/option"
3739
"google.golang.org/grpc/codes"
40+
"google.golang.org/grpc/status"
3841
"k8s.io/apimachinery/pkg/util/wait"
3942
"k8s.io/klog/v2"
4043
)
@@ -70,6 +73,8 @@ type ServiceManager interface {
7073

7174
type gcsService struct {
7275
storageClient *storage.Client
76+
// The control client is for the GetStorageLayout bucket access check.
77+
controlClient *control.StorageControlClient
7378
}
7479

7580
type gcsServiceManager struct{}
@@ -97,7 +102,16 @@ func (manager *gcsServiceManager) SetupService(ctx context.Context, ts oauth2.To
97102
return nil, err
98103
}
99104

100-
return &gcsService{storageClient: storageClient}, nil
105+
controlClient, err := control.NewStorageControlClient(ctx, option.WithTokenSource(ts))
106+
if err != nil {
107+
storageClient.Close()
108+
return nil, err
109+
}
110+
111+
return &gcsService{
112+
storageClient: storageClient,
113+
controlClient: controlClient,
114+
}, nil
101115
}
102116

103117
func (manager *gcsServiceManager) SetupServiceWithDefaultCredential(ctx context.Context, enableZB bool) (Service, error) {
@@ -112,7 +126,18 @@ func (manager *gcsServiceManager) SetupServiceWithDefaultCredential(ctx context.
112126
return nil, err
113127
}
114128

115-
return &gcsService{storageClient: storageClient}, nil
129+
var controlClient *control.StorageControlClient
130+
// Without explicit auth options, it automatically uses Application Default Credentials.
131+
controlClient, err = control.NewStorageControlClient(ctx)
132+
if err != nil {
133+
storageClient.Close()
134+
return nil, err
135+
}
136+
137+
return &gcsService{
138+
storageClient: storageClient,
139+
controlClient: controlClient,
140+
}, nil
116141
}
117142

118143
func (service *gcsService) CreateBucket(ctx context.Context, obj *ServiceBucket) (*ServiceBucket, error) {
@@ -201,14 +226,24 @@ func (service *gcsService) GetBucket(ctx context.Context, obj *ServiceBucket) (*
201226
return nil, fmt.Errorf("failed to get bucket %q: got empty attrs", obj.Name)
202227
}
203228

229+
// CheckBucketExists checks the bucket access the bucket exists by calling the
230+
// API's GetStorageLayout method which uses storage.objects.list permission.
231+
// If the customer hasn't set up permission to the bucket, this will return a
232+
// 403 error. A 404 error will be returned if the bucket doesn't exist, but
233+
// correct permissions are configured.
204234
func (service *gcsService) CheckBucketExists(ctx context.Context, obj *ServiceBucket) (bool, error) {
205-
bkt := service.storageClient.Bucket(obj.Name)
206-
_, err := bkt.Objects(ctx, &storage.Query{Prefix: ""}).Next()
235+
// This call matches the access check GCSFuse does:
236+
// https://github.com/GoogleCloudPlatform/gcsfuse/blob/919dbde3e074ff8f6a2cc7f7b612863542a1fb90/internal/storage/storage_handle.go#L319
237+
req := &controlpb.GetStorageLayoutRequest{
238+
Name: fmt.Sprintf("projects/_/buckets/%s/storageLayout", obj.Name),
239+
Prefix: "",
240+
RequestId: "",
241+
}
207242

208-
if err == nil || errors.Is(err, iterator.Done) {
243+
_, err := service.controlClient.GetStorageLayout(ctx, req)
244+
if err == nil {
209245
return true, nil
210246
}
211-
212247
return false, err
213248
}
214249

@@ -244,6 +279,7 @@ func (service *gcsService) RemoveIAMPolicy(ctx context.Context, obj *ServiceBuck
244279

245280
func (service *gcsService) Close() {
246281
service.storageClient.Close()
282+
service.controlClient.Close()
247283
}
248284

249285
func cloudBucketToServiceBucket(attrs *storage.BucketAttrs) (*ServiceBucket, error) {
@@ -289,21 +325,31 @@ func isCanceledErr(err error) bool {
289325
}
290326

291327
// ParseErrCode parses error and returns a gRPC code.
328+
// This function is necessary because the gcsService uses two different Google Cloud
329+
// clients: a gRPC-based StorageControlClient and a JSON-based vanilla GCS client.
330+
// These clients return different error types.
331+
//
332+
// The function first checks if the error is a gRPC status error.
333+
// If so, it returns the gRPC code. This is the case for errors from
334+
// the StorageControlClient (e.g., in CheckBucketExists).
335+
// If not, it falls back to checking for legacy error types used by the
336+
// vanilla GCS client (e.g., in GetBucket).
292337
func ParseErrCode(err error) codes.Code {
293-
code := codes.Internal
294-
if IsNotExistErr(err) {
295-
code = codes.NotFound
338+
if s, ok := status.FromError(err); ok {
339+
return s.Code()
296340
}
297341

342+
if IsNotExistErr(err) {
343+
return codes.NotFound
344+
}
298345
if isPermissionDeniedErr(err) {
299-
code = codes.PermissionDenied
346+
return codes.PermissionDenied
300347
}
301-
302348
if isCanceledErr(err) {
303-
code = codes.Aborted
349+
return codes.Aborted
304350
}
305351

306-
return code
352+
return codes.Internal
307353
}
308354

309355
// UploadGCSObject uploads a local file to a specified GCS bucket and object.
@@ -387,22 +433,32 @@ func isBucketAZonalBucket(ctx context.Context, client *storage.Client, bucketNam
387433
}
388434

389435
func (manager *gcsServiceManager) SetupStorageServiceForSidecar(ctx context.Context, ts oauth2.TokenSource) (Service, error) {
390-
var err error
391-
var storageClient *storage.Client
436+
var storageOpts []option.ClientOption
437+
var controlOpts []option.ClientOption
438+
392439
// For workload identity enabled resources we need to create the storage service with default credentials so as to not consume more STS quota.
393440
// The token source thus is only shared for host network enabled workload. If token source is nil then create storage client with default credentials else use the tokenSource.
394441
// This is needed as the storage API checks calls TokenSource.Token() function (defined above) which leads to increased STS quota since we are directly hitting the STS API.
395442
if ts != nil {
396443
client := oauth2.NewClient(ctx, ts)
397-
storageClient, err = storage.NewClient(ctx, option.WithHTTPClient(client))
398-
} else {
399-
storageClient, err = storage.NewClient(ctx)
444+
storageOpts = append(storageOpts, option.WithHTTPClient(client))
445+
controlOpts = append(controlOpts, option.WithTokenSource(ts))
400446
}
401-
// Storage client is expected to be created by either path, return the error if storage client creation fails
402-
if err != nil || storageClient == nil {
403-
klog.Errorf("Errored while creating with tokensource %v, got storage client %v", err, storageClient)
404-
return nil, err
447+
448+
storageClient, err := storage.NewClient(ctx, storageOpts...)
449+
if err != nil {
450+
return nil, fmt.Errorf("failed to create storage client: %w", err)
405451
}
406-
klog.Infof("Storage service client created successfully, %v", storageClient)
407-
return &gcsService{storageClient: storageClient}, nil
452+
453+
controlClient, err := control.NewStorageControlClient(ctx, controlOpts...)
454+
if err != nil {
455+
storageClient.Close()
456+
return nil, fmt.Errorf("failed to create control client: %w", err)
457+
}
458+
459+
klog.Infof("Storage client and control client created successfully for sidecar.")
460+
return &gcsService{
461+
storageClient: storageClient,
462+
controlClient: controlClient,
463+
}, nil
408464
}

test/e2e/specs/specs.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"k8s.io/apimachinery/pkg/api/resource"
3535
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3636
"k8s.io/apimachinery/pkg/fields"
37+
"k8s.io/apimachinery/pkg/util/rand"
3738
"k8s.io/apimachinery/pkg/util/wait"
3839

3940
clientset "k8s.io/client-go/kubernetes"
@@ -76,7 +77,6 @@ const (
7677
EnableMetadataPrefetchPrefixForceNewBucketPrefix = "gcsfuse-csi-enable-metadata-prefetch-and-force-new-bucket"
7778
EnableMetadataPrefetchAndInvalidMountOptionsVolumePrefix = "gcsfuse-csi-enable-metadata-prefetch-and-invalid-mount-options-volume"
7879
ImplicitDirsPath = "implicit-dir"
79-
InvalidVolume = "<invalid-name>"
8080
OptInHnwKSAPrefix = "opt-in-hnw-ksa"
8181
SkipCSIBucketAccessCheckPrefix = "gcsfuse-csi-skip-bucket-access-check"
8282
SkipCSIBucketAccessCheckAndFakeVolumePrefix = "gcsfuse-csi-skip-bucket-access-check-fake-volume"
@@ -117,6 +117,8 @@ const (
117117
IsOSSEnvVar = "IS_OSS"
118118
)
119119

120+
var InvalidVolume = fmt.Sprintf("non-existent-test-bucket-%s", rand.String(8))
121+
120122
// Note to developers adding new testing methods - Please check the code path of newly added methods and ensure that those requiring
121123
// konnectivity agents are wrapped with retry logic, see `runKubectlWithFullOutputWithRetry` as an example.
122124
// See here for the list of commands that require the agents - go/konnectivity-network-proxy#egress_traffic.

test/e2e/testsuites/failed_mount.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func (t *gcsFuseCSIFailedMountTestSuite) DefineTests(driver storageframework.Tes
160160
if configPrefix == specs.SkipCSIBucketAccessCheckAndFakeVolumePrefix && (err != nil || v.AtLeast(version.MustParseSemantic("v2.5.0-gke.0"))) {
161161
tPod.WaitForLog(ctx, webhook.GcsFuseSidecarName, "bucket does not exist")
162162
} else {
163-
tPod.WaitForFailedMountError(ctx, "storage: bucket doesn't exist")
163+
tPod.WaitForFailedMountError(ctx, codes.NotFound.String())
164164
}
165165
}
166166

@@ -205,14 +205,13 @@ func (t *gcsFuseCSIFailedMountTestSuite) DefineTests(driver storageframework.Tes
205205
if configPrefix == specs.SkipCSIBucketAccessCheckAndInvalidVolumePrefix && (err != nil || v.AtLeast(version.MustParseSemantic("v2.9.0-gke.0"))) {
206206
if enableSidecarBucketAccessCheck {
207207
tPod.WaitForFailedContainerError(ctx, "Error: failed to reserve container name")
208-
tPod.WaitForLog(ctx, webhook.GcsFuseSidecarName, "storage: bucket doesn't exist")
208+
tPod.WaitForLog(ctx, webhook.GcsFuseSidecarName, codes.NotFound.String())
209209
} else {
210210
tPod.WaitForFailedMountError(ctx, codes.InvalidArgument.String())
211211
tPod.WaitForFailedMountError(ctx, "name should be a valid bucket resource name")
212212
}
213213
} else {
214214
tPod.WaitForFailedMountError(ctx, codes.NotFound.String())
215-
tPod.WaitForFailedMountError(ctx, "storage: bucket doesn't exist")
216215
}
217216
}
218217

0 commit comments

Comments
 (0)