Skip to content

Commit 6aea7fc

Browse files
committed
Added topology translation and backward compatible access modes
1 parent 101c629 commit 6aea7fc

File tree

7 files changed

+252
-27
lines changed

7 files changed

+252
-27
lines changed

staging/src/k8s.io/csi-translation-lib/plugins/BUILD

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,8 @@ go_test(
4040
"gce_pd_test.go",
4141
],
4242
embed = [":go_default_library"],
43-
deps = ["//staging/src/k8s.io/api/storage/v1:go_default_library"],
43+
deps = [
44+
"//staging/src/k8s.io/api/core/v1:go_default_library",
45+
"//staging/src/k8s.io/api/storage/v1:go_default_library",
46+
],
4447
)

staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func NewAWSElasticBlockStoreCSITranslator() InTreePlugin {
4545
}
4646

4747
// TranslateInTreeStorageClassParametersToCSI translates InTree EBS storage class parameters to CSI storage class
48-
func (t *awsElasticBlockStoreCSITranslator) TranslateInTreeVolumeOptionsToCSI(sc storage.StorageClass) (storage.StorageClass, error) {
48+
func (t *awsElasticBlockStoreCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
4949
return sc, nil
5050
}
5151

staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ const (
3333
// GCEPDInTreePluginName is the name of the intree plugin for GCE PD
3434
GCEPDInTreePluginName = "kubernetes.io/gce-pd"
3535

36+
// GCEPDTopologyKey is the zonal topology key for GCE PD CSI Driver
37+
GCEPDTopologyKey = "topology.gke.io/zone"
38+
3639
// Volume ID Expected Format
3740
// "projects/{projectName}/zones/{zoneName}/disks/{diskName}"
3841
volIDZonalFmt = "projects/%s/zones/%s/disks/%s"
@@ -56,24 +59,104 @@ func NewGCEPersistentDiskCSITranslator() InTreePlugin {
5659
return &gcePersistentDiskCSITranslator{}
5760
}
5861

62+
func translateAllowedTopologies(terms []v1.TopologySelectorTerm) ([]v1.TopologySelectorTerm, error) {
63+
if terms == nil {
64+
return nil, nil
65+
}
66+
67+
newTopologies := []v1.TopologySelectorTerm{}
68+
for _, term := range terms {
69+
newTerm := v1.TopologySelectorTerm{}
70+
for _, exp := range term.MatchLabelExpressions {
71+
var newExp v1.TopologySelectorLabelRequirement
72+
if exp.Key == v1.LabelZoneFailureDomain {
73+
newExp = v1.TopologySelectorLabelRequirement{
74+
Key: GCEPDTopologyKey,
75+
Values: exp.Values,
76+
}
77+
} else if exp.Key == GCEPDTopologyKey {
78+
newExp = exp
79+
} else {
80+
return nil, fmt.Errorf("unknown topology key: %v", exp.Key)
81+
}
82+
newTerm.MatchLabelExpressions = append(newTerm.MatchLabelExpressions, newExp)
83+
}
84+
newTopologies = append(newTopologies, newTerm)
85+
}
86+
return newTopologies, nil
87+
}
88+
89+
func generateToplogySelectors(key string, values []string) []v1.TopologySelectorTerm {
90+
return []v1.TopologySelectorTerm{
91+
{
92+
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
93+
{
94+
Key: key,
95+
Values: values,
96+
},
97+
},
98+
},
99+
}
100+
}
101+
59102
// TranslateInTreeStorageClassParametersToCSI translates InTree GCE storage class parameters to CSI storage class
60-
func (g *gcePersistentDiskCSITranslator) TranslateInTreeVolumeOptionsToCSI(sc storage.StorageClass) (storage.StorageClass, error) {
103+
func (g *gcePersistentDiskCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
104+
var generatedTopologies []v1.TopologySelectorTerm
105+
61106
np := map[string]string{}
62107
for k, v := range sc.Parameters {
63108
switch strings.ToLower(k) {
64109
case "fstype":
110+
// prefixed fstype parameter is stripped out by external provisioner
65111
np["csi.storage.k8s.io/fstype"] = v
112+
// Strip out zone and zones parameters and translate them into topologies instead
113+
case "zone":
114+
generatedTopologies = generateToplogySelectors(GCEPDTopologyKey, []string{v})
115+
case "zones":
116+
generatedTopologies = generateToplogySelectors(GCEPDTopologyKey, strings.Split(v, ","))
66117
default:
67118
np[k] = v
68119
}
69120
}
70-
sc.Parameters = np
71121

72-
// TODO(#77235): Translate AccessModes and zone/zones to AccessibleTopologies
122+
if len(generatedTopologies) > 0 && len(sc.AllowedTopologies) > 0 {
123+
return nil, fmt.Errorf("cannot simultaneously set allowed topologies and zone/zones parameters")
124+
} else if len(generatedTopologies) > 0 {
125+
sc.AllowedTopologies = generatedTopologies
126+
} else if len(sc.AllowedTopologies) > 0 {
127+
newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies)
128+
if err != nil {
129+
return nil, fmt.Errorf("failed translating allowed topologies: %v", err)
130+
}
131+
sc.AllowedTopologies = newTopologies
132+
}
133+
134+
sc.Parameters = np
73135

74136
return sc, nil
75137
}
76138

139+
// backwardCompatibleAccessModes translates all instances of ReadWriteMany
140+
// access mode from the in-tree plugin to ReadWriteOnce. This is because in-tree
141+
// plugin never supported ReadWriteMany but also did not validate or enforce
142+
// this access mode for pre-provisioned volumes. The GCE PD CSI Driver validates
143+
// and enforces (fails) ReadWriteMany. Therefore we treat all in-tree
144+
// ReadWriteMany as ReadWriteOnce volumes to not break legacy volumes.
145+
func backwardCompatibleAccessModes(ams []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
146+
if ams == nil {
147+
return nil
148+
}
149+
newAM := []v1.PersistentVolumeAccessMode{}
150+
for _, am := range ams {
151+
if am == v1.ReadWriteMany {
152+
newAM = append(newAM, v1.ReadWriteOnce)
153+
} else {
154+
newAM = append(newAM, am)
155+
}
156+
}
157+
return newAM
158+
}
159+
77160
// TranslateInTreePVToCSI takes a PV with GCEPersistentDisk set from in-tree
78161
// and converts the GCEPersistentDisk source to a CSIPersistentVolumeSource
79162
func (g *gcePersistentDiskCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
@@ -119,6 +202,7 @@ func (g *gcePersistentDiskCSITranslator) TranslateInTreePVToCSI(pv *v1.Persisten
119202

120203
pv.Spec.PersistentVolumeSource.GCEPersistentDisk = nil
121204
pv.Spec.PersistentVolumeSource.CSI = csiSource
205+
pv.Spec.AccessModes = backwardCompatibleAccessModes(pv.Spec.AccessModes)
122206

123207
return pv, nil
124208
}

staging/src/k8s.io/csi-translation-lib/plugins/gce_pd_test.go

Lines changed: 151 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,49 +24,186 @@ import (
2424
storage "k8s.io/api/storage/v1"
2525
)
2626

27-
func NewStorageClass(params map[string]string) storage.StorageClass {
28-
return storage.StorageClass{
29-
Parameters: params,
27+
func NewStorageClass(params map[string]string, allowedTopologies []v1.TopologySelectorTerm) *storage.StorageClass {
28+
return &storage.StorageClass{
29+
Parameters: params,
30+
AllowedTopologies: allowedTopologies,
3031
}
3132
}
3233

33-
func TestTranslatePDInTreeVolumeOptionsToCSI(t *testing.T) {
34+
func TestTranslatePDInTreeStorageClassToCSI(t *testing.T) {
3435
g := NewGCEPersistentDiskCSITranslator()
3536

3637
tcs := []struct {
3738
name string
38-
options storage.StorageClass
39-
expOptions storage.StorageClass
39+
options *storage.StorageClass
40+
expOptions *storage.StorageClass
41+
expErr bool
4042
}{
4143
{
4244
name: "nothing special",
43-
options: NewStorageClass(map[string]string{"foo": "bar"}),
44-
expOptions: NewStorageClass(map[string]string{"foo": "bar"}),
45+
options: NewStorageClass(map[string]string{"foo": "bar"}, nil),
46+
expOptions: NewStorageClass(map[string]string{"foo": "bar"}, nil),
4547
},
4648
{
4749
name: "fstype",
48-
options: NewStorageClass(map[string]string{"fstype": "myfs"}),
49-
expOptions: NewStorageClass(map[string]string{"csi.storage.k8s.io/fstype": "myfs"}),
50+
options: NewStorageClass(map[string]string{"fstype": "myfs"}, nil),
51+
expOptions: NewStorageClass(map[string]string{"csi.storage.k8s.io/fstype": "myfs"}, nil),
5052
},
5153
{
5254
name: "empty params",
53-
options: NewStorageClass(map[string]string{}),
54-
expOptions: NewStorageClass(map[string]string{}),
55+
options: NewStorageClass(map[string]string{}, nil),
56+
expOptions: NewStorageClass(map[string]string{}, nil),
57+
},
58+
{
59+
name: "zone",
60+
options: NewStorageClass(map[string]string{"zone": "foo"}, nil),
61+
expOptions: NewStorageClass(map[string]string{}, generateToplogySelectors(GCEPDTopologyKey, []string{"foo"})),
62+
},
63+
{
64+
name: "zones",
65+
options: NewStorageClass(map[string]string{"zones": "foo,bar,baz"}, nil),
66+
expOptions: NewStorageClass(map[string]string{}, generateToplogySelectors(GCEPDTopologyKey, []string{"foo", "bar", "baz"})),
67+
},
68+
{
69+
name: "some normal topology",
70+
options: NewStorageClass(map[string]string{}, generateToplogySelectors(GCEPDTopologyKey, []string{"foo"})),
71+
expOptions: NewStorageClass(map[string]string{}, generateToplogySelectors(GCEPDTopologyKey, []string{"foo"})),
72+
},
73+
{
74+
name: "some translated topology",
75+
options: NewStorageClass(map[string]string{}, generateToplogySelectors(v1.LabelZoneFailureDomain, []string{"foo"})),
76+
expOptions: NewStorageClass(map[string]string{}, generateToplogySelectors(GCEPDTopologyKey, []string{"foo"})),
77+
},
78+
{
79+
name: "zone and topology",
80+
options: NewStorageClass(map[string]string{"zone": "foo"}, generateToplogySelectors(GCEPDTopologyKey, []string{"foo"})),
81+
expErr: true,
5582
},
5683
}
5784

5885
for _, tc := range tcs {
5986
t.Logf("Testing %v", tc.name)
60-
gotOptions, err := g.TranslateInTreeVolumeOptionsToCSI(tc.options)
61-
if err != nil {
87+
gotOptions, err := g.TranslateInTreeStorageClassToCSI(tc.options)
88+
if err != nil && !tc.expErr {
6289
t.Errorf("Did not expect error but got: %v", err)
6390
}
91+
if err == nil && tc.expErr {
92+
t.Errorf("Expected error, but did not get one.")
93+
}
6494
if !reflect.DeepEqual(gotOptions, tc.expOptions) {
6595
t.Errorf("Got parameters: %v, expected :%v", gotOptions, tc.expOptions)
6696
}
6797
}
6898
}
6999

100+
func TestTranslateAllowedTopologies(t *testing.T) {
101+
testCases := []struct {
102+
name string
103+
topology []v1.TopologySelectorTerm
104+
expectedToplogy []v1.TopologySelectorTerm
105+
expErr bool
106+
}{
107+
{
108+
name: "no translation",
109+
topology: generateToplogySelectors(GCEPDTopologyKey, []string{"foo", "bar"}),
110+
expectedToplogy: []v1.TopologySelectorTerm{
111+
{
112+
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
113+
{
114+
Key: GCEPDTopologyKey,
115+
Values: []string{"foo", "bar"},
116+
},
117+
},
118+
},
119+
},
120+
},
121+
{
122+
name: "translate",
123+
topology: []v1.TopologySelectorTerm{
124+
{
125+
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
126+
{
127+
Key: "failure-domain.beta.kubernetes.io/zone",
128+
Values: []string{"foo", "bar"},
129+
},
130+
},
131+
},
132+
},
133+
expectedToplogy: []v1.TopologySelectorTerm{
134+
{
135+
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
136+
{
137+
Key: GCEPDTopologyKey,
138+
Values: []string{"foo", "bar"},
139+
},
140+
},
141+
},
142+
},
143+
},
144+
{
145+
name: "combo",
146+
topology: []v1.TopologySelectorTerm{
147+
{
148+
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
149+
{
150+
Key: "failure-domain.beta.kubernetes.io/zone",
151+
Values: []string{"foo", "bar"},
152+
},
153+
{
154+
Key: GCEPDTopologyKey,
155+
Values: []string{"boo", "baz"},
156+
},
157+
},
158+
},
159+
},
160+
expectedToplogy: []v1.TopologySelectorTerm{
161+
{
162+
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
163+
{
164+
Key: GCEPDTopologyKey,
165+
Values: []string{"foo", "bar"},
166+
},
167+
{
168+
Key: GCEPDTopologyKey,
169+
Values: []string{"boo", "baz"},
170+
},
171+
},
172+
},
173+
},
174+
},
175+
{
176+
name: "some other key",
177+
topology: []v1.TopologySelectorTerm{
178+
{
179+
MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
180+
{
181+
Key: "test",
182+
Values: []string{"foo", "bar"},
183+
},
184+
},
185+
},
186+
},
187+
expErr: true,
188+
},
189+
}
190+
191+
for _, tc := range testCases {
192+
t.Logf("Running test: %v", tc.name)
193+
gotTop, err := translateAllowedTopologies(tc.topology)
194+
if err != nil && !tc.expErr {
195+
t.Errorf("Did not expect an error, got: %v", err)
196+
}
197+
if err == nil && tc.expErr {
198+
t.Errorf("Expected an error but did not get one")
199+
}
200+
201+
if !reflect.DeepEqual(gotTop, tc.expectedToplogy) {
202+
t.Errorf("Expected topology: %v, but got: %v", tc.expectedToplogy, gotTop)
203+
}
204+
}
205+
}
206+
70207
func TestBackwardCompatibleAccessModes(t *testing.T) {
71208
testCases := []struct {
72209
name string

staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import (
2424
// InTreePlugin handles translations between CSI and in-tree sources in a PV
2525
type InTreePlugin interface {
2626

27-
// TranslateInTreeVolumeOptionsToCSI takes in-tree volume options
27+
// TranslateInTreeStorageClassToCSI takes in-tree volume options
2828
// and translates them to a volume options consumable by CSI plugin
29-
TranslateInTreeVolumeOptionsToCSI(sc storage.StorageClass) (storage.StorageClass, error)
29+
TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error)
3030

3131
// TranslateInTreePVToCSI takes a persistent volume and will translate
3232
// the in-tree source to a CSI Source. The input persistent volume can be modified

staging/src/k8s.io/csi-translation-lib/plugins/openstack_cinder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func NewOpenStackCinderCSITranslator() InTreePlugin {
4141
}
4242

4343
// TranslateInTreeStorageClassParametersToCSI translates InTree Cinder storage class parameters to CSI storage class
44-
func (t *osCinderCSITranslator) TranslateInTreeVolumeOptionsToCSI(sc storage.StorageClass) (storage.StorageClass, error) {
44+
func (t *osCinderCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
4545
return sc, nil
4646
}
4747

staging/src/k8s.io/csi-translation-lib/translate.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,16 @@ var (
3333
}
3434
)
3535

36-
// TranslateInTreeVolumeOptionsToCSI takes in-tree volume options
37-
// and translates them to a set of parameters consumable by CSI plugin
38-
func TranslateInTreeVolumeOptionsToCSI(inTreePluginName string, sc storage.StorageClass) (storage.StorageClass, error) {
36+
// TranslateInTreeStorageClassToCSI takes in-tree Storage Class
37+
// and translates it to a set of parameters consumable by CSI plugin
38+
func TranslateInTreeStorageClassToCSI(inTreePluginName string, sc *storage.StorageClass) (*storage.StorageClass, error) {
39+
newSC := sc.DeepCopy()
3940
for _, curPlugin := range inTreePlugins {
4041
if inTreePluginName == curPlugin.GetInTreePluginName() {
41-
return curPlugin.TranslateInTreeVolumeOptionsToCSI(sc)
42+
return curPlugin.TranslateInTreeStorageClassToCSI(newSC)
4243
}
4344
}
44-
return storage.StorageClass{}, fmt.Errorf("could not find in-tree storage class parameter translation logic for %#v", inTreePluginName)
45+
return nil, fmt.Errorf("could not find in-tree storage class parameter translation logic for %#v", inTreePluginName)
4546
}
4647

4748
// TranslateInTreePVToCSI takes a persistent volume and will translate

0 commit comments

Comments
 (0)