Merge pull request #85251 from leakingtapan/ebs-migraiton-zone

Add CSI migration logic for EBS storageclass zone/zones/topology
This commit is contained in:
Kubernetes Prow Robot 2019-11-16 03:27:40 -08:00 committed by GitHub
commit d362b4297c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 202 additions and 145 deletions

View File

@ -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 = [

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}
}