Repository: kubernetes-sigs/gcp-compute-persistent-disk-csi-driver
Pull request #1292 (2023-07-11 22:54:21)

[release-1.10] Filter out GCE operation error codes caused by user errors
This is an automated cherry-pick of #1261

/assign amacaskill

```release-note
None
```


Patch:
diff --git a/pkg/gce-cloud-provider/compute/gce-compute.go b/pkg/gce-cloud-provider/compute/gce-compute.go
index a9adc551..5c20e686 100644
--- a/pkg/gce-cloud-provider/compute/gce-compute.go
+++ b/pkg/gce-cloud-provider/compute/gce-compute.go
@@ -859,6 +859,10 @@ func (cloud *CloudProvider) WaitForAttach(ctx context.Context, project string, v
 }
 
 func wrapOpErr(name string, opErr *computev1.OperationErrorErrors) error {
+	if opErr == nil {
+		return nil
+	}
+
 	if opErr.Code == "UNSUPPORTED_OPERATION" {
 		if diskType := pdDiskTypeUnsupportedRegex.FindStringSubmatch(opErr.Message); diskType != nil {
 			return &UnsupportedDiskError{
@@ -866,7 +870,29 @@ func wrapOpErr(name string, opErr *computev1.OperationErrorErrors) error {
 			}
 		}
 	}
-	return fmt.Errorf("operation %v failed (%v): %v", name, opErr.Code, opErr.Message)
+	grpcErrCode := codeForGCEOpError(*opErr)
+	return status.Errorf(grpcErrCode, "operation %v failed (%v): %v", name, opErr.Code, opErr.Message)
+}
+
+// codeForGCEOpError return the grpc error code for the passed in
+// gce operation error.
+func codeForGCEOpError(err computev1.OperationErrorErrors) codes.Code {
+	userErrors := map[string]codes.Code{
+		"RESOURCE_NOT_FOUND":                        codes.NotFound,
+		"RESOURCE_ALREADY_EXISTS":                   codes.AlreadyExists,
+		"RESOURCE_IN_USE_BY_ANOTHER_RESOURCE":       codes.InvalidArgument,
+		"OPERATION_CANCELED_BY_USER":                codes.Aborted,
+		"QUOTA_EXCEEDED":                            codes.ResourceExhausted,
+		"ZONE_RESOURCE_POOL_EXHAUSTED":              codes.Unavailable,
+		"ZONE_RESOURCE_POOL_EXHAUSTED_WITH_DETAILS": codes.Unavailable,
+		"REGION_QUOTA_EXCEEDED":                     codes.ResourceExhausted,
+		"RATE_LIMIT_EXCEEDED":                       codes.ResourceExhausted,
+		"INVALID_USAGE":                             codes.InvalidArgument,
+	}
+	if code, ok := userErrors[err.Code]; ok {
+		return code
+	}
+	return codes.Internal
 }
 
 func opIsDone(op *computev1.Operation) (bool, error) {


Test patch:
diff --git a/pkg/gce-cloud-provider/compute/gce-compute_test.go b/pkg/gce-cloud-provider/compute/gce-compute_test.go
index 1a9c9f6e..652b111a 100644
--- a/pkg/gce-cloud-provider/compute/gce-compute_test.go
+++ b/pkg/gce-cloud-provider/compute/gce-compute_test.go
@@ -18,6 +18,7 @@ import (
 	"testing"
 
 	computev1 "google.golang.org/api/compute/v1"
+	"google.golang.org/grpc/codes"
 	"sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common"
 )
 
@@ -101,3 +102,75 @@ func TestValidateDiskParameters(t *testing.T) {
 		}
 	}
 }
+
+func TestCodeForGCEOpError(t *testing.T) {
+	testCases := []struct {
+		name     string
+		inputErr computev1.OperationErrorErrors
+		expCode  codes.Code
+	}{
+		{
+			name:     "RESOURCE_NOT_FOUND error",
+			inputErr: computev1.OperationErrorErrors{Code: "RESOURCE_NOT_FOUND"},
+			expCode:  codes.NotFound,
+		},
+		{
+			name:     "RESOURCE_ALREADY_EXISTS error",
+			inputErr: computev1.OperationErrorErrors{Code: "RESOURCE_ALREADY_EXISTS"},
+			expCode:  codes.AlreadyExists,
+		},
+		{
+			name:     "OPERATION_CANCELED_BY_USER error",
+			inputErr: computev1.OperationErrorErrors{Code: "OPERATION_CANCELED_BY_USER"},
+			expCode:  codes.Aborted,
+		},
+		{
+			name:     "QUOTA_EXCEEDED error",
+			inputErr: computev1.OperationErrorErrors{Code: "QUOTA_EXCEEDED"},
+			expCode:  codes.ResourceExhausted,
+		},
+		{
+			name:     "ZONE_RESOURCE_POOL_EXHAUSTED error",
+			inputErr: computev1.OperationErrorErrors{Code: "ZONE_RESOURCE_POOL_EXHAUSTED"},
+			expCode:  codes.Unavailable,
+		},
+		{
+			name:     "ZONE_RESOURCE_POOL_EXHAUSTED_WITH_DETAILS error",
+			inputErr: computev1.OperationErrorErrors{Code: "ZONE_RESOURCE_POOL_EXHAUSTED_WITH_DETAILS"},
+			expCode:  codes.Unavailable,
+		},
+		{
+			name:     "REGION_QUOTA_EXCEEDED error",
+			inputErr: computev1.OperationErrorErrors{Code: "REGION_QUOTA_EXCEEDED"},
+			expCode:  codes.ResourceExhausted,
+		},
+		{
+			name:     "RATE_LIMIT_EXCEEDED error",
+			inputErr: computev1.OperationErrorErrors{Code: "RATE_LIMIT_EXCEEDED"},
+			expCode:  codes.ResourceExhausted,
+		},
+		{
+			name:     "INVALID_USAGE error",
+			inputErr: computev1.OperationErrorErrors{Code: "INVALID_USAGE"},
+			expCode:  codes.InvalidArgument,
+		},
+		{
+			name:     "RESOURCE_IN_USE_BY_ANOTHER_RESOURCE error",
+			inputErr: computev1.OperationErrorErrors{Code: "RESOURCE_IN_USE_BY_ANOTHER_RESOURCE"},
+			expCode:  codes.InvalidArgument,
+		},
+		{
+			name:     "UNSUPPORTED_OPERATION error",
+			inputErr: computev1.OperationErrorErrors{Code: "UNSUPPORTED_OPERATION"},
+			expCode:  codes.Internal,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Logf("Running test: %v", tc.name)
+		errCode := codeForGCEOpError(tc.inputErr)
+		if errCode != tc.expCode {
+			t.Errorf("test %v failed: got %v, expected %v", tc.name, errCode, tc.expCode)
+		}
+	}
+}