Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CSI migration logic for EBS storageclass zone/zones/topology #85251

Merged
merged 1 commit into from Nov 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions staging/src/k8s.io/csi-translation-lib/plugins/BUILD
Expand Up @@ -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 = [
Expand Down
30 changes: 24 additions & 6 deletions staging/src/k8s.io/csi-translation-lib/plugins/aws_ebs.go
Expand Up @@ -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{}
Expand All @@ -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
Expand Down
37 changes: 5 additions & 32 deletions staging/src/k8s.io/csi-translation-lib/plugins/gce_pd.go
Expand Up @@ -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{
{
Expand All @@ -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
Expand All @@ -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)
}
Expand Down
107 changes: 0 additions & 107 deletions staging/src/k8s.io/csi-translation-lib/plugins/gce_pd_test.go
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions staging/src/k8s.io/csi-translation-lib/plugins/in_tree_volume.go
Expand Up @@ -18,6 +18,7 @@ package plugins

import (
"errors"
"fmt"
"strings"

v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}