mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #85251 from leakingtapan/ebs-migraiton-zone
Add CSI migration logic for EBS storageclass zone/zones/topology
This commit is contained in:
commit
d362b4297c
@ -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 = [
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user