Skip to content

Commit a27c702

Browse files
committed
Properly unwrap gce-compute error code.
1 parent 629ddec commit a27c702

File tree

3 files changed

+122
-9
lines changed

3 files changed

+122
-9
lines changed

pkg/common/utils.go

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"time"
2727

2828
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
29+
"github.com/googleapis/gax-go/v2/apierror"
2930
"golang.org/x/time/rate"
3031
"google.golang.org/api/googleapi"
3132
"google.golang.org/grpc/codes"
@@ -423,7 +424,6 @@ func CodeForError(sourceError error) codes.Code {
423424
if sourceError == nil {
424425
return codes.Internal
425426
}
426-
427427
if code, err := isUserMultiAttachError(sourceError); err == nil {
428428
return code
429429
}
@@ -433,12 +433,7 @@ func CodeForError(sourceError error) codes.Code {
433433
if code, err := isContextError(sourceError); err == nil {
434434
return code
435435
}
436-
437-
var apiErr *googleapi.Error
438-
if !errors.As(sourceError, &apiErr) {
439-
return codes.Internal
440-
}
441-
if code, ok := userErrorCodeMap[apiErr.Code]; ok {
436+
if code, err := isGoogleAPIError(sourceError); err == nil {
442437
return code
443438
}
444439

@@ -475,16 +470,64 @@ func isUserMultiAttachError(err error) (codes.Code, error) {
475470
return codes.Unknown, fmt.Errorf("Not a user multiattach error: %w", err)
476471
}
477472

473+
// existingErrorCode returns the existing gRPC Status error code for the given error, if one exists,
474+
// or Unknown if one doesn't exist. Since github.com/googleapis/gax-go/v2/apierror now wraps googleapi
475+
// errors (returned from GCE API calls), and sets their status error code to Unknown, we now have to
476+
// make sure we only return existing error codes from errors that are either TemporaryErrors, or errors
477+
// that do not wrap googleAPI errors. Otherwise, we will return Unknown for all GCE API calls that
478+
// return googleapi errors.
478479
func existingErrorCode(err error) (codes.Code, error) {
479480
if err == nil {
480481
return codes.Unknown, fmt.Errorf("null error")
481482
}
482-
if status, ok := status.FromError(err); ok {
483-
return status.Code(), nil
483+
var tmpError *TemporaryError
484+
// This explicitly checks our error is a temporary error before extracting its
485+
// status, as there can be other errors that can qualify as statusable
486+
// while not necessarily being temporary.
487+
if errors.As(err, &tmpError) {
488+
if status, ok := status.FromError(err); ok {
489+
return status.Code(), nil
490+
}
491+
}
492+
// We want to make sure we catch other error types that are statusable.
493+
// (eg. grpc-go/internal/status/status.go Error struct that wraps a status)
494+
var googleErr *googleapi.Error
495+
if !errors.As(err, &googleErr) {
496+
if status, ok := status.FromError(err); ok {
497+
return status.Code(), nil
498+
}
484499
}
500+
485501
return codes.Unknown, fmt.Errorf("no existing error code for %w", err)
486502
}
487503

504+
// isGoogleAPIError returns the gRPC status code for the given googleapi error by mapping
505+
// the googleapi error's HTTP code to the corresponding gRPC error code. If the error is
506+
// wrapped in an APIError (github.com/googleapis/gax-go/v2/apierror), it maps the wrapped
507+
// googleAPI error's HTTP code to the corresponding gRPC error code. Returns an error if
508+
// the given error is not a googleapi error.
509+
func isGoogleAPIError(err error) (codes.Code, error) {
510+
var googleErr *googleapi.Error
511+
if !errors.As(err, &googleErr) {
512+
return codes.Unknown, fmt.Errorf("error %w is not a googleapi.Error", err)
513+
}
514+
var sourceCode int
515+
var apiErr *apierror.APIError
516+
if errors.As(err, &apiErr) {
517+
// When googleapi.Err is used as a wrapper, we return the error code of the wrapped contents.
518+
sourceCode = apiErr.HTTPCode()
519+
} else {
520+
// Rely on error code in googleapi.Err when it is our primary error.
521+
sourceCode = googleErr.Code
522+
}
523+
// Map API error code to user error code.
524+
if code, ok := userErrorCodeMap[sourceCode]; ok {
525+
return code, nil
526+
}
527+
528+
return codes.Unknown, fmt.Errorf("googleapi.Error %w does not map to any known errors", err)
529+
}
530+
488531
func LoggedError(msg string, err error) error {
489532
klog.Errorf(msg+"%v", err.Error())
490533
return status.Errorf(CodeForError(err), msg+"%v", err.Error())

pkg/common/utils_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
2828
"github.com/google/go-cmp/cmp"
29+
"github.com/googleapis/gax-go/v2/apierror"
2930
"google.golang.org/api/googleapi"
3031
"google.golang.org/grpc/codes"
3132
"google.golang.org/grpc/status"
@@ -1205,6 +1206,17 @@ func TestParseMachineType(t *testing.T) {
12051206
}
12061207

12071208
func TestCodeForError(t *testing.T) {
1209+
getGoogleAPIWrappedError := func(err error) *googleapi.Error {
1210+
apierr, _ := apierror.ParseError(err, false)
1211+
wrappedError := &googleapi.Error{}
1212+
wrappedError.Wrap(apierr)
1213+
1214+
return wrappedError
1215+
}
1216+
getAPIError := func(err error) *apierror.APIError {
1217+
apierror, _ := apierror.ParseError(err, true)
1218+
return apierror
1219+
}
12081220
testCases := []struct {
12091221
name string
12101222
inputErr error
@@ -1220,6 +1232,36 @@ func TestCodeForError(t *testing.T) {
12201232
inputErr: &googleapi.Error{Code: http.StatusBadRequest, Message: "User error with bad request"},
12211233
expCode: codes.InvalidArgument,
12221234
},
1235+
{
1236+
name: "googleapi.Error that wraps apierror.APIError of http kind",
1237+
inputErr: getGoogleAPIWrappedError(&googleapi.Error{
1238+
Code: 404,
1239+
Message: "data requested not found error",
1240+
}),
1241+
expCode: codes.NotFound,
1242+
},
1243+
{
1244+
name: "googleapi.Error that wraps apierror.APIError of status kind",
1245+
inputErr: getGoogleAPIWrappedError(status.New(
1246+
codes.Internal, "Internal status error",
1247+
).Err()),
1248+
expCode: codes.Internal,
1249+
},
1250+
{
1251+
name: "apierror.APIError of http kind",
1252+
inputErr: getAPIError(&googleapi.Error{
1253+
Code: 404,
1254+
Message: "data requested not found error",
1255+
}),
1256+
expCode: codes.NotFound,
1257+
},
1258+
{
1259+
name: "apierror.APIError of status kind",
1260+
inputErr: getAPIError(status.New(
1261+
codes.Canceled, "Internal status error",
1262+
).Err()),
1263+
expCode: codes.Canceled,
1264+
},
12231265
{
12241266
name: "googleapi.Error but not a user error",
12251267
inputErr: &googleapi.Error{Code: http.StatusInternalServerError, Message: "Internal error"},

test/e2e/tests/single_zone_e2e_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const (
5050
defaultRepdSizeGb int64 = 200
5151
defaultMwSizeGb int64 = 200
5252
defaultVolumeLimit int64 = 127
53+
invalidSizeGb int64 = 66000
5354
readyState = "READY"
5455
standardDiskType = "pd-standard"
5556
ssdDiskType = "pd-ssd"
@@ -269,6 +270,33 @@ var _ = Describe("GCE PD CSI Driver", func() {
269270
}
270271
})
271272

273+
It("Should return InvalidArgument when disk size exceeds limit", func() {
274+
// If this returns a different error code (like Unknown), the error wrapping logic in #1708 has regressed.
275+
Expect(testContexts).ToNot(BeEmpty())
276+
testContext := getRandomTestContext()
277+
278+
zones := []string{"us-central1-c", "us-central1-b", "us-central1-a"}
279+
280+
for _, zone := range zones {
281+
volName := testNamePrefix + string(uuid.NewUUID())
282+
topReq := &csi.TopologyRequirement{
283+
Requisite: []*csi.Topology{
284+
{
285+
Segments: map[string]string{common.TopologyKeyZone: zone},
286+
},
287+
},
288+
}
289+
volume, err := testContext.Client.CreateVolume(volName, nil, invalidSizeGb, topReq, nil)
290+
Expect(err).ToNot(BeNil(), "Failed to fetch error from create volume.")
291+
Expect(err.Error()).To(ContainSubstring("InvalidArgument"), "Failed to verify error code matches InvalidArgument.")
292+
defer func() {
293+
if volume != nil {
294+
testContext.Client.DeleteVolume(volume.VolumeId)
295+
}
296+
}()
297+
}
298+
})
299+
272300
DescribeTable("Should complete entire disk lifecycle with underspecified volume ID",
273301
func(diskType string) {
274302
testContext := getRandomTestContext()

0 commit comments

Comments
 (0)