Skip to content

Commit cf106c9

Browse files
authored
Merge pull request #1223 from pwschuurman/automated-cherry-pick-of-#1212-upstream-release-1.8
Automated cherry pick of #1212: Rewrite PD CSI error message when encountering diskType
2 parents 46e81ea + bfb5139 commit cf106c9

File tree

4 files changed

+117
-1
lines changed

4 files changed

+117
-1
lines changed

pkg/common/utils.go

+20
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,19 @@ const (
5757
multiRegionalLocationFmt = "^[a-z]+$"
5858
// Example: us-east1
5959
regionalLocationFmt = "^[a-z]+-[a-z]+[0-9]$"
60+
61+
// Full or partial URL of the machine type resource, in the format:
62+
// zones/zone/machineTypes/machine-type
63+
machineTypePattern = "zones/[^/]+/machineTypes/([^/]+)$"
6064
)
6165

6266
var (
6367
multiRegionalPattern = regexp.MustCompile(multiRegionalLocationFmt)
6468
regionalPattern = regexp.MustCompile(regionalLocationFmt)
69+
70+
// Full or partial URL of the machine type resource, in the format:
71+
// zones/zone/machineTypes/machine-type
72+
machineTypeRegex = regexp.MustCompile(machineTypePattern)
6573
)
6674

6775
func BytesToGbRoundDown(bytes int64) int64 {
@@ -248,3 +256,15 @@ func ValidateSnapshotType(snapshotType string) error {
248256
return fmt.Errorf("invalid snapshot type %s", snapshotType)
249257
}
250258
}
259+
260+
// ParseMachineType returns an extracted machineType from a URL, or empty if not found.
261+
// machineTypeUrl: Full or partial URL of the machine type resource, in the format:
262+
//
263+
// zones/zone/machineTypes/machine-type
264+
func ParseMachineType(machineTypeUrl string) (string, error) {
265+
machineType := machineTypeRegex.FindStringSubmatch(machineTypeUrl)
266+
if machineType == nil {
267+
return "", fmt.Errorf("failed to parse machineTypeUrl. Expected suffix: zones/{zone}/machineTypes/{machine-type}. Got: %s", machineTypeUrl)
268+
}
269+
return machineType[1], nil
270+
}

pkg/common/utils_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,57 @@ func TestSnapshotStorageLocations(t *testing.T) {
577577
})
578578
}
579579
}
580+
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+
}

pkg/gce-cloud-provider/compute/gce-compute.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"encoding/json"
2020
"fmt"
21+
"regexp"
2122
"strings"
2223
"time"
2324

@@ -38,8 +39,12 @@ const (
3839
waitForImageCreationTimeOut = 5 * time.Minute
3940
diskKind = "compute#disk"
4041
cryptoKeyVerDelimiter = "/cryptoKeyVersions"
42+
// Example message: "[pd-standard] features are not compatible for creating instance"
43+
pdDiskTypeUnsupportedPattern = `\[([a-z-]+)\] features are not compatible for creating instance`
4144
)
4245

46+
var pdDiskTypeUnsupportedRegex = regexp.MustCompile(pdDiskTypeUnsupportedPattern)
47+
4348
type GCEAPIVersion string
4449

4550
const (
@@ -67,6 +72,15 @@ var WaitForOpBackoff = wait.Backoff{
6772
Steps: 100,
6873
Cap: 0}
6974

75+
// Custom error type to propagate error messages up to clients.
76+
type UnsupportedDiskError struct {
77+
DiskType string
78+
}
79+
80+
func (udErr *UnsupportedDiskError) Error() string {
81+
return ""
82+
}
83+
7084
type GCECompute interface {
7185
// Metadata information
7286
GetDefaultProject() string
@@ -830,12 +844,23 @@ func (cloud *CloudProvider) WaitForAttach(ctx context.Context, project string, v
830844
})
831845
}
832846

847+
func wrapOpErr(name string, opErr *computev1.OperationErrorErrors) error {
848+
if opErr.Code == "UNSUPPORTED_OPERATION" {
849+
if diskType := pdDiskTypeUnsupportedRegex.FindStringSubmatch(opErr.Message); diskType != nil {
850+
return &UnsupportedDiskError{
851+
DiskType: diskType[1],
852+
}
853+
}
854+
}
855+
return fmt.Errorf("operation %v failed (%v): %v", name, opErr.Code, opErr.Message)
856+
}
857+
833858
func opIsDone(op *computev1.Operation) (bool, error) {
834859
if op == nil || op.Status != operationStatusDone {
835860
return false, nil
836861
}
837862
if op.Error != nil && len(op.Error.Errors) > 0 && op.Error.Errors[0] != nil {
838-
return true, fmt.Errorf("operation %v failed (%v): %v", op.Name, op.Error.Errors[0].Code, op.Error.Errors[0].Message)
863+
return true, wrapOpErr(op.Name, op.Error.Errors[0])
839864
}
840865
return true, nil
841866
}

pkg/gce-pd-csi-driver/controller.go

+17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package gceGCEDriver
1616

1717
import (
1818
"context"
19+
"errors"
1920
"fmt"
2021
"math/rand"
2122
"sort"
@@ -449,6 +450,15 @@ func (gceCS *GCEControllerServer) validateControllerPublishVolumeRequest(ctx con
449450
return project, volKey, nil
450451
}
451452

453+
func parseMachineType(machineTypeUrl string) string {
454+
machineType, parseErr := common.ParseMachineType(machineTypeUrl)
455+
if parseErr != nil {
456+
// Parse errors represent an unexpected API change with instance.MachineType; log a warning.
457+
klog.Warningf("ParseMachineType(%v): %v", machineTypeUrl, parseErr)
458+
}
459+
return machineType
460+
}
461+
452462
func (gceCS *GCEControllerServer) executeControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
453463
project, volKey, err := gceCS.validateControllerPublishVolumeRequest(ctx, req)
454464

@@ -525,6 +535,13 @@ func (gceCS *GCEControllerServer) executeControllerPublishVolume(ctx context.Con
525535
}
526536
err = gceCS.CloudProvider.AttachDisk(ctx, project, volKey, readWrite, attachableDiskTypePersistent, instanceZone, instanceName)
527537
if err != nil {
538+
var udErr *gce.UnsupportedDiskError
539+
if errors.As(err, &udErr) {
540+
// If we encountered an UnsupportedDiskError, rewrite the error message to be more user friendly.
541+
// The error message from GCE is phrased around disk create on VM creation, not runtime attach.
542+
machineType := parseMachineType(instance.MachineType)
543+
return nil, status.Errorf(codes.InvalidArgument, "'%s' is not a compatible disk type with the machine type %s, please review the GCP online documentation for available persistent disk options", udErr.DiskType, machineType)
544+
}
528545
return nil, status.Errorf(codes.Internal, "unknown Attach error: %v", err.Error())
529546
}
530547

0 commit comments

Comments
 (0)