@@ -22,6 +22,7 @@ import (
22
22
"fmt"
23
23
"net/http"
24
24
"regexp"
25
+ "slices"
25
26
"strings"
26
27
"time"
27
28
@@ -73,6 +74,10 @@ const (
73
74
// Full or partial URL of the machine type resource, in the format:
74
75
// zones/zone/machineTypes/machine-type
75
76
machineTypePattern = "zones/[^/]+/machineTypes/([^/]+)$"
77
+
78
+ // Full or partial URL of the zone resource, in the format:
79
+ // projects/{project}/zones/{zone}
80
+ zoneURIPattern = "projects/[^/]+/zones/([^/]+)$"
76
81
)
77
82
78
83
var (
85
90
86
91
storagePoolFieldsRegex = regexp .MustCompile (`^projects/([^/]+)/zones/([^/]+)/storagePools/([^/]+)$` )
87
92
93
+ zoneURIRegex = regexp .MustCompile (zoneURIPattern )
94
+
88
95
// userErrorCodeMap tells how API error types are translated to error codes.
89
96
userErrorCodeMap = map [int ]codes.Code {
90
97
http .StatusForbidden : codes .PermissionDenied ,
97
104
regexParent = regexp .MustCompile (`(^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$)` )
98
105
regexKey = regexp .MustCompile (`^[a-zA-Z0-9]([0-9A-Za-z_.-]{0,61}[a-zA-Z0-9])?$` )
99
106
regexValue = regexp .MustCompile (`^[a-zA-Z0-9]([0-9A-Za-z_.@%=+:,*#&()\[\]{}\-\s]{0,61}[a-zA-Z0-9])?$` )
107
+
108
+ csiRetryableErrorCodes = []codes.Code {codes .Canceled , codes .DeadlineExceeded , codes .Unavailable , codes .Aborted , codes .ResourceExhausted }
100
109
)
101
110
102
111
func BytesToGbRoundDown (bytes int64 ) int64 {
@@ -545,9 +554,37 @@ func isGoogleAPIError(err error) (codes.Code, error) {
545
554
return codes .Unknown , fmt .Errorf ("googleapi.Error %w does not map to any known errors" , err )
546
555
}
547
556
548
- func LoggedError (msg string , err error ) error {
557
+ func loggedErrorForCode (msg string , code codes. Code , err error ) error {
549
558
klog .Errorf (msg + "%v" , err .Error ())
550
- return status .Errorf (CodeForError (err ), msg + "%v" , err .Error ())
559
+ return status .Errorf (code , msg + "%v" , err .Error ())
560
+ }
561
+
562
+ func LoggedError (msg string , err error ) error {
563
+ return loggedErrorForCode (msg , CodeForError (err ), err )
564
+ }
565
+
566
+ // NewCombinedError tries to return an appropriate wrapped error that captures
567
+ // useful information as an error code
568
+ // If there are multiple errors, it extracts the first "retryable" error
569
+ // as interpreted by the CSI sidecar.
570
+ func NewCombinedError (msg string , errs []error ) error {
571
+ // If there is only one error, return it as the single error code
572
+ if len (errs ) == 1 {
573
+ LoggedError (msg , errs [0 ])
574
+ }
575
+
576
+ for _ , err := range errs {
577
+ code := CodeForError (err )
578
+ if slices .Contains (csiRetryableErrorCodes , code ) {
579
+ // Return this as a TemporaryError to lock-in the retryable code
580
+ // This will invoke the "existing" error code check in CodeForError
581
+ return NewTemporaryError (code , fmt .Errorf ("%s: %w" , msg , err ))
582
+ }
583
+ }
584
+
585
+ // None of these error codes were retryable. Just return a combined error
586
+ // The first matching error (based on our CodeForError) logic will be returned.
587
+ return LoggedError (msg , errors .Join (errs ... ))
551
588
}
552
589
553
590
func isValidDiskEncryptionKmsKey (DiskEncryptionKmsKey string ) bool {
@@ -556,6 +593,14 @@ func isValidDiskEncryptionKmsKey(DiskEncryptionKmsKey string) bool {
556
593
return kmsKeyPattern .MatchString (DiskEncryptionKmsKey )
557
594
}
558
595
596
+ func ParseZoneFromURI (zoneURI string ) (string , error ) {
597
+ zoneMatch := zoneURIRegex .FindStringSubmatch (zoneURI )
598
+ if zoneMatch == nil {
599
+ return "" , fmt .Errorf ("failed to parse zone URI. Expected projects/{project}/zones/{zone}. Got: %s" , zoneURI )
600
+ }
601
+ return zoneMatch [1 ], nil
602
+ }
603
+
559
604
// ParseStoragePools returns an error if none of the given storagePools
560
605
// (delimited by a comma) are in the format
561
606
// projects/project/zones/zone/storagePools/storagePool.
0 commit comments