Skip to content

Commit 4b2711b

Browse files
authored
Merge pull request #1708 from hime/master
Properly unwrap gce-compute error code.
2 parents 82c6deb + 384acf8 commit 4b2711b

File tree

3 files changed

+122
-9
lines changed

3 files changed

+122
-9
lines changed

pkg/common/utils.go

+52-9
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
}
@@ -436,12 +436,7 @@ func CodeForError(sourceError error) codes.Code {
436436
if code, err := isConnectionResetError(sourceError); err == nil {
437437
return code
438438
}
439-
440-
var apiErr *googleapi.Error
441-
if !errors.As(sourceError, &apiErr) {
442-
return codes.Internal
443-
}
444-
if code, ok := userErrorCodeMap[apiErr.Code]; ok {
439+
if code, err := isGoogleAPIError(sourceError); err == nil {
445440
return code
446441
}
447442

@@ -492,16 +487,64 @@ func isUserMultiAttachError(err error) (codes.Code, error) {
492487
return codes.Unknown, fmt.Errorf("Not a user multiattach error: %w", err)
493488
}
494489

490+
// existingErrorCode returns the existing gRPC Status error code for the given error, if one exists,
491+
// or an error if one doesn't exist. Since github.com/googleapis/gax-go/v2/apierror now wraps googleapi
492+
// errors (returned from GCE API calls), and sets their status error code to Unknown, we now have to
493+
// make sure we only return existing error codes from errors that are either TemporaryErrors, or errors
494+
// that do not wrap googleAPI errors. Otherwise, we will return Unknown for all GCE API calls that
495+
// return googleapi errors.
495496
func existingErrorCode(err error) (codes.Code, error) {
496497
if err == nil {
497498
return codes.Unknown, fmt.Errorf("null error")
498499
}
499-
if status, ok := status.FromError(err); ok {
500-
return status.Code(), nil
500+
var tmpError *TemporaryError
501+
// This explicitly checks our error is a temporary error before extracting its
502+
// status, as there can be other errors that can qualify as statusable
503+
// while not necessarily being temporary.
504+
if errors.As(err, &tmpError) {
505+
if status, ok := status.FromError(err); ok {
506+
return status.Code(), nil
507+
}
508+
}
509+
// We want to make sure we catch other error types that are statusable.
510+
// (eg. grpc-go/internal/status/status.go Error struct that wraps a status)
511+
var googleErr *googleapi.Error
512+
if !errors.As(err, &googleErr) {
513+
if status, ok := status.FromError(err); ok {
514+
return status.Code(), nil
515+
}
501516
}
517+
502518
return codes.Unknown, fmt.Errorf("no existing error code for %w", err)
503519
}
504520

521+
// isGoogleAPIError returns the gRPC status code for the given googleapi error by mapping
522+
// the googleapi error's HTTP code to the corresponding gRPC error code. If the error is
523+
// wrapped in an APIError (github.com/googleapis/gax-go/v2/apierror), it maps the wrapped
524+
// googleAPI error's HTTP code to the corresponding gRPC error code. Returns an error if
525+
// the given error is not a googleapi error.
526+
func isGoogleAPIError(err error) (codes.Code, error) {
527+
var googleErr *googleapi.Error
528+
if !errors.As(err, &googleErr) {
529+
return codes.Unknown, fmt.Errorf("error %w is not a googleapi.Error", err)
530+
}
531+
var sourceCode int
532+
var apiErr *apierror.APIError
533+
if errors.As(err, &apiErr) {
534+
// When googleapi.Err is used as a wrapper, we return the error code of the wrapped contents.
535+
sourceCode = apiErr.HTTPCode()
536+
} else {
537+
// Rely on error code in googleapi.Err when it is our primary error.
538+
sourceCode = googleErr.Code
539+
}
540+
// Map API error code to user error code.
541+
if code, ok := userErrorCodeMap[sourceCode]; ok {
542+
return code, nil
543+
}
544+
545+
return codes.Unknown, fmt.Errorf("googleapi.Error %w does not map to any known errors", err)
546+
}
547+
505548
func LoggedError(msg string, err error) error {
506549
klog.Errorf(msg+"%v", err.Error())
507550
return status.Errorf(CodeForError(err), msg+"%v", err.Error())

pkg/common/utils_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

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

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

test/e2e/tests/single_zone_e2e_test.go

+28
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"
@@ -268,6 +269,33 @@ var _ = Describe("GCE PD CSI Driver", func() {
268269
Expect(err).To(BeNil(), "Could not find disk in correct zone")
269270
}
270271
})
272+
// TODO(hime): Enable this test once all release branches contain the fix from PR#1708.
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+
// })
271299

272300
DescribeTable("Should complete entire disk lifecycle with underspecified volume ID",
273301
func(diskType string) {

0 commit comments

Comments
 (0)