mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 20:24:09 +00:00
Merge pull request #108611 from divyenpatel/add-csi-topology-translation-for-in-tree-vSphere-volumes
topology translation of in-tree vSphere volume to vSphere CSI
This commit is contained in:
commit
dd2596e40d
@ -32,6 +32,12 @@ const (
|
||||
// VSphereInTreePluginName is the name of the in-tree plugin for vSphere Volume
|
||||
VSphereInTreePluginName = "kubernetes.io/vsphere-volume"
|
||||
|
||||
// vSphereCSITopologyZoneKey is the zonal topology key for vSphere CSI Driver
|
||||
vSphereCSITopologyZoneKey = "topology.csi.vmware.com/zone"
|
||||
|
||||
// vSphereCSITopologyRegionKey is the region topology key for vSphere CSI Driver
|
||||
vSphereCSITopologyRegionKey = "topology.csi.vmware.com/region"
|
||||
|
||||
// paramStoragePolicyName used to supply SPBM Policy name for Volume provisioning
|
||||
paramStoragePolicyName = "storagepolicyname"
|
||||
|
||||
@ -104,7 +110,14 @@ func (t *vSphereCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.Stor
|
||||
// When this is true, Driver returns initialvolumefilepath in the VolumeContext, which is
|
||||
// used in TranslateCSIPVToInTree
|
||||
params[paramcsiMigration] = "true"
|
||||
// Note: sc.AllowedTopologies for Topology based volume provisioning will be supplied as it is.
|
||||
// translate AllowedTopologies to vSphere CSI Driver topology
|
||||
if len(sc.AllowedTopologies) > 0 {
|
||||
newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, vSphereCSITopologyZoneKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed translating allowed topologies: %v", err)
|
||||
}
|
||||
sc.AllowedTopologies = newTopologies
|
||||
}
|
||||
sc.Parameters = params
|
||||
return sc, nil
|
||||
}
|
||||
@ -154,6 +167,10 @@ func (t *vSphereCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (
|
||||
if pv.Spec.VsphereVolume.StoragePolicyName != "" {
|
||||
csiSource.VolumeAttributes[paramStoragePolicyName] = pv.Spec.VsphereVolume.StoragePolicyName
|
||||
}
|
||||
// translate in-tree topology to CSI topology for migration
|
||||
if err := translateTopologyFromInTreevSphereToCSI(pv, vSphereCSITopologyZoneKey, vSphereCSITopologyRegionKey); err != nil {
|
||||
return nil, fmt.Errorf("failed to translate topology: %v", err)
|
||||
}
|
||||
pv.Spec.VsphereVolume = nil
|
||||
pv.Spec.CSI = csiSource
|
||||
return pv, nil
|
||||
@ -173,6 +190,10 @@ func (t *vSphereCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (
|
||||
if ok {
|
||||
vsphereVirtualDiskVolumeSource.VolumePath = volumeFilePath
|
||||
}
|
||||
// translate CSI topology to In-tree topology for rollback compatibility.
|
||||
if err := translateTopologyFromCSIToInTreevSphere(pv, vSphereCSITopologyZoneKey, vSphereCSITopologyRegionKey); err != nil {
|
||||
return nil, fmt.Errorf("failed to translate topology. PV:%+v. Error:%v", *pv, err)
|
||||
}
|
||||
pv.Spec.CSI = nil
|
||||
pv.Spec.VsphereVolume = vsphereVirtualDiskVolumeSource
|
||||
return pv, nil
|
||||
@ -206,3 +227,76 @@ func (t *vSphereCSITranslator) GetCSIPluginName() string {
|
||||
func (t *vSphereCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
|
||||
return volumeHandle, nil
|
||||
}
|
||||
|
||||
// translateTopologyFromInTreevSphereToCSI converts existing zone labels or in-tree vsphere topology to
|
||||
// vSphere CSI topology.
|
||||
func translateTopologyFromInTreevSphereToCSI(pv *v1.PersistentVolume, csiTopologyKeyZone string, csiTopologyKeyRegion string) error {
|
||||
zoneLabel, regionLabel := getTopologyLabel(pv)
|
||||
|
||||
// If Zone kubernetes topology exist, replace it to use csiTopologyKeyZone
|
||||
zones := getTopologyValues(pv, zoneLabel)
|
||||
if len(zones) > 0 {
|
||||
replaceTopology(pv, zoneLabel, csiTopologyKeyZone)
|
||||
} else {
|
||||
// if nothing is in the NodeAffinity, try to fetch the topology from PV labels
|
||||
if label, ok := pv.Labels[zoneLabel]; ok {
|
||||
if len(label) > 0 {
|
||||
addTopology(pv, csiTopologyKeyZone, []string{label})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If region kubernetes topology exist, replace it to use csiTopologyKeyRegion
|
||||
regions := getTopologyValues(pv, regionLabel)
|
||||
if len(regions) > 0 {
|
||||
replaceTopology(pv, regionLabel, csiTopologyKeyRegion)
|
||||
} else {
|
||||
// if nothing is in the NodeAffinity, try to fetch the topology from PV labels
|
||||
if label, ok := pv.Labels[regionLabel]; ok {
|
||||
if len(label) > 0 {
|
||||
addTopology(pv, csiTopologyKeyRegion, []string{label})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// translateTopologyFromCSIToInTreevSphere converts CSI zone/region affinity rules to in-tree vSphere zone/region labels
|
||||
func translateTopologyFromCSIToInTreevSphere(pv *v1.PersistentVolume,
|
||||
csiTopologyKeyZone string, csiTopologyKeyRegion string) error {
|
||||
zoneLabel, regionLabel := getTopologyLabel(pv)
|
||||
|
||||
// Replace all CSI topology to Kubernetes Zone label
|
||||
err := replaceTopology(pv, csiTopologyKeyZone, zoneLabel)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to replace CSI topology to Kubernetes topology, error: %v", err)
|
||||
}
|
||||
|
||||
// Replace all CSI topology to Kubernetes Region label
|
||||
err = replaceTopology(pv, csiTopologyKeyRegion, regionLabel)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to replace CSI topology to Kubernetes topology, error: %v", err)
|
||||
}
|
||||
|
||||
zoneVals := getTopologyValues(pv, zoneLabel)
|
||||
if len(zoneVals) > 0 {
|
||||
if pv.Labels == nil {
|
||||
pv.Labels = make(map[string]string)
|
||||
}
|
||||
_, zoneOK := pv.Labels[zoneLabel]
|
||||
if !zoneOK {
|
||||
pv.Labels[zoneLabel] = zoneVals[0]
|
||||
}
|
||||
}
|
||||
regionVals := getTopologyValues(pv, regionLabel)
|
||||
if len(regionVals) > 0 {
|
||||
if pv.Labels == nil {
|
||||
pv.Labels = make(map[string]string)
|
||||
}
|
||||
_, regionOK := pv.Labels[regionLabel]
|
||||
if !regionOK {
|
||||
pv.Labels[regionLabel] = regionVals[0]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -33,19 +33,17 @@ func TestTranslatevSphereInTreeStorageClassToCSI(t *testing.T) {
|
||||
Key: v1.LabelTopologyZone,
|
||||
Values: []string{"zone-a"},
|
||||
},
|
||||
{
|
||||
Key: v1.LabelTopologyRegion,
|
||||
Values: []string{"region-a"},
|
||||
},
|
||||
}}
|
||||
topologySelectorTermWithBetaLabels := v1.TopologySelectorTerm{[]v1.TopologySelectorLabelRequirement{
|
||||
{
|
||||
Key: v1.LabelFailureDomainBetaZone,
|
||||
Values: []string{"zone-a"},
|
||||
},
|
||||
}}
|
||||
expectedTopologySelectorTerm := v1.TopologySelectorTerm{[]v1.TopologySelectorLabelRequirement{
|
||||
{
|
||||
Key: v1.LabelFailureDomainBetaRegion,
|
||||
Values: []string{"region-a"},
|
||||
Key: vSphereCSITopologyZoneKey,
|
||||
Values: []string{"zone-a"},
|
||||
},
|
||||
}}
|
||||
cases := []struct {
|
||||
@ -88,27 +86,27 @@ func TestTranslatevSphereInTreeStorageClassToCSI(t *testing.T) {
|
||||
{
|
||||
name: "translate with no parameter and allowedTopology",
|
||||
sc: NewStorageClass(map[string]string{}, []v1.TopologySelectorTerm{topologySelectorTerm}),
|
||||
expSc: NewStorageClass(map[string]string{paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
|
||||
expSc: NewStorageClass(map[string]string{paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
|
||||
},
|
||||
{
|
||||
name: "translate with storagepolicyname and allowedTopology",
|
||||
sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
|
||||
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
|
||||
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
|
||||
},
|
||||
{
|
||||
name: "translate with storagepolicyname and allowedTopology beta labels",
|
||||
sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name"}, []v1.TopologySelectorTerm{topologySelectorTermWithBetaLabels}),
|
||||
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTermWithBetaLabels}),
|
||||
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
|
||||
},
|
||||
{
|
||||
name: "translate with raw vSAN policy parameters, datastore and diskformat",
|
||||
sc: NewStorageClass(map[string]string{"hostfailurestotolerate": "2", "datastore": "vsanDatastore", "diskformat": "thin"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
|
||||
expSc: NewStorageClass(map[string]string{"hostfailurestotolerate-migrationparam": "2", "datastore-migrationparam": "vsanDatastore", "diskformat-migrationparam": "thin", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
|
||||
expSc: NewStorageClass(map[string]string{"hostfailurestotolerate-migrationparam": "2", "datastore-migrationparam": "vsanDatastore", "diskformat-migrationparam": "thin", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
|
||||
},
|
||||
{
|
||||
name: "translate with all parameters",
|
||||
sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore": "test-datastore-name", "fstype": "ext4", "diskformat": "thin", "hostfailurestotolerate": "1", "forceprovisioning": "yes", "cachereservation": "25", "diskstripes": "4", "objectspacereservation": "10", "iopslimit": "32"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
|
||||
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore-migrationparam": "test-datastore-name", "csi.storage.k8s.io/fstype": "ext4", "diskformat-migrationparam": "thin", "hostfailurestotolerate-migrationparam": "1", "forceprovisioning-migrationparam": "yes", "cachereservation-migrationparam": "25", "diskstripes-migrationparam": "4", "objectspacereservation-migrationparam": "10", "iopslimit-migrationparam": "32", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
|
||||
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore-migrationparam": "test-datastore-name", "csi.storage.k8s.io/fstype": "ext4", "diskformat-migrationparam": "thin", "hostfailurestotolerate-migrationparam": "1", "forceprovisioning-migrationparam": "yes", "cachereservation-migrationparam": "25", "diskstripes-migrationparam": "4", "objectspacereservation-migrationparam": "10", "iopslimit-migrationparam": "32", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
@ -194,6 +192,84 @@ func TestTranslateVSphereCSIPVToInTree(t *testing.T) {
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "translate valid vSphere CSI PV with topology Node Affinity rules to vSphere CSI PV with topology labels",
|
||||
csiPV: &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
CSI: &v1.CSIPersistentVolumeSource{
|
||||
Driver: VSphereDriverName,
|
||||
VolumeHandle: "e4073a6d-642e-4dff-8f4a-b4e3a47c4bbd",
|
||||
FSType: "ext4",
|
||||
VolumeAttributes: map[string]string{
|
||||
paramStoragePolicyName: "vSAN Default Storage Policy",
|
||||
AttributeInitialVolumeFilepath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
|
||||
},
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
NodeAffinity: &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "topology.csi.vmware.com/zone",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"z1"},
|
||||
},
|
||||
{
|
||||
Key: "topology.csi.vmware.com/region",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"r1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
intreePV: &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
|
||||
Labels: map[string]string{"topology.kubernetes.io/zone": "z1", "topology.kubernetes.io/region": "r1"},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
|
||||
FSType: "ext4",
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
NodeAffinity: &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "topology.kubernetes.io/zone",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"z1"},
|
||||
},
|
||||
{
|
||||
Key: "topology.kubernetes.io/region",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"r1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
@ -264,6 +340,124 @@ func TestTranslateVSphereInTreePVToCSI(t *testing.T) {
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "translate valid vSphere in-tree PV with beta topology labels to vSphere CSI PV",
|
||||
intreePV: &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
|
||||
Labels: map[string]string{"failure-domain.beta.kubernetes.io/zone": "z1", "failure-domain.beta.kubernetes.io/region": "r1"},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
|
||||
FSType: "ext4",
|
||||
StoragePolicyName: "vSAN Default Storage Policy",
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
},
|
||||
},
|
||||
csiPV: &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
|
||||
Labels: map[string]string{"failure-domain.beta.kubernetes.io/zone": "z1", "failure-domain.beta.kubernetes.io/region": "r1"},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
CSI: &v1.CSIPersistentVolumeSource{
|
||||
Driver: VSphereDriverName,
|
||||
VolumeHandle: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
|
||||
FSType: "ext4",
|
||||
VolumeAttributes: map[string]string{
|
||||
paramStoragePolicyName: "vSAN Default Storage Policy",
|
||||
},
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
NodeAffinity: &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "topology.csi.vmware.com/zone",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"z1"},
|
||||
},
|
||||
{
|
||||
Key: "topology.csi.vmware.com/region",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"r1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
{
|
||||
name: "translate valid vSphere in-tree PV with GA topology labels to vSphere CSI PV",
|
||||
intreePV: &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
|
||||
Labels: map[string]string{"topology.kubernetes.io/zone": "z1", "topology.kubernetes.io/region": "r1"},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
|
||||
FSType: "ext4",
|
||||
StoragePolicyName: "vSAN Default Storage Policy",
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
},
|
||||
},
|
||||
csiPV: &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
|
||||
Labels: map[string]string{"topology.kubernetes.io/zone": "z1", "topology.kubernetes.io/region": "r1"},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
CSI: &v1.CSIPersistentVolumeSource{
|
||||
Driver: VSphereDriverName,
|
||||
VolumeHandle: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
|
||||
FSType: "ext4",
|
||||
VolumeAttributes: map[string]string{
|
||||
paramStoragePolicyName: "vSAN Default Storage Policy",
|
||||
},
|
||||
},
|
||||
},
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
NodeAffinity: &v1.VolumeNodeAffinity{
|
||||
Required: &v1.NodeSelector{
|
||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "topology.csi.vmware.com/zone",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"z1"},
|
||||
},
|
||||
{
|
||||
Key: "topology.csi.vmware.com/region",
|
||||
Operator: v1.NodeSelectorOpIn,
|
||||
Values: []string{"r1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
Loading…
Reference in New Issue
Block a user