Skip to content

Commit e80c438

Browse files
committed
Add gce disk labels support via create volume parameters
Add labels validation to match gce requirements add e2e test case for labels
1 parent ac52f17 commit e80c438

File tree

16 files changed

+407
-25
lines changed

16 files changed

+407
-25
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ See Github [Issues](https://github.com/kubernetes-sigs/gcp-compute-persistent-di
6868
| type | `pd-ssd` OR `pd-standard` | `pd-standard` | Type allows you to choose between standard Persistent Disks or Solid State Drive Persistent Disks |
6969
| replication-type | `none` OR `regional-pd` | `none` | Replication type allows you to choose between Zonal Persistent Disks or Regional Persistent Disks |
7070
| disk-encryption-kms-key | Fully qualified resource identifier for the key to use to encrypt new disks. | Empty string. | Encrypt disk using Customer Managed Encryption Key (CMEK). See [GKE Docs](https://cloud.google.com/kubernetes-engine/docs/how-to/using-cmek#create_a_cmek_protected_attached_disk) for details. |
71+
| labels | `key1=value1,key2=value2` | | Labels allow you to assign custom [GCE Disk labels](https://cloud.google.com/compute/docs/labeling-resources). |
7172

7273
### Topology
7374

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

+10-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424

2525
"k8s.io/klog"
2626

27-
cliflag "k8s.io/component-base/cli/flag"
27+
"sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/common"
2828
gce "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/compute"
2929
metadataservice "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-cloud-provider/metadata"
3030
driver "sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/pkg/gce-pd-csi-driver"
@@ -36,7 +36,7 @@ var (
3636
endpoint = flag.String("endpoint", "unix:/tmp/csi.sock", "CSI endpoint")
3737
runControllerService = flag.Bool("run-controller-service", true, "If set to false then the CSI driver does not activate its controller service (default: true)")
3838
runNodeService = flag.Bool("run-node-service", true, "If set to false then the CSI driver does not activate its node service (default: true)")
39-
extraVolumeLabels map[string]string
39+
extraVolumeLabelsStr = flag.String("extra-labels", "", "Extra labels to attach to each PD created. It is a comma separated list of key value pairs like '<key1>=<value1>,<key2>=<value2>'. See https://cloud.google.com/compute/docs/labeling-resources for details")
4040
version string
4141
)
4242

@@ -55,7 +55,6 @@ func init() {
5555
}
5656

5757
func main() {
58-
flag.Var(cliflag.NewMapStringString(&extraVolumeLabels), "extra-labels", "Extra labels to attach to each PD created. It is a comma separated list of key value pairs like '<key1>=<value1>,<key2>=<value2>'")
5958
flag.Parse()
6059
rand.Seed(time.Now().UnixNano())
6160
handle()
@@ -70,6 +69,14 @@ func handle() {
7069
}
7170
klog.V(2).Infof("Driver vendor version %v", version)
7271

72+
if len(*extraVolumeLabelsStr) > 0 && !*runControllerService {
73+
klog.Fatalf("Extra volume labels provided but not running controller")
74+
}
75+
extraVolumeLabels, err := common.ConvertLabelsStringToMap(*extraVolumeLabelsStr)
76+
if err != nil {
77+
klog.Fatalf("Bad extra volume labels: %v", err)
78+
}
79+
7380
gceDriver := driver.GetGCEDriver()
7481

7582
//Initialize GCE Driver
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# for gce-pd-driver
2+
- op: add
3+
path: /spec/template/spec/containers/4/args/2
4+
value: "--extra-labels=csi=gce-pd-driver # Used for testing, not to be merged to stable"

deploy/kubernetes/overlays/prow-gke-release-staging-rc-master/kustomization.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,13 @@ namespace:
44
gce-pd-csi-driver
55
resources:
66
- ../stable-master
7+
patchesJson6902:
8+
# disk_labels.yaml is used for testing and should not be merged to stable.
9+
- target:
10+
group: apps
11+
version: v1
12+
kind: Deployment
13+
name: csi-gce-pd-controller
14+
path: disk_labels.yaml
715
transformers:
816
- ../../images/prow-gke-release-staging-rc-master
+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

pkg/common/parameters.go

+18-5
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

@@ -33,7 +34,7 @@ const (
3334
ParameterKeyPVCNamespace = "csi.storage.k8s.io/pvc/namespace"
3435
ParameterKeyPVName = "csi.storage.k8s.io/pv/name"
3536

36-
// Keys for tags to attach to the provisioned disk.
37+
// Keys for tags to put in the provisioned disk description.
3738
tagKeyCreatedForClaimNamespace = "kubernetes.io/created-for/pvc/namespace"
3839
tagKeyCreatedForClaimName = "kubernetes.io/created-for/pvc/name"
3940
tagKeyCreatedForVolumeName = "kubernetes.io/created-for/pv/name"
@@ -60,7 +61,9 @@ type DiskParameters struct {
6061
}
6162

6263
// ExtractAndDefaultParameters will take the relevant parameters from a map and
63-
// put them into a well defined struct making sure to default unspecified fields
64+
// put them into a well defined struct making sure to default unspecified fields.
65+
// extraVolumeLabels are added as labels; if there are also labels specified in
66+
// parameters, any matching extraVolumeLabels will be overridden.
6467
func ExtractAndDefaultParameters(parameters map[string]string, driverName string, extraVolumeLabels map[string]string) (DiskParameters, error) {
6568
p := DiskParameters{
6669
DiskType: "pd-standard", // Default
@@ -70,6 +73,10 @@ func ExtractAndDefaultParameters(parameters map[string]string, driverName string
7073
Labels: make(map[string]string), // Default
7174
}
7275

76+
for k, v := range extraVolumeLabels {
77+
p.Labels[k] = v
78+
}
79+
7380
for k, v := range parameters {
7481
if k == "csiProvisionerSecretName" || k == "csiProvisionerSecretNamespace" {
7582
// These are hardcoded secrets keys required to function but not needed by GCE PD
@@ -93,15 +100,21 @@ func ExtractAndDefaultParameters(parameters map[string]string, driverName string
93100
p.Tags[tagKeyCreatedForClaimNamespace] = v
94101
case ParameterKeyPVName:
95102
p.Tags[tagKeyCreatedForVolumeName] = v
103+
case ParameterKeyLabels:
104+
paramLabels, err := ConvertLabelsStringToMap(v)
105+
if err != nil {
106+
return p, fmt.Errorf("parameters contain invalid labels parameter: %w", err)
107+
}
108+
// Override any existing labels with those from this parameter.
109+
for labelKey, labelValue := range paramLabels {
110+
p.Labels[labelKey] = labelValue
111+
}
96112
default:
97113
return p, fmt.Errorf("parameters contains invalid option %q", k)
98114
}
99115
}
100116
if len(p.Tags) > 0 {
101117
p.Tags[tagKeyCreatedBy] = driverName
102118
}
103-
for k, v := range extraVolumeLabels {
104-
p.Labels[k] = v
105-
}
106119
return p, nil
107120
}

pkg/common/parameters_test.go

+42-15
Original file line numberDiff line numberDiff line change
@@ -37,38 +37,41 @@ func TestExtractAndDefaultParameters(t *testing.T) {
3737
DiskType: "pd-standard",
3838
ReplicationType: "none",
3939
DiskEncryptionKMSKey: "",
40-
Tags: make(map[string]string),
41-
Labels: make(map[string]string),
40+
Tags: map[string]string{},
41+
Labels: map[string]string{},
4242
},
4343
},
4444
{
4545
name: "specified empties",
46-
parameters: map[string]string{ParameterKeyType: "", ParameterKeyReplicationType: "", ParameterKeyDiskEncryptionKmsKey: ""},
46+
parameters: map[string]string{ParameterKeyType: "", ParameterKeyReplicationType: "", ParameterKeyDiskEncryptionKmsKey: "", ParameterKeyLabels: ""},
4747
labels: map[string]string{},
4848
expectParams: DiskParameters{
4949
DiskType: "pd-standard",
5050
ReplicationType: "none",
5151
DiskEncryptionKMSKey: "",
52-
Tags: make(map[string]string),
53-
Labels: make(map[string]string),
52+
Tags: map[string]string{},
53+
Labels: map[string]string{},
5454
},
5555
},
5656
{
5757
name: "random keys",
58-
parameters: map[string]string{ParameterKeyType: "", "foo": "", ParameterKeyDiskEncryptionKmsKey: ""},
58+
parameters: map[string]string{ParameterKeyType: "", "foo": "", ParameterKeyDiskEncryptionKmsKey: "", ParameterKeyLabels: ""},
5959
labels: map[string]string{},
6060
expectErr: true,
6161
},
6262
{
6363
name: "real values",
64-
parameters: map[string]string{ParameterKeyType: "pd-ssd", ParameterKeyReplicationType: "regional-pd", ParameterKeyDiskEncryptionKmsKey: "foo/key"},
64+
parameters: map[string]string{ParameterKeyType: "pd-ssd", ParameterKeyReplicationType: "regional-pd", ParameterKeyDiskEncryptionKmsKey: "foo/key", ParameterKeyLabels: "key1=value1,key2=value2"},
6565
labels: map[string]string{},
6666
expectParams: DiskParameters{
6767
DiskType: "pd-ssd",
6868
ReplicationType: "regional-pd",
6969
DiskEncryptionKMSKey: "foo/key",
70-
Tags: make(map[string]string),
71-
Labels: make(map[string]string),
70+
Tags: map[string]string{},
71+
Labels: map[string]string{
72+
"key1": "value1",
73+
"key2": "value2",
74+
},
7275
},
7376
},
7477
{
@@ -79,8 +82,8 @@ func TestExtractAndDefaultParameters(t *testing.T) {
7982
DiskType: "pd-balanced",
8083
ReplicationType: "regional-pd",
8184
DiskEncryptionKMSKey: "foo/key",
82-
Tags: make(map[string]string),
83-
Labels: make(map[string]string),
85+
Tags: map[string]string{},
86+
Labels: map[string]string{},
8487
},
8588
},
8689
{
@@ -91,8 +94,8 @@ func TestExtractAndDefaultParameters(t *testing.T) {
9194
DiskType: "pd-standard",
9295
ReplicationType: "none",
9396
DiskEncryptionKMSKey: "foo/key",
94-
Tags: make(map[string]string),
95-
Labels: make(map[string]string),
97+
Tags: map[string]string{},
98+
Labels: map[string]string{},
9699
},
97100
},
98101
{
@@ -104,11 +107,11 @@ func TestExtractAndDefaultParameters(t *testing.T) {
104107
ReplicationType: "none",
105108
DiskEncryptionKMSKey: "",
106109
Tags: map[string]string{tagKeyCreatedForClaimName: "testPVCName", tagKeyCreatedForClaimNamespace: "testPVCNamespace", tagKeyCreatedForVolumeName: "testPVName", tagKeyCreatedBy: "testDriver"},
107-
Labels: make(map[string]string),
110+
Labels: map[string]string{},
108111
},
109112
},
110113
{
111-
name: "labels",
114+
name: "extra labels",
112115
parameters: map[string]string{},
113116
labels: map[string]string{"label-1": "label-value-1", "label-2": "label-value-2"},
114117
expectParams: DiskParameters{
@@ -119,6 +122,30 @@ func TestExtractAndDefaultParameters(t *testing.T) {
119122
Labels: map[string]string{"label-1": "label-value-1", "label-2": "label-value-2"},
120123
},
121124
},
125+
{
126+
name: "parameter and extra labels",
127+
parameters: map[string]string{ParameterKeyLabels: "key1=value1,key2=value2"},
128+
labels: map[string]string{"label-1": "label-value-1", "label-2": "label-value-2"},
129+
expectParams: DiskParameters{
130+
DiskType: "pd-standard",
131+
ReplicationType: "none",
132+
DiskEncryptionKMSKey: "",
133+
Tags: map[string]string{},
134+
Labels: map[string]string{"key1": "value1", "key2": "value2", "label-1": "label-value-1", "label-2": "label-value-2"},
135+
},
136+
},
137+
{
138+
name: "parameter and extra labels, overlapping",
139+
parameters: map[string]string{ParameterKeyLabels: "label-1=value-a,key1=value1"},
140+
labels: map[string]string{"label-1": "label-value-1", "label-2": "label-value-2"},
141+
expectParams: DiskParameters{
142+
DiskType: "pd-standard",
143+
ReplicationType: "none",
144+
DiskEncryptionKMSKey: "",
145+
Tags: map[string]string{},
146+
Labels: map[string]string{"key1": "value1", "label-1": "value-a", "label-2": "label-value-2"},
147+
},
148+
},
122149
}
123150

124151
for _, tc := range tests {

pkg/common/utils.go

+58
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package common
1818

1919
import (
2020
"fmt"
21+
"regexp"
2122
"strings"
2223

2324
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
@@ -156,3 +157,60 @@ func CreateNodeID(project, zone, name string) string {
156157
func CreateZonalVolumeID(project, zone, name string) string {
157158
return fmt.Sprintf(volIDZonalFmt, project, zone, name)
158159
}
160+
161+
// ConvertLabelsStringToMap converts the labels from string to map
162+
// example: "key1=value1,key2=value2" gets converted into {"key1": "value1", "key2": "value2"}
163+
func ConvertLabelsStringToMap(labels string) (map[string]string, error) {
164+
const labelsDelimiter = ","
165+
const labelsKeyValueDelimiter = "="
166+
167+
labelsMap := make(map[string]string)
168+
if labels == "" {
169+
return labelsMap, nil
170+
}
171+
172+
regexKey, _ := regexp.Compile(`^\p{Ll}[\p{Ll}0-9_-]{0,62}$`)
173+
checkLabelKeyFn := func(key string) error {
174+
if !regexKey.MatchString(key) {
175+
return fmt.Errorf("label value %q is invalid (should start with lowercase letter / lowercase letter, digit, _ and - chars are allowed / 1-63 characters", key)
176+
}
177+
return nil
178+
}
179+
180+
regexValue, _ := regexp.Compile(`^[\p{Ll}0-9_-]{0,63}$`)
181+
checkLabelValueFn := func(value string) error {
182+
if !regexValue.MatchString(value) {
183+
return fmt.Errorf("label value %q is invalid (lowercase letter, digit, _ and - chars are allowed / 0-63 characters", value)
184+
}
185+
186+
return nil
187+
}
188+
189+
keyValueStrings := strings.Split(labels, labelsDelimiter)
190+
for _, keyValue := range keyValueStrings {
191+
keyValue := strings.Split(keyValue, labelsKeyValueDelimiter)
192+
193+
if len(keyValue) != 2 {
194+
return nil, fmt.Errorf("labels %q are invalid, correct format: 'key1=value1,key2=value2'", labels)
195+
}
196+
197+
key := strings.TrimSpace(keyValue[0])
198+
if err := checkLabelKeyFn(key); err != nil {
199+
return nil, err
200+
}
201+
202+
value := strings.TrimSpace(keyValue[1])
203+
if err := checkLabelValueFn(value); err != nil {
204+
return nil, err
205+
}
206+
207+
labelsMap[key] = value
208+
}
209+
210+
const maxNumberOfLabels = 64
211+
if len(labelsMap) > maxNumberOfLabels {
212+
return nil, fmt.Errorf("more than %d labels is not allowed, given: %d", maxNumberOfLabels, len(labelsMap))
213+
}
214+
215+
return labelsMap, nil
216+
}

0 commit comments

Comments
 (0)