Skip to content

Commit 256c7c3

Browse files
authored
Merge pull request #1244 from sunnylovestiramisu/automated-cherry-pick-of-#1150-#1232-#1227-upstream-release-1.8
Automated cherry pick of #1150: fix bug where volume cloning topology requirements are #1232: Use errors.As so we can detect wrapped errors, and check for #1227: Adding new metric pdcsi_operation_errors to fetch error
2 parents d630e83 + 78b3946 commit 256c7c3

File tree

12 files changed

+1562
-285
lines changed

12 files changed

+1562
-285
lines changed

cmd/gce-pd-csi-driver/main.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,14 @@ func handle() {
9494
}
9595
klog.V(2).Infof("Driver vendor version %v", version)
9696

97-
if *runControllerService && *httpEndpoint != "" && metrics.IsGKEComponentVersionAvailable() {
97+
if *runControllerService && *httpEndpoint != "" {
9898
mm := metrics.NewMetricsManager()
9999
mm.InitializeHttpHandler(*httpEndpoint, *metricsPath)
100-
mm.EmitGKEComponentVersion()
100+
mm.RegisterPDCSIMetric()
101+
102+
if metrics.IsGKEComponentVersionAvailable() {
103+
mm.EmitGKEComponentVersion()
104+
}
101105
}
102106

103107
if len(*extraVolumeLabelsStr) > 0 && !*runControllerService {

pkg/common/utils.go

+99-12
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,21 @@ limitations under the License.
1717
package common
1818

1919
import (
20+
"context"
21+
"errors"
2022
"fmt"
23+
"net/http"
2124
"regexp"
2225
"strings"
2326

2427
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
28+
"google.golang.org/api/googleapi"
29+
"google.golang.org/grpc/codes"
30+
"google.golang.org/grpc/status"
2531
"k8s.io/apimachinery/pkg/api/resource"
2632
"k8s.io/apimachinery/pkg/util/sets"
2733
volumehelpers "k8s.io/cloud-provider/volume/helpers"
34+
"k8s.io/klog/v2"
2835
)
2936

3037
const (
@@ -259,18 +266,6 @@ func ValidateSnapshotType(snapshotType string) error {
259266
}
260267
}
261268

262-
// ParseMachineType returns an extracted machineType from a URL, or empty if not found.
263-
// machineTypeUrl: Full or partial URL of the machine type resource, in the format:
264-
//
265-
// zones/zone/machineTypes/machine-type
266-
func ParseMachineType(machineTypeUrl string) (string, error) {
267-
machineType := machineTypeRegex.FindStringSubmatch(machineTypeUrl)
268-
if machineType == nil {
269-
return "", fmt.Errorf("failed to parse machineTypeUrl. Expected suffix: zones/{zone}/machineTypes/{machine-type}. Got: %s", machineTypeUrl)
270-
}
271-
return machineType[1], nil
272-
}
273-
274269
// ConvertStringToInt64 converts a string to int64
275270
func ConvertStringToInt64(str string) (int64, error) {
276271
quantity, err := resource.ParseQuantity(str)
@@ -288,3 +283,95 @@ func ConvertMiStringToInt64(str string) (int64, error) {
288283
}
289284
return volumehelpers.RoundUpToMiB(quantity)
290285
}
286+
287+
// ParseMachineType returns an extracted machineType from a URL, or empty if not found.
288+
// machineTypeUrl: Full or partial URL of the machine type resource, in the format:
289+
//
290+
// zones/zone/machineTypes/machine-type
291+
func ParseMachineType(machineTypeUrl string) (string, error) {
292+
machineType := machineTypeRegex.FindStringSubmatch(machineTypeUrl)
293+
if machineType == nil {
294+
return "", fmt.Errorf("failed to parse machineTypeUrl. Expected suffix: zones/{zone}/machineTypes/{machine-type}. Got: %s", machineTypeUrl)
295+
}
296+
return machineType[1], nil
297+
}
298+
299+
// CodeForError returns a pointer to the grpc error code that maps to the http
300+
// error code for the passed in user googleapi error or context error. Returns
301+
// codes.Internal if the given error is not a googleapi error caused by the user.
302+
// The following http error codes are considered user errors:
303+
// (1) http 400 Bad Request, returns grpc InvalidArgument,
304+
// (2) http 403 Forbidden, returns grpc PermissionDenied,
305+
// (3) http 404 Not Found, returns grpc NotFound
306+
// (4) http 429 Too Many Requests, returns grpc ResourceExhausted
307+
// The following errors are considered context errors:
308+
// (1) "context deadline exceeded", returns grpc DeadlineExceeded,
309+
// (2) "context canceled", returns grpc Canceled
310+
func CodeForError(err error) *codes.Code {
311+
if err == nil {
312+
return nil
313+
}
314+
315+
if errCode := existingErrorCode(err); errCode != nil {
316+
return errCode
317+
}
318+
if code := isContextError(err); code != nil {
319+
return code
320+
}
321+
322+
internalErrorCode := codes.Internal
323+
// Upwrap the error
324+
var apiErr *googleapi.Error
325+
if !errors.As(err, &apiErr) {
326+
return &internalErrorCode
327+
}
328+
329+
userErrors := map[int]codes.Code{
330+
http.StatusForbidden: codes.PermissionDenied,
331+
http.StatusBadRequest: codes.InvalidArgument,
332+
http.StatusTooManyRequests: codes.ResourceExhausted,
333+
http.StatusNotFound: codes.NotFound,
334+
}
335+
if code, ok := userErrors[apiErr.Code]; ok {
336+
return &code
337+
}
338+
339+
return &internalErrorCode
340+
}
341+
342+
// isContextError returns a pointer to the grpc error code DeadlineExceeded
343+
// if the passed in error contains the "context deadline exceeded" string and returns
344+
// the grpc error code Canceled if the error contains the "context canceled" string.
345+
func isContextError(err error) *codes.Code {
346+
if err == nil {
347+
return nil
348+
}
349+
350+
errStr := err.Error()
351+
if strings.Contains(errStr, context.DeadlineExceeded.Error()) {
352+
return errCodePtr(codes.DeadlineExceeded)
353+
}
354+
if strings.Contains(errStr, context.Canceled.Error()) {
355+
return errCodePtr(codes.Canceled)
356+
}
357+
return nil
358+
}
359+
360+
func existingErrorCode(err error) *codes.Code {
361+
if err == nil {
362+
return nil
363+
}
364+
if status, ok := status.FromError(err); ok {
365+
return errCodePtr(status.Code())
366+
}
367+
return nil
368+
}
369+
370+
func errCodePtr(code codes.Code) *codes.Code {
371+
return &code
372+
}
373+
374+
func LoggedError(msg string, err error) error {
375+
klog.Errorf(msg+"%v", err.Error())
376+
return status.Errorf(*CodeForError(err), msg+"%v", err.Error())
377+
}

pkg/common/utils_test.go

+166-54
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@ limitations under the License.
1717
package common
1818

1919
import (
20+
"context"
21+
"errors"
2022
"fmt"
23+
"net/http"
2124
"reflect"
2225
"testing"
2326

2427
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
28+
"google.golang.org/api/googleapi"
29+
"google.golang.org/grpc/codes"
30+
"google.golang.org/grpc/status"
2531
)
2632

2733
const (
@@ -578,60 +584,6 @@ func TestSnapshotStorageLocations(t *testing.T) {
578584
}
579585
}
580586

581-
func TestParseMachineType(t *testing.T) {
582-
tests := []struct {
583-
desc string
584-
inputMachineTypeUrl string
585-
expectedMachineType string
586-
expectError bool
587-
}{
588-
{
589-
desc: "full URL machine type",
590-
inputMachineTypeUrl: "https://www.googleapis.com/compute/v1/projects/my-project/zones/us-central1-c/machineTypes/c3-highcpu-4",
591-
expectedMachineType: "c3-highcpu-4",
592-
},
593-
{
594-
desc: "partial URL machine type",
595-
inputMachineTypeUrl: "zones/us-central1-c/machineTypes/n2-standard-4",
596-
expectedMachineType: "n2-standard-4",
597-
},
598-
{
599-
desc: "custom partial URL machine type",
600-
inputMachineTypeUrl: "zones/us-central1-c/machineTypes/e2-custom-2-4096",
601-
expectedMachineType: "e2-custom-2-4096",
602-
},
603-
{
604-
desc: "incorrect URL",
605-
inputMachineTypeUrl: "https://www.googleapis.com/compute/v1/projects/psch-gke-dev/zones/us-central1-c",
606-
expectError: true,
607-
},
608-
{
609-
desc: "incorrect partial URL",
610-
inputMachineTypeUrl: "zones/us-central1-c/machineTypes/",
611-
expectError: true,
612-
},
613-
{
614-
desc: "missing zone",
615-
inputMachineTypeUrl: "zones//machineTypes/n2-standard-4",
616-
expectError: true,
617-
},
618-
}
619-
for _, tc := range tests {
620-
t.Run(tc.desc, func(t *testing.T) {
621-
actualMachineFamily, err := ParseMachineType(tc.inputMachineTypeUrl)
622-
if err != nil && !tc.expectError {
623-
t.Errorf("Got error %v parsing machine type %s; expect no error", err, tc.inputMachineTypeUrl)
624-
}
625-
if err == nil && tc.expectError {
626-
t.Errorf("Got no error parsing machine type %s; expect an error", tc.inputMachineTypeUrl)
627-
}
628-
if err == nil && actualMachineFamily != tc.expectedMachineType {
629-
t.Errorf("Got %s parsing machine type; expect %s", actualMachineFamily, tc.expectedMachineType)
630-
}
631-
})
632-
}
633-
}
634-
635587
func TestConvertStringToInt64(t *testing.T) {
636588
tests := []struct {
637589
desc string
@@ -853,3 +805,163 @@ func TestConvertMiStringToInt64(t *testing.T) {
853805
})
854806
}
855807
}
808+
809+
func TestParseMachineType(t *testing.T) {
810+
tests := []struct {
811+
desc string
812+
inputMachineTypeUrl string
813+
expectedMachineType string
814+
expectError bool
815+
}{
816+
{
817+
desc: "full URL machine type",
818+
inputMachineTypeUrl: "https://www.googleapis.com/compute/v1/projects/my-project/zones/us-central1-c/machineTypes/c3-highcpu-4",
819+
expectedMachineType: "c3-highcpu-4",
820+
},
821+
{
822+
desc: "partial URL machine type",
823+
inputMachineTypeUrl: "zones/us-central1-c/machineTypes/n2-standard-4",
824+
expectedMachineType: "n2-standard-4",
825+
},
826+
{
827+
desc: "custom partial URL machine type",
828+
inputMachineTypeUrl: "zones/us-central1-c/machineTypes/e2-custom-2-4096",
829+
expectedMachineType: "e2-custom-2-4096",
830+
},
831+
{
832+
desc: "incorrect URL",
833+
inputMachineTypeUrl: "https://www.googleapis.com/compute/v1/projects/psch-gke-dev/zones/us-central1-c",
834+
expectError: true,
835+
},
836+
{
837+
desc: "incorrect partial URL",
838+
inputMachineTypeUrl: "zones/us-central1-c/machineTypes/",
839+
expectError: true,
840+
},
841+
{
842+
desc: "missing zone",
843+
inputMachineTypeUrl: "zones//machineTypes/n2-standard-4",
844+
expectError: true,
845+
},
846+
}
847+
for _, tc := range tests {
848+
t.Run(tc.desc, func(t *testing.T) {
849+
actualMachineFamily, err := ParseMachineType(tc.inputMachineTypeUrl)
850+
if err != nil && !tc.expectError {
851+
t.Errorf("Got error %v parsing machine type %s; expect no error", err, tc.inputMachineTypeUrl)
852+
}
853+
if err == nil && tc.expectError {
854+
t.Errorf("Got no error parsing machine type %s; expect an error", tc.inputMachineTypeUrl)
855+
}
856+
if err == nil && actualMachineFamily != tc.expectedMachineType {
857+
t.Errorf("Got %s parsing machine type; expect %s", actualMachineFamily, tc.expectedMachineType)
858+
}
859+
})
860+
}
861+
}
862+
863+
func TestCodeForError(t *testing.T) {
864+
internalErrorCode := codes.Internal
865+
userErrorCode := codes.InvalidArgument
866+
testCases := []struct {
867+
name string
868+
inputErr error
869+
expCode *codes.Code
870+
}{
871+
{
872+
name: "Not googleapi.Error",
873+
inputErr: errors.New("I am not a googleapi.Error"),
874+
expCode: &internalErrorCode,
875+
},
876+
{
877+
name: "User error",
878+
inputErr: &googleapi.Error{Code: http.StatusBadRequest, Message: "User error with bad request"},
879+
expCode: &userErrorCode,
880+
},
881+
{
882+
name: "googleapi.Error but not a user error",
883+
inputErr: &googleapi.Error{Code: http.StatusInternalServerError, Message: "Internal error"},
884+
expCode: &internalErrorCode,
885+
},
886+
{
887+
name: "context canceled error",
888+
inputErr: context.Canceled,
889+
expCode: errCodePtr(codes.Canceled),
890+
},
891+
{
892+
name: "context deadline exceeded error",
893+
inputErr: context.DeadlineExceeded,
894+
expCode: errCodePtr(codes.DeadlineExceeded),
895+
},
896+
{
897+
name: "status error with Aborted error code",
898+
inputErr: status.Error(codes.Aborted, "aborted error"),
899+
expCode: errCodePtr(codes.Aborted),
900+
},
901+
{
902+
name: "nil error",
903+
inputErr: nil,
904+
expCode: nil,
905+
},
906+
}
907+
908+
for _, tc := range testCases {
909+
t.Logf("Running test: %v", tc.name)
910+
errCode := CodeForError(tc.inputErr)
911+
if (tc.expCode == nil) != (errCode == nil) {
912+
t.Errorf("test %v failed: got %v, expected %v", tc.name, errCode, tc.expCode)
913+
}
914+
if tc.expCode != nil && *errCode != *tc.expCode {
915+
t.Errorf("test %v failed: got %v, expected %v", tc.name, errCode, tc.expCode)
916+
}
917+
}
918+
}
919+
920+
func TestIsContextError(t *testing.T) {
921+
cases := []struct {
922+
name string
923+
err error
924+
expectedErrCode *codes.Code
925+
}{
926+
{
927+
name: "deadline exceeded error",
928+
err: context.DeadlineExceeded,
929+
expectedErrCode: errCodePtr(codes.DeadlineExceeded),
930+
},
931+
{
932+
name: "contains 'context deadline exceeded'",
933+
err: fmt.Errorf("got error: %w", context.DeadlineExceeded),
934+
expectedErrCode: errCodePtr(codes.DeadlineExceeded),
935+
},
936+
{
937+
name: "context canceled error",
938+
err: context.Canceled,
939+
expectedErrCode: errCodePtr(codes.Canceled),
940+
},
941+
{
942+
name: "contains 'context canceled'",
943+
err: fmt.Errorf("got error: %w", context.Canceled),
944+
expectedErrCode: errCodePtr(codes.Canceled),
945+
},
946+
{
947+
name: "does not contain 'context canceled' or 'context deadline exceeded'",
948+
err: fmt.Errorf("unknown error"),
949+
expectedErrCode: nil,
950+
},
951+
{
952+
name: "nil error",
953+
err: nil,
954+
expectedErrCode: nil,
955+
},
956+
}
957+
958+
for _, test := range cases {
959+
errCode := isContextError(test.err)
960+
if (test.expectedErrCode == nil) != (errCode == nil) {
961+
t.Errorf("test %v failed: got %v, expected %v", test.name, errCode, test.expectedErrCode)
962+
}
963+
if test.expectedErrCode != nil && *errCode != *test.expectedErrCode {
964+
t.Errorf("test %v failed: got %v, expected %v", test.name, errCode, test.expectedErrCode)
965+
}
966+
}
967+
}

0 commit comments

Comments
 (0)