Skip to content

Commit d3eb2b4

Browse files
GCNV SAN cleanup follow-up: review nits, NAS/SAN parity, and gcp_common
1 parent 03dd441 commit d3eb2b4

File tree

6 files changed

+483
-285
lines changed

6 files changed

+483
-285
lines changed

storage_drivers/gcp/gcp_common.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2026 NetApp, Inc. All Rights Reserved.
2+
3+
package gcp
4+
5+
import (
6+
"fmt"
7+
"regexp"
8+
"strings"
9+
"time"
10+
"unicode"
11+
"unicode/utf8"
12+
13+
tridentconfig "github.com/netapp/trident/config"
14+
"github.com/netapp/trident/pkg/convert"
15+
"github.com/netapp/trident/storage_drivers/gcp/api"
16+
)
17+
18+
var (
19+
// gcpLabelRegex matches characters that are not allowed in GCP label keys or values.
20+
// Allowed: lowercase letters, digits, hyphens, underscores, and Unicode letters (\p{L}).
21+
gcpLabelRegex = regexp.MustCompile(`[^-_a-z0-9\p{L}]`)
22+
)
23+
24+
// DefaultCreateTimeout returns the volume create/delete timeout for the given driver context.
25+
// Docker gets more time since it has no retry mechanism; CSI uses the API default.
26+
func DefaultCreateTimeout(driverContext tridentconfig.DriverContext) time.Duration {
27+
switch driverContext {
28+
case tridentconfig.ContextDocker:
29+
return tridentconfig.DockerCreateTimeout
30+
default:
31+
return api.VolumeCreateTimeout
32+
}
33+
}
34+
35+
// FixGCPLabelKey accepts a label key and modifies it to satisfy GCP label key rules, or returns
36+
// false if not possible.
37+
func FixGCPLabelKey(s string) (string, bool) {
38+
if s == "" {
39+
return "", false
40+
}
41+
s = strings.ToLower(s)
42+
s = gcpLabelRegex.ReplaceAllStringFunc(s, func(m string) string {
43+
return strings.Repeat("_", len(m))
44+
})
45+
first, _ := utf8.DecodeRuneInString(s)
46+
if first == utf8.RuneError || !unicode.IsLower(first) {
47+
return "", false
48+
}
49+
s = convert.TruncateString(s, api.MaxLabelLength)
50+
return s, true
51+
}
52+
53+
// FixGCPLabelValue accepts a label value and modifies it to satisfy GCP label value rules.
54+
func FixGCPLabelValue(s string) string {
55+
if s == "" {
56+
return ""
57+
}
58+
s = strings.ToLower(s)
59+
s = gcpLabelRegex.ReplaceAllStringFunc(s, func(m string) string {
60+
return strings.Repeat("_", len(m))
61+
})
62+
s = convert.TruncateString(s, api.MaxLabelLength)
63+
return s
64+
}
65+
66+
// ErrRefreshGCNVResourceCache returns a standard error when refreshing the GCNV resource cache fails.
67+
func ErrRefreshGCNVResourceCache(err error) error {
68+
return fmt.Errorf("could not update GCNV resource cache; %w", err)
69+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2026 NetApp, Inc. All Rights Reserved.
2+
3+
package gcp
4+
5+
import (
6+
"errors"
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/assert"
11+
12+
tridentconfig "github.com/netapp/trident/config"
13+
"github.com/netapp/trident/storage_drivers/gcp/api"
14+
)
15+
16+
func TestGCPCommon_DefaultCreateTimeout(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
ctx tridentconfig.DriverContext
20+
expected time.Duration
21+
}{
22+
{"Docker", tridentconfig.ContextDocker, tridentconfig.DockerCreateTimeout},
23+
{"CSI", tridentconfig.ContextCSI, api.VolumeCreateTimeout},
24+
{"Empty", tridentconfig.DriverContext(""), api.VolumeCreateTimeout},
25+
{"Unknown", tridentconfig.DriverContext("k8s"), api.VolumeCreateTimeout},
26+
}
27+
for _, tt := range tests {
28+
t.Run(tt.name, func(t *testing.T) {
29+
got := DefaultCreateTimeout(tt.ctx)
30+
assert.Equal(t, tt.expected, got)
31+
})
32+
}
33+
}
34+
35+
func TestGCPCommon_FixGCPLabelKey(t *testing.T) {
36+
tests := []struct {
37+
name string
38+
in string
39+
wantOut string
40+
wantOK bool
41+
}{
42+
{"Empty", "", "", false},
43+
{"Valid lowercase", "valid_key", "valid_key", true},
44+
{"Uppercase normalized", "ValidKey", "validkey", true},
45+
{"Starts with digit", "1key", "", false},
46+
{"Starts with hyphen", "-key", "", false},
47+
{"Disallowed chars replaced", "key.with.dots", "key_with_dots", true},
48+
{"Spaces to underscores", "key with spaces", "key_with_spaces", true},
49+
{"Unicode letter allowed", "këy", "këy", true},
50+
{"Multibyte lowercase first rune", "émoji", "émoji", true},
51+
{"Too long truncated", string(make([]byte, 70)), "", false}, // all zeros, first rune not lower
52+
}
53+
for _, tt := range tests {
54+
t.Run(tt.name, func(t *testing.T) {
55+
gotOut, gotOK := FixGCPLabelKey(tt.in)
56+
assert.Equal(t, tt.wantOK, gotOK, "ok")
57+
if tt.wantOK {
58+
assert.Equal(t, tt.wantOut, gotOut)
59+
assert.True(t, len(gotOut) <= api.MaxLabelLength, "length <= MaxLabelLength")
60+
}
61+
})
62+
}
63+
// Key that is long but valid (starts with letter) gets truncated to 63
64+
longKey := "a" + string(make([]byte, api.MaxLabelLength+10))
65+
out, ok := FixGCPLabelKey(longKey)
66+
assert.True(t, ok)
67+
assert.Equal(t, api.MaxLabelLength, len(out))
68+
}
69+
70+
func TestGCPCommon_FixGCPLabelValue(t *testing.T) {
71+
tests := []struct {
72+
name string
73+
in string
74+
wantOut string
75+
}{
76+
{"Empty", "", ""},
77+
{"Valid", "value-123", "value-123"},
78+
{"Uppercase normalized", "Value", "value"},
79+
{"Dots replaced", "v.a.l", "v_a_l"},
80+
{"Spaces replaced", "a b c", "a_b_c"},
81+
}
82+
for _, tt := range tests {
83+
t.Run(tt.name, func(t *testing.T) {
84+
got := FixGCPLabelValue(tt.in)
85+
assert.Equal(t, tt.wantOut, got)
86+
if len(got) > 0 {
87+
assert.True(t, len(got) <= api.MaxLabelLength, "length <= MaxLabelLength")
88+
}
89+
})
90+
}
91+
// Long value truncated
92+
longVal := "v" + string(make([]byte, api.MaxLabelLength+5))
93+
got := FixGCPLabelValue(longVal)
94+
assert.Equal(t, api.MaxLabelLength, len(got))
95+
}
96+
97+
func TestGCPCommon_ErrRefreshGCNVResourceCache(t *testing.T) {
98+
err := errors.New("connection refused")
99+
wrapped := ErrRefreshGCNVResourceCache(err)
100+
assert.Error(t, wrapped)
101+
assert.Contains(t, wrapped.Error(), "could not update GCNV resource cache")
102+
assert.Contains(t, wrapped.Error(), "connection refused")
103+
}

0 commit comments

Comments
 (0)