Skip to content

Commit f00974a

Browse files
committed
Add gce disk labels support via create volume parameters
1 parent f476c98 commit f00974a

File tree

11 files changed

+179
-3
lines changed

11 files changed

+179
-3
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ See Github [Issues](https://github.com/kubernetes-sigs/gcp-compute-persistent-di
5555
|------------------|---------------------------|---------------|----------------------------------------------------------------------------------------------------|
5656
| type | `pd-ssd` OR `pd-standard` | `pd-standard` | Type allows you to choose between standard Persistent Disks or Solid State Drive Persistent Disks |
5757
| replication-type | `none` OR `regional-pd` | `none` | Replication type allows you to choose between Zonal Persistent Disks or Regional Persistent Disks |
58+
| labels | `key1=value1,key2=value2` | | Labels allow you to assign custom GCE Disk labels |
5859

5960
### Topology
6061

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: storage.k8s.io/v1
2+
kind: StorageClass
3+
metadata:
4+
name: csi-gce-pd
5+
provisioner: pd.csi.storage.gke.io
6+
parameters:
7+
labels: key1=value1,key2=value2
8+
volumeBindingMode: WaitForFirstConsumer

examples/kubernetes/demo-regional-restricted-sc.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ provisioner: pd.csi.storage.gke.io
66
parameters:
77
type: pd-standard
88
replication-type: regional-pd
9+
labels: key1=value1,key2=value2
910
volumeBindingMode: WaitForFirstConsumer
1011
allowedTopologies:
1112
- matchLabelExpressions:

examples/kubernetes/demo-regional-sc.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ provisioner: pd.csi.storage.gke.io
66
parameters:
77
type: pd-standard
88
replication-type: regional-pd
9+
labels: key1=value1,key2=value2
910
volumeBindingMode: WaitForFirstConsumer

pkg/common/parameters.go

+12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
ParameterKeyType = "type"
2626
ParameterKeyReplicationType = "replication-type"
2727
ParameterKeyDiskEncryptionKmsKey = "disk-encryption-kms-key"
28+
ParameterKeyLabels = "labels"
2829

2930
replicationTypeNone = "none"
3031
)
@@ -40,6 +41,9 @@ type DiskParameters struct {
4041
// Values: {string}
4142
// Default: ""
4243
DiskEncryptionKMSKey string
44+
// Values: map with arbitrary keys and values
45+
// Default: empty map
46+
Labels map[string]string
4347
}
4448

4549
// ExtractAndDefaultParameters will take the relevant parameters from a map and
@@ -49,6 +53,7 @@ func ExtractAndDefaultParameters(parameters map[string]string) (DiskParameters,
4953
DiskType: "pd-standard", // Default
5054
ReplicationType: replicationTypeNone, // Default
5155
DiskEncryptionKMSKey: "", // Default
56+
Labels: map[string]string{}, // Default
5257
}
5358
for k, v := range parameters {
5459
if k == "csiProvisionerSecretName" || k == "csiProvisionerSecretNamespace" {
@@ -67,6 +72,13 @@ func ExtractAndDefaultParameters(parameters map[string]string) (DiskParameters,
6772
case ParameterKeyDiskEncryptionKmsKey:
6873
// Resource names (e.g. "keyRings", "cryptoKeys", etc.) are case sensitive, so do not change case
6974
p.DiskEncryptionKMSKey = v
75+
case ParameterKeyLabels:
76+
labels, err := ConvertLabelsStringToMap(v)
77+
if err != nil {
78+
return p, fmt.Errorf("parameters contain invalid labels parameter: %w", err)
79+
}
80+
81+
p.Labels = labels
7082
default:
7183
return p, fmt.Errorf("parameters contains invalid option %q", k)
7284
}

pkg/common/parameters_test.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,35 @@ func TestExtractAndDefaultParameters(t *testing.T) {
3535
DiskType: "pd-standard",
3636
ReplicationType: "none",
3737
DiskEncryptionKMSKey: "",
38+
Labels: map[string]string{},
3839
},
3940
},
4041
{
4142
name: "specified empties",
42-
parameters: map[string]string{ParameterKeyType: "", ParameterKeyReplicationType: "", ParameterKeyDiskEncryptionKmsKey: ""},
43+
parameters: map[string]string{ParameterKeyType: "", ParameterKeyReplicationType: "", ParameterKeyDiskEncryptionKmsKey: "", ParameterKeyLabels: ""},
4344
expectParams: DiskParameters{
4445
DiskType: "pd-standard",
4546
ReplicationType: "none",
4647
DiskEncryptionKMSKey: "",
48+
Labels: map[string]string{},
4749
},
4850
},
4951
{
5052
name: "random keys",
51-
parameters: map[string]string{ParameterKeyType: "", "foo": "", ParameterKeyDiskEncryptionKmsKey: ""},
53+
parameters: map[string]string{ParameterKeyType: "", "foo": "", ParameterKeyDiskEncryptionKmsKey: "", ParameterKeyLabels: ""},
5254
expectErr: true,
5355
},
5456
{
5557
name: "real values",
56-
parameters: map[string]string{ParameterKeyType: "pd-ssd", ParameterKeyReplicationType: "regional-pd", ParameterKeyDiskEncryptionKmsKey: "foo/key"},
58+
parameters: map[string]string{ParameterKeyType: "pd-ssd", ParameterKeyReplicationType: "regional-pd", ParameterKeyDiskEncryptionKmsKey: "foo/key", ParameterKeyLabels: "key1=value1,key2=value2"},
5759
expectParams: DiskParameters{
5860
DiskType: "pd-ssd",
5961
ReplicationType: "regional-pd",
6062
DiskEncryptionKMSKey: "foo/key",
63+
Labels: map[string]string{
64+
"key1": "value1",
65+
"key2": "value2",
66+
},
6167
},
6268
},
6369
{
@@ -67,6 +73,7 @@ func TestExtractAndDefaultParameters(t *testing.T) {
6773
DiskType: "pd-standard",
6874
ReplicationType: "none",
6975
DiskEncryptionKMSKey: "foo/key",
76+
Labels: map[string]string{},
7077
},
7178
},
7279
}

pkg/common/utils.go

+33
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,36 @@ func CreateNodeID(project, zone, name string) string {
148148
func CreateZonalVolumeID(project, zone, name string) string {
149149
return fmt.Sprintf(volIDZonalFmt, project, zone, name)
150150
}
151+
152+
// ConvertLabelsStringToMap converts the labels from string to map
153+
// example: "key1=value1,key2=value2" gets converted into {"key1": "value1", "key2": "value2"}
154+
func ConvertLabelsStringToMap(labels string) (map[string]string, error) {
155+
const labelsDelimiter = ","
156+
const labelsKeyValueDelimiter = "="
157+
158+
m := make(map[string]string)
159+
if labels == "" {
160+
return m, nil
161+
}
162+
163+
errIncorrectFormat := fmt.Errorf("labels %q are invalid, correct format: 'key1=value1,key2=value2'", labels)
164+
165+
keyValueStrings := strings.Split(labels, labelsDelimiter)
166+
for _, keyValue := range keyValueStrings {
167+
keyValue := strings.Split(keyValue, labelsKeyValueDelimiter)
168+
169+
if len(keyValue) != 2 {
170+
return nil, errIncorrectFormat
171+
}
172+
173+
key := strings.TrimSpace(keyValue[0])
174+
if key == "" {
175+
return nil, errIncorrectFormat
176+
}
177+
178+
value := strings.TrimSpace(keyValue[1])
179+
m[key] = value
180+
}
181+
182+
return m, nil
183+
}

pkg/common/utils_test.go

+84
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,87 @@ func TestKeyToVolumeID(t *testing.T) {
295295
}
296296

297297
}
298+
299+
func TestConvertLabelsStringToMap(t *testing.T) {
300+
testCases := []struct {
301+
name string
302+
labels string
303+
expectedOutput map[string]string
304+
expectedError bool
305+
}{
306+
{
307+
name: "should return empty map when labels string is empty",
308+
labels: "",
309+
expectedOutput: map[string]string{},
310+
expectedError: false,
311+
},
312+
{
313+
name: "single label string",
314+
labels: "key=value",
315+
expectedOutput: map[string]string{
316+
"key": "value",
317+
},
318+
expectedError: false,
319+
},
320+
{
321+
name: "multiple label string",
322+
labels: "key1=value1,key2=value2",
323+
expectedOutput: map[string]string{
324+
"key1": "value1",
325+
"key2": "value2",
326+
},
327+
expectedError: false,
328+
},
329+
{
330+
name: "multiple labels string with whitespaces gets trimmed",
331+
labels: "key1=value1, key2=value2",
332+
expectedOutput: map[string]string{
333+
"key1": "value1",
334+
"key2": "value2",
335+
},
336+
expectedError: false,
337+
},
338+
{
339+
name: "malformed labels string (no keys and values)",
340+
labels: ",,",
341+
expectedOutput: nil,
342+
expectedError: true,
343+
},
344+
{
345+
name: "malformed labels string (incorrect format)",
346+
labels: "foo,bar",
347+
expectedOutput: nil,
348+
expectedError: true,
349+
},
350+
{
351+
name: "malformed labels string (missing key)",
352+
labels: "key1=value1,=bar",
353+
expectedOutput: nil,
354+
expectedError: true,
355+
},
356+
{
357+
name: "malformed labels string (missing key and value)",
358+
labels: "key1=value1,=bar,=",
359+
expectedOutput: nil,
360+
expectedError: true,
361+
},
362+
}
363+
364+
for _, tc := range testCases {
365+
t.Logf("test case: %s", tc.name)
366+
output, err := ConvertLabelsStringToMap(tc.labels)
367+
if err == nil && tc.expectedError {
368+
t.Errorf("Expected error but got none")
369+
}
370+
if err != nil {
371+
if !tc.expectedError {
372+
t.Errorf("Did not expect error but got: %v", err)
373+
}
374+
continue
375+
}
376+
377+
if !reflect.DeepEqual(output, tc.expectedOutput) {
378+
t.Errorf("Got labels %v, but expected %v", output, tc.expectedOutput)
379+
}
380+
}
381+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ func (cloud *FakeCloudProvider) InsertDisk(ctx context.Context, volKey *meta.Key
256256
SelfLink: fmt.Sprintf("projects/%s/zones/%s/disks/%s", cloud.project, volKey.Zone, volKey.Name),
257257
SourceSnapshotId: snapshotID,
258258
Status: cloud.mockDiskStatus,
259+
Labels: params.Labels,
259260
}
260261
if params.DiskEncryptionKMSKey != "" {
261262
diskToCreateGA.DiskEncryptionKey = &computev1.CustomerEncryptionKey{
@@ -272,6 +273,7 @@ func (cloud *FakeCloudProvider) InsertDisk(ctx context.Context, volKey *meta.Key
272273
SelfLink: fmt.Sprintf("projects/%s/regions/%s/disks/%s", cloud.project, volKey.Region, volKey.Name),
273274
SourceSnapshotId: snapshotID,
274275
Status: cloud.mockDiskStatus,
276+
Labels: params.Labels,
275277
}
276278
if params.DiskEncryptionKMSKey != "" {
277279
diskToCreateV1.DiskEncryptionKey = &computev1.CustomerEncryptionKey{

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

+2
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ func (cloud *CloudProvider) insertRegionalDisk(ctx context.Context, volKey *meta
284284
SizeGb: common.BytesToGb(capBytes),
285285
Description: "Regional disk created by GCE-PD CSI Driver",
286286
Type: cloud.GetDiskTypeURI(volKey, params.DiskType),
287+
Labels: params.Labels,
287288
}
288289
if snapshotID != "" {
289290
diskToCreate.SourceSnapshot = snapshotID
@@ -343,6 +344,7 @@ func (cloud *CloudProvider) insertZonalDisk(ctx context.Context, volKey *meta.Ke
343344
SizeGb: common.BytesToGb(capBytes),
344345
Description: "Disk created by GCE-PD CSI Driver",
345346
Type: cloud.GetDiskTypeURI(volKey, params.DiskType),
347+
Labels: params.Labels,
346348
}
347349

348350
if snapshotID != "" {

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

+25
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,31 @@ func TestCreateVolumeArguments(t *testing.T) {
668668
AccessibleTopology: stdTopology,
669669
},
670670
},
671+
{
672+
name: "success with labels parameter",
673+
req: &csi.CreateVolumeRequest{
674+
Name: name,
675+
CapacityRange: stdCapRange,
676+
VolumeCapabilities: stdVolCaps,
677+
Parameters: map[string]string{"labels": "key1=value1,key2=value2"},
678+
},
679+
expVol: &csi.Volume{
680+
CapacityBytes: common.GbToBytes(20),
681+
VolumeId: testVolumeID,
682+
VolumeContext: nil,
683+
AccessibleTopology: stdTopology,
684+
},
685+
},
686+
{
687+
name: "success with malformed labels parameter",
688+
req: &csi.CreateVolumeRequest{
689+
Name: name,
690+
CapacityRange: stdCapRange,
691+
VolumeCapabilities: stdVolCaps,
692+
Parameters: map[string]string{"labels": "key1=value1,;;"},
693+
},
694+
expErrCode: codes.InvalidArgument,
695+
},
671696
}
672697

673698
// Run test cases

0 commit comments

Comments
 (0)