diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/BUILD b/staging/src/k8s.io/csi-translation-lib/plugins/BUILD index befec190443..d3125111b6f 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/BUILD +++ b/staging/src/k8s.io/csi-translation-lib/plugins/BUILD @@ -43,6 +43,7 @@ go_test( "azure_disk_test.go", "azure_file_test.go", "gce_pd_test.go", + "in_tree_volume_test.go", ], embed = [":go_default_library"], deps = [ diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go b/staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go index 7f66f39808c..5db1e921f8d 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go @@ -31,10 +31,10 @@ import ( const ( // AWSEBSDriverName is the name of the CSI driver for EBS AWSEBSDriverName = "ebs.csi.aws.com" - // AWSEBSTopologyKey is the zonal topology key for AWS EBS CSI Driver - AWSEBSTopologyKey = "topology.ebs.csi.aws.com/zone" // AWSEBSInTreePluginName is the name of the intree plugin for EBS AWSEBSInTreePluginName = "kubernetes.io/aws-ebs" + // AWSEBSTopologyKey is the zonal topology key for AWS EBS CSI driver + AWSEBSTopologyKey = "topology." + AWSEBSDriverName + "/zone" ) var _ InTreePlugin = &awsElasticBlockStoreCSITranslator{} @@ -49,17 +49,35 @@ func NewAWSElasticBlockStoreCSITranslator() InTreePlugin { // TranslateInTreeStorageClassToCSI translates InTree EBS storage class parameters to CSI storage class func (t *awsElasticBlockStoreCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) { - params := map[string]string{} - + var ( + generatedTopologies []v1.TopologySelectorTerm + params = map[string]string{} + ) for k, v := range sc.Parameters { switch strings.ToLower(k) { - case "fstype": - params["csi.storage.k8s.io/fstype"] = v + case fsTypeKey: + params[csiFsTypeKey] = v + case zoneKey: + generatedTopologies = generateToplogySelectors(AWSEBSTopologyKey, []string{v}) + case zonesKey: + generatedTopologies = generateToplogySelectors(AWSEBSTopologyKey, strings.Split(v, ",")) default: params[k] = v } } + if len(generatedTopologies) > 0 && len(sc.AllowedTopologies) > 0 { + return nil, fmt.Errorf("cannot simultaneously set allowed topologies and zone/zones parameters") + } else if len(generatedTopologies) > 0 { + sc.AllowedTopologies = generatedTopologies + } else if len(sc.AllowedTopologies) > 0 { + newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, AWSEBSTopologyKey) + if err != nil { + return nil, fmt.Errorf("failed translating allowed topologies: %v", err) + } + sc.AllowedTopologies = newTopologies + } + sc.Parameters = params return sc, nil diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go b/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go index 3340a53cdda..95677562d1c 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go @@ -65,33 +65,6 @@ func NewGCEPersistentDiskCSITranslator() InTreePlugin { return &gcePersistentDiskCSITranslator{} } -func translateAllowedTopologies(terms []v1.TopologySelectorTerm) ([]v1.TopologySelectorTerm, error) { - if terms == nil { - return nil, nil - } - - newTopologies := []v1.TopologySelectorTerm{} - for _, term := range terms { - newTerm := v1.TopologySelectorTerm{} - for _, exp := range term.MatchLabelExpressions { - var newExp v1.TopologySelectorLabelRequirement - if exp.Key == v1.LabelZoneFailureDomain { - newExp = v1.TopologySelectorLabelRequirement{ - Key: GCEPDTopologyKey, - Values: exp.Values, - } - } else if exp.Key == GCEPDTopologyKey { - newExp = exp - } else { - return nil, fmt.Errorf("unknown topology key: %v", exp.Key) - } - newTerm.MatchLabelExpressions = append(newTerm.MatchLabelExpressions, newExp) - } - newTopologies = append(newTopologies, newTerm) - } - return newTopologies, nil -} - func generateToplogySelectors(key string, values []string) []v1.TopologySelectorTerm { return []v1.TopologySelectorTerm{ { @@ -112,13 +85,13 @@ func (g *gcePersistentDiskCSITranslator) TranslateInTreeStorageClassToCSI(sc *st np := map[string]string{} for k, v := range sc.Parameters { switch strings.ToLower(k) { - case "fstype": + case fsTypeKey: // prefixed fstype parameter is stripped out by external provisioner - np["csi.storage.k8s.io/fstype"] = v + np[csiFsTypeKey] = v // Strip out zone and zones parameters and translate them into topologies instead - case "zone": + case zoneKey: generatedTopologies = generateToplogySelectors(GCEPDTopologyKey, []string{v}) - case "zones": + case zonesKey: generatedTopologies = generateToplogySelectors(GCEPDTopologyKey, strings.Split(v, ",")) default: np[k] = v @@ -130,7 +103,7 @@ func (g *gcePersistentDiskCSITranslator) TranslateInTreeStorageClassToCSI(sc *st } else if len(generatedTopologies) > 0 { sc.AllowedTopologies = generatedTopologies } else if len(sc.AllowedTopologies) > 0 { - newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies) + newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, GCEPDTopologyKey) if err != nil { return nil, fmt.Errorf("failed translating allowed topologies: %v", err) } diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd_test.go b/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd_test.go index be3d4f195cc..c11932affd7 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd_test.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/gce_pd_test.go @@ -98,113 +98,6 @@ func TestTranslatePDInTreeStorageClassToCSI(t *testing.T) { } } -func TestTranslateAllowedTopologies(t *testing.T) { - testCases := []struct { - name string - topology []v1.TopologySelectorTerm - expectedToplogy []v1.TopologySelectorTerm - expErr bool - }{ - { - name: "no translation", - topology: generateToplogySelectors(GCEPDTopologyKey, []string{"foo", "bar"}), - expectedToplogy: []v1.TopologySelectorTerm{ - { - MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ - { - Key: GCEPDTopologyKey, - Values: []string{"foo", "bar"}, - }, - }, - }, - }, - }, - { - name: "translate", - topology: []v1.TopologySelectorTerm{ - { - MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"foo", "bar"}, - }, - }, - }, - }, - expectedToplogy: []v1.TopologySelectorTerm{ - { - MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ - { - Key: GCEPDTopologyKey, - Values: []string{"foo", "bar"}, - }, - }, - }, - }, - }, - { - name: "combo", - topology: []v1.TopologySelectorTerm{ - { - MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ - { - Key: "failure-domain.beta.kubernetes.io/zone", - Values: []string{"foo", "bar"}, - }, - { - Key: GCEPDTopologyKey, - Values: []string{"boo", "baz"}, - }, - }, - }, - }, - expectedToplogy: []v1.TopologySelectorTerm{ - { - MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ - { - Key: GCEPDTopologyKey, - Values: []string{"foo", "bar"}, - }, - { - Key: GCEPDTopologyKey, - Values: []string{"boo", "baz"}, - }, - }, - }, - }, - }, - { - name: "some other key", - topology: []v1.TopologySelectorTerm{ - { - MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ - { - Key: "test", - Values: []string{"foo", "bar"}, - }, - }, - }, - }, - expErr: true, - }, - } - - for _, tc := range testCases { - t.Logf("Running test: %v", tc.name) - gotTop, err := translateAllowedTopologies(tc.topology) - if err != nil && !tc.expErr { - t.Errorf("Did not expect an error, got: %v", err) - } - if err == nil && tc.expErr { - t.Errorf("Expected an error but did not get one") - } - - if !reflect.DeepEqual(gotTop, tc.expectedToplogy) { - t.Errorf("Expected topology: %v, but got: %v", tc.expectedToplogy, gotTop) - } - } -} - func TestRepairVolumeHandle(t *testing.T) { testCases := []struct { name string diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go b/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go index a93d82cfb3f..eda5c10c27a 100644 --- a/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go +++ b/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go @@ -18,6 +18,7 @@ package plugins import ( "errors" + "fmt" "strings" v1 "k8s.io/api/core/v1" @@ -65,6 +66,17 @@ type InTreePlugin interface { RepairVolumeHandle(volumeHandle, nodeID string) (string, error) } +const ( + // fsTypeKey is the deprecated storage class parameter key for fstype + fsTypeKey = "fstype" + // csiFsTypeKey is the storage class parameter key for CSI fstype + csiFsTypeKey = "csi.storage.k8s.io/fstype" + // zoneKey is the deprecated storage class parameter key for zone + zoneKey = "zone" + // zonesKey is the deprecated storage class parameter key for zones + zonesKey = "zones" +) + // replaceTopology overwrites an existing topology key by a new one. func replaceTopology(pv *v1.PersistentVolume, oldKey, newKey string) error { for i := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms { @@ -153,3 +165,32 @@ func translateTopology(pv *v1.PersistentVolume, topologyKey string) error { return nil } + +// translateAllowedTopologies translates allowed topologies within storage class +// from legacy failure domain to given CSI topology key +func translateAllowedTopologies(terms []v1.TopologySelectorTerm, key string) ([]v1.TopologySelectorTerm, error) { + if terms == nil { + return nil, nil + } + + newTopologies := []v1.TopologySelectorTerm{} + for _, term := range terms { + newTerm := v1.TopologySelectorTerm{} + for _, exp := range term.MatchLabelExpressions { + var newExp v1.TopologySelectorLabelRequirement + if exp.Key == v1.LabelZoneFailureDomain { + newExp = v1.TopologySelectorLabelRequirement{ + Key: key, + Values: exp.Values, + } + } else if exp.Key == key { + newExp = exp + } else { + return nil, fmt.Errorf("unknown topology key: %v", exp.Key) + } + newTerm.MatchLabelExpressions = append(newTerm.MatchLabelExpressions, newExp) + } + newTopologies = append(newTopologies, newTerm) + } + return newTopologies, nil +} diff --git a/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume_test.go b/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume_test.go new file mode 100644 index 00000000000..895e169b1cc --- /dev/null +++ b/staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plugins + +import ( + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" +) + +func TestTranslateAllowedTopologies(t *testing.T) { + testCases := []struct { + name string + topology []v1.TopologySelectorTerm + expectedToplogy []v1.TopologySelectorTerm + expErr bool + }{ + { + name: "no translation", + topology: generateToplogySelectors(GCEPDTopologyKey, []string{"foo", "bar"}), + expectedToplogy: []v1.TopologySelectorTerm{ + { + MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ + { + Key: GCEPDTopologyKey, + Values: []string{"foo", "bar"}, + }, + }, + }, + }, + }, + { + name: "translate", + topology: []v1.TopologySelectorTerm{ + { + MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ + { + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"foo", "bar"}, + }, + }, + }, + }, + expectedToplogy: []v1.TopologySelectorTerm{ + { + MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ + { + Key: GCEPDTopologyKey, + Values: []string{"foo", "bar"}, + }, + }, + }, + }, + }, + { + name: "combo", + topology: []v1.TopologySelectorTerm{ + { + MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ + { + Key: "failure-domain.beta.kubernetes.io/zone", + Values: []string{"foo", "bar"}, + }, + { + Key: GCEPDTopologyKey, + Values: []string{"boo", "baz"}, + }, + }, + }, + }, + expectedToplogy: []v1.TopologySelectorTerm{ + { + MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ + { + Key: GCEPDTopologyKey, + Values: []string{"foo", "bar"}, + }, + { + Key: GCEPDTopologyKey, + Values: []string{"boo", "baz"}, + }, + }, + }, + }, + }, + { + name: "some other key", + topology: []v1.TopologySelectorTerm{ + { + MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ + { + Key: "test", + Values: []string{"foo", "bar"}, + }, + }, + }, + }, + expErr: true, + }, + } + + for _, tc := range testCases { + t.Logf("Running test: %v", tc.name) + gotTop, err := translateAllowedTopologies(tc.topology, GCEPDTopologyKey) + if err != nil && !tc.expErr { + t.Errorf("Did not expect an error, got: %v", err) + } + if err == nil && tc.expErr { + t.Errorf("Expected an error but did not get one") + } + + if !reflect.DeepEqual(gotTop, tc.expectedToplogy) { + t.Errorf("Expected topology: %v, but got: %v", tc.expectedToplogy, gotTop) + } + } +}